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