Skip to content

Commit d4d1aa0

Browse files
authored
SVG-conversion, possible filedialogs, GPU slicing
Added conversion of Slic3r multi svg-file to photon Added arguments to show dialogs for selecting input file and output file/directory Ported hackathon-slicer to python (GPU slicing) which can be used with argument -f False
1 parent 6b15af6 commit d4d1aa0

16 files changed

+1415
-122
lines changed

GL_Stl2Slices.py

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
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

Comments
 (0)