-
Notifications
You must be signed in to change notification settings - Fork 11
/
GL_Stl2Slices.py
245 lines (199 loc) · 8.44 KB
/
GL_Stl2Slices.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
# Port of https:#github.com/Formlabs/hackathon-slicer/blob/master/app/js/slicer.js
#
#
# internal
import struct
import math
import time
import os
import sys # for stdout
# external
import cv2
import numpy
# user
import GL_Viewport
import rleEncode
from PhotonFile import *
class GL_Stl2Slices:
gui=False
viewport = None
def clearModel(self):
self.points = []
self.normals = []
self.cmin = []
self.cmax = []
def load_binary_stl(self,filename, scale=1):
print("Reading binary")
# filebytes = os.path.getsize(filename)
#scale=scale*0.1
fp = open(filename, 'rb')
h = fp.read(80)
l = struct.unpack('I', fp.read(4))[0]
count = 0
t0 = time.time()
self.clearModel()
points = []
normals = []
filepos = 0
while True:
try:
p = fp.read(12)
if len(p) == 12:
n = struct.unpack('f', p[0:4])[0], struct.unpack('f', p[4:8])[0], struct.unpack('f', p[8:12])[0]
p = fp.read(12)
if len(p) == 12:
p1 = struct.unpack('f', p[0:4])[0], struct.unpack('f', p[4:8])[0], struct.unpack('f', p[8:12])[0]
p = fp.read(12)
if len(p) == 12:
p2 = struct.unpack('f', p[0:4])[0], struct.unpack('f', p[4:8])[0], struct.unpack('f', p[8:12])[0]
p = fp.read(12)
if len(p) == 12:
p3 = struct.unpack('f', p[0:4])[0], struct.unpack('f', p[4:8])[0], struct.unpack('f', p[8:12])[0]
if len(p) == 12:
# switch coordinates to OpenGL
a = 0
b = 1
c = 2
n = [n[a], n[b], n[c]]
p1 = [p1[a], p1[b], p1[c]]
p2 = [p2[a], p2[b], p2[c]]
p3 = [p3[a], p3[b], p3[c]]
# add points to array
points.append(p1)
points.append(p2)
points.append(p3)
normals.append(n)
count += 1
fp.read(2)
# Check if we reached end of file
if len(p) == 0:
break
except EOFError:
break
fp.close()
# t1=time.time()
# print ("t1-t0",t1-t0)
# use numpy for easy and fast center and scale model
np_points = numpy.array(points)
np_normals = numpy.array(normals)
# scale model, 1mm should be 1/0,047 pixels
#scale=scale/0.047
np_points = np_points * scale
# find max and min of x, y and z
x = np_points[:, 0]
y = np_points[:, 1]
z = np_points[:, 2]
self.cmin = (x.min(), y.min(), z.min())
self.cmax = (x.max(), y.max(), z.max())
self.modelheight = self.cmax[2] - self.cmin[2]
#print ("min: ",self.cmin)
#print ("max: ",self.cmax)
# Center model and put on base
#trans = [0, 0, 0]
#trans[0] = -(self.cmax[0] - self.cmin[0]) / 2 - self.cmin[0]
#trans[1] = -(self.cmax[2] - self.cmin[2]) / 2 - self.cmin[2]
#trans[2] = -self.cmin[1]
# We want the model centered in 2560x1440
# 2560x1440 pixels equals 120x67
#trans[0] = trans[0] +1440 / 2
#trans[2] = trans[2] +2560 / 2
# Center numpy array of points which is returned for fast OGL model loading
#np_points = np_points + trans
# Find bounding box again
x = np_points[:, 0]
y = np_points[:, 1]
z = np_points[:, 2]
self.cmin = (x.min(), y.min(), z.min())
self.cmax = (x.max(), y.max(), z.max())
# align coordinates on grid
# this will reduce number of points and speed up loading
# with benchy grid-screenres/1: total time 28 sec, nr points remain 63k , but large artifacts
# with benchy grid-screenres/50: total time 39 sec, nr points remain 112k, no artifacts
# w/o benchy : total time 40 sec, nr points remain 113k, no artifacts
#screenres = 0.047
#grid = screenres / 50 # we do not want artifacts but reduce rounding errors in the file to cause misconnected triangles
#np_points = grid * (np_points // grid)
# return points and normal for OGLEngine to display
return np_points, np_normals
def __init__(self, stlfilename, scale=1,
outputpath=None, # should end with '/'
layerheight=0.05,
photonfilename=None, # keep outputpath=None if output to photonfilename
normalexposure=8.0,
bottomexposure=90,
bottomlayers=8,
offtime=6.5,
):
self.viewport = GL_Viewport.Viewport()
# Get path of script/exe for local resources like iconpath and newfile.photon
if getattr(sys, 'frozen', False):# frozen
self.installpath = os.path.dirname(sys.executable)
else: # unfrozen
self.installpath = os.path.dirname(os.path.realpath(__file__))
# Measure how long it takes
t1 = time.time()
# Setup output path
if outputpath==None and photonfilename==None:return
#create path if not exists
if not outputpath==None:
if not os.path.exists(outputpath):
os.makedirs(outputpath)
# if we output to PhotonFile we need a place to store RunLengthEncoded images
if not photonfilename==None:
rlestack=[]
# Load 3d Model in memory
points, normals = self.load_binary_stl(stlfilename, scale=scale)
# Check if inside build area
size=(self.cmax[0]-self.cmin[0],self.cmax[1]-self.cmin[1],self.cmax[2]-self.cmin[2])
if size[0]>65 or size[1]>115:
sizestr="("+str(int(size[0]))+"x"+str(int(size[2]))+")"
areastr="(65x115)"
errmsg="Model is too big "+sizestr+" for build area "+areastr+". Maybe try another orientation, use the scale argument (-s or --scale) or cut up the model."
if not self.gui:
print (errmsg)
else:
sys.tracebacklimit = None
raise Exception(errmsg)
sys.tracebacklimit = 0
sys.exit() # quit() does not work if we make this an exe with cx_Freeze
# Load mesh
#print ("loading mesh")
self.viewport.loadMesh(points,normals,self.cmin,self.cmax);
#self.viewport.display() # this will loop until window is closed
self.viewport.draw()
microns = layerheight*1000 #document.getElementById("height").value;
bounds = self.viewport.getBounds()
#print ((bounds['zmax']-bounds['zmin']) , self.viewport.printer.getGLscale())
#quit()
zrange_mm=(bounds['zmax']-bounds['zmin']) / self.viewport.printer.getGLscale()
count=math.ceil(zrange_mm * 1000 / microns);
#print ("b",bounds)
#print ("z",zrange_mm)
#print ("m",microns)
#print ("c",count)
if not photonfilename==None:
rlestack=[]
for i in range(0,count):
data = self.viewport.getSliceAt(i / count)
img=data.reshape(2560,1440,4)
imgarr8=img[:,:,1]
if photonfilename==None:
Sstr = "%04d" % i
filename = outputpath+Sstr + ".png"
print (i,"/",count,filename)
cv2.imwrite(filename, imgarr8)
else:
img1D=imgarr8.flatten(0)
rlestack.append(rleEncode.encodedBitmap_Bytes_numpy1DBlock(img1D))
if not photonfilename==None:
tempfilename=os.path.join(self.installpath,"newfile.photon")
photonfile=PhotonFile(tempfilename)
photonfile.readFile()
photonfile.Header["Layer height (mm)"]= PhotonFile.float_to_bytes(layerheight)
photonfile.Header["Exp. time (s)"] = PhotonFile.float_to_bytes(normalexposure)
photonfile.Header["Exp. bottom (s)"] = PhotonFile.float_to_bytes(bottomexposure)
photonfile.Header["# Bottom Layers"] = PhotonFile.int_to_bytes(bottomlayers)
photonfile.Header["Off time (s)"] = PhotonFile.float_to_bytes(offtime)
photonfile.replaceBitmaps(rlestack)
photonfile.writeFile(photonfilename)
print("Elapsed: ", "%.2f" % (time.time() - t1), "secs")