chiark / gitweb /
Draw the object sink. Fix the lay-flat function.
[cura.git] / Cura / util / sliceEngine.py
1 import subprocess
2 import time
3 import math
4 import numpy
5 import os
6 import warnings
7 import threading
8 import traceback
9 import platform
10 import sys
11
12 from Cura.util import profile
13
14 def getEngineFilename():
15         if platform.system() == 'Windows':
16                 if os.path.exists('C:/Software/Cura_SteamEngine/_bin/Release/Cura_SteamEngine.exe'):
17                         return 'C:/Software/Cura_SteamEngine/_bin/Release/Cura_SteamEngine.exe'
18                 return os.path.abspath(os.path.join(os.path.dirname(__file__), '../..', 'CuraEngine.exe'))
19         if hasattr(sys, 'frozen'):
20                 return os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../..', 'CuraEngine'))
21         return os.path.abspath(os.path.join(os.path.dirname(__file__), '../..', 'CuraEngine'))
22
23 def getTempFilename():
24         warnings.simplefilter('ignore')
25         ret = os.tempnam(None, "Cura_Tmp")
26         warnings.simplefilter('default')
27         return ret
28
29 class Slicer(object):
30         def __init__(self, progressCallback):
31                 self._process = None
32                 self._thread = None
33                 self._callback = progressCallback
34                 self._binaryStorageFilename = getTempFilename()
35                 self._exportFilename = getTempFilename()
36                 self._progressSteps = ['inset', 'skin', 'export']
37                 self._objCount = 0
38                 self._sliceLog = []
39                 self._printTimeSeconds = None
40                 self._filamentMM = None
41
42         def cleanup(self):
43                 self.abortSlicer()
44                 try:
45                         os.remove(self._binaryStorageFilename)
46                 except:
47                         pass
48                 try:
49                         os.remove(self._exportFilename)
50                 except:
51                         pass
52
53         def abortSlicer(self):
54                 if self._process is not None:
55                         try:
56                                 self._process.terminate()
57                         except:
58                                 pass
59                         self._thread.join()
60
61         def getGCodeFilename(self):
62                 return self._exportFilename
63
64         def getSliceLog(self):
65                 return self._sliceLog
66
67         def getFilamentWeight(self):
68                 #Calculates the weight of the filament in kg
69                 radius = float(profile.getProfileSetting('filament_diameter')) / 2
70                 volumeM3 = (self._filamentMM * (math.pi * radius * radius)) / (1000*1000*1000)
71                 return volumeM3 * profile.getPreferenceFloat('filament_physical_density')
72
73         def getFilamentCost(self):
74                 cost_kg = profile.getPreferenceFloat('filament_cost_kg')
75                 cost_meter = profile.getPreferenceFloat('filament_cost_meter')
76                 if cost_kg > 0.0 and cost_meter > 0.0:
77                         return "%.2f / %.2f" % (self.getFilamentWeight() * cost_kg, self._filamentMM / 1000.0 * cost_meter)
78                 elif cost_kg > 0.0:
79                         return "%.2f" % (self.getFilamentWeight() * cost_kg)
80                 elif cost_meter > 0.0:
81                         return "%.2f" % (self._filamentMM / 1000.0 * cost_meter)
82                 return None
83
84         def getPrintTime(self):
85                 if int(self._printTimeSeconds / 60 / 60) < 1:
86                         return '%d minutes' % (int(self._printTimeSeconds / 60) % 60)
87                 if int(self._printTimeSeconds / 60 / 60) == 1:
88                         return '%d hour %d minutes' % (int(self._printTimeSeconds / 60 / 60), int(self._printTimeSeconds / 60) % 60)
89                 return '%d hours %d minutes' % (int(self._printTimeSeconds / 60 / 60), int(self._printTimeSeconds / 60) % 60)
90
91         def getFilamentAmount(self):
92                 return '%0.2f meter %0.0f gram' % (float(self._filamentMM) / 1000.0, self.getFilamentWeight() * 1000.0)
93
94         def runSlicer(self, scene):
95                 self.abortSlicer()
96                 self._callback(-1.0, False)
97
98                 commandList = [getEngineFilename(), '-vv']
99                 for k, v in self._engineSettings().iteritems():
100                         commandList += ['-s', '%s=%s' % (k, str(v))]
101                 commandList += ['-o', self._exportFilename]
102                 commandList += ['-b', self._binaryStorageFilename]
103                 self._objCount = 0
104                 with open(self._binaryStorageFilename, "wb") as f:
105                         order = scene.printOrder()
106                         if order is None:
107                                 pos = numpy.array([profile.getPreferenceFloat('machine_width') * 1000 / 2, profile.getPreferenceFloat('machine_depth') * 1000 / 2])
108                                 commandList += ['-s', 'posx=%d' % int(pos[0]), '-s', 'posy=%d' % int(pos[1])]
109
110                                 vertexTotal = 0
111                                 for obj in scene.objects():
112                                         if scene.checkPlatform(obj):
113                                                 for mesh in obj._meshList:
114                                                         vertexTotal += mesh.vertexCount
115
116                                 f.write(numpy.array([vertexTotal], numpy.int32).tostring())
117                                 for obj in scene.objects():
118                                         if scene.checkPlatform(obj):
119                                                 for mesh in obj._meshList:
120                                                         vertexes = (numpy.matrix(mesh.vertexes, copy = False) * numpy.matrix(obj._matrix, numpy.float32)).getA()
121                                                         vertexes -= obj._drawOffset
122                                                         vertexes += numpy.array([obj.getPosition()[0], obj.getPosition()[1], 0.0])
123                                                         f.write(vertexes.tostring())
124
125                                 commandList += ['#']
126                                 self._objCount = 1
127                         else:
128                                 for n in order:
129                                         obj = scene.objects()[n]
130                                         for mesh in obj._meshList:
131                                                 f.write(numpy.array([mesh.vertexCount], numpy.int32).tostring())
132                                                 f.write(mesh.vertexes.tostring())
133                                         pos = obj.getPosition() * 1000
134                                         pos += numpy.array([profile.getPreferenceFloat('machine_width') * 1000 / 2, profile.getPreferenceFloat('machine_depth') * 1000 / 2])
135                                         commandList += ['-m', ','.join(map(str, obj._matrix.getA().flatten()))]
136                                         commandList += ['-s', 'posx=%d' % int(pos[0]), '-s', 'posy=%d' % int(pos[1])]
137                                         commandList += ['#' * len(obj._meshList)]
138                                         self._objCount += 1
139                 if self._objCount > 0:
140                         try:
141                                 self._process = self._runSliceProcess(commandList)
142                                 self._thread = threading.Thread(target=self._watchProcess)
143                                 self._thread.daemon = True
144                                 self._thread.start()
145                         except OSError:
146                                 traceback.print_exc()
147
148         def _watchProcess(self):
149                 self._callback(0.0, False)
150                 self._sliceLog = []
151                 self._printTimeSeconds = None
152                 self._filamentMM = None
153
154                 line = self._process.stdout.readline()
155                 objectNr = 0
156                 while len(line):
157                         line = line.strip()
158                         if line.startswith('Progress:'):
159                                 line = line.split(':')
160                                 if line[1] == 'process':
161                                         objectNr += 1
162                                 elif line[1] in self._progressSteps:
163                                         progressValue = float(line[2]) / float(line[3])
164                                         progressValue /= len(self._progressSteps)
165                                         progressValue += 1.0 / len(self._progressSteps) * self._progressSteps.index(line[1])
166
167                                         progressValue /= self._objCount
168                                         progressValue += 1.0 / self._objCount * objectNr
169                                         try:
170                                                 self._callback(progressValue, False)
171                                         except:
172                                                 pass
173                         elif line.startswith('Print time:'):
174                                 self._printTimeSeconds = int(line.split(':')[1].strip())
175                         elif line.startswith('Filament:'):
176                                 self._filamentMM = int(line.split(':')[1].strip())
177                         else:
178                                 self._sliceLog.append(line.strip())
179                         line = self._process.stdout.readline()
180                 for line in self._process.stderr:
181                         self._sliceLog.append(line.strip())
182                 returnCode = self._process.wait()
183                 try:
184                         if returnCode == 0:
185                                 profile.runPostProcessingPlugins(self._exportFilename)
186                                 self._callback(1.0, True)
187                         else:
188                                 self._callback(-1.0, False)
189                 except:
190                         pass
191                 self._process = None
192
193         def _engineSettings(self):
194                 settings = {
195                         'layerThickness': int(profile.getProfileSettingFloat('layer_height') * 1000),
196                         'initialLayerThickness': int(profile.getProfileSettingFloat('bottom_thickness') * 1000) if profile.getProfileSettingFloat('bottom_thickness') > 0.0 else int(profile.getProfileSettingFloat('layer_height') * 1000),
197                         'filamentDiameter': int(profile.getProfileSettingFloat('filament_diameter') * 1000),
198                         'filamentFlow': int(profile.getProfileSettingFloat('filament_flow')),
199                         'extrusionWidth': int(profile.calculateEdgeWidth() * 1000),
200                         'insetCount': int(profile.calculateLineCount()),
201                         'downSkinCount': int(profile.calculateSolidLayerCount()) if profile.getProfileSetting('solid_bottom') == 'True' else 0,
202                         'upSkinCount': int(profile.calculateSolidLayerCount()) if profile.getProfileSetting('solid_top') == 'True' else 0,
203                         'sparseInfillLineDistance': int(100 * profile.calculateEdgeWidth() * 1000 / profile.getProfileSettingFloat('fill_density')) if profile.getProfileSettingFloat('fill_density') > 0 else -1,
204                         'infillOverlap': int(profile.getProfileSettingFloat('fill_overlap')),
205                         'initialSpeedupLayers': int(4),
206                         'initialLayerSpeed': int(profile.getProfileSettingFloat('bottom_layer_speed')),
207                         'printSpeed': int(profile.getProfileSettingFloat('print_speed')),
208                         'infillSpeed': int(profile.getProfileSettingFloat('infill_speed')) if int(profile.getProfileSettingFloat('infill_speed')) > 0 else int(profile.getProfileSettingFloat('print_speed')),
209                         'moveSpeed': int(profile.getProfileSettingFloat('travel_speed')),
210                         'fanOnLayerNr': int(profile.getProfileSettingFloat('fan_layer')),
211                         'fanSpeedMin': int(profile.getProfileSettingFloat('fan_speed')) if profile.getProfileSetting('fan_enabled') == 'True' else 0,
212                         'fanSpeedMax': int(profile.getProfileSettingFloat('fan_speed_max')) if profile.getProfileSetting('fan_enabled') == 'True' else 0,
213                         'supportAngle': int(-1) if profile.getProfileSetting('support') == 'None' else int(60),
214                         'supportEverywhere': int(1) if profile.getProfileSetting('support') == 'Everywhere' else int(0),
215                         'supportLineWidth': int(profile.getProfileSettingFloat('support_rate') * profile.calculateEdgeWidth() * 1000 / 100),
216                         'retractionAmount': int(profile.getProfileSettingFloat('retraction_amount') * 1000),
217                         'retractionSpeed': int(profile.getProfileSettingFloat('retraction_speed')),
218                         'objectSink': int(profile.getProfileSettingFloat('object_sink') * 1000),
219                         'minimalLayerTime': int(profile.getProfileSettingFloat('cool_min_layer_time')),
220                         'minimalFeedrate': int(profile.getProfileSettingFloat('cool_min_feedrate')),
221                         'coolHeadLift': 1 if profile.getProfileSetting('cool_head_lift') == 'True' else 0,
222                         'startCode': profile.getAlterationFileContents('start.gcode'),
223                         'endCode': profile.getAlterationFileContents('end.gcode'),
224
225                         'extruderOffset[1].X': int(profile.getPreferenceFloat('extruder_offset_x1') * 1000),
226                         'extruderOffset[1].Y': int(profile.getPreferenceFloat('extruder_offset_y1') * 1000),
227                         'extruderOffset[2].X': int(profile.getPreferenceFloat('extruder_offset_x2') * 1000),
228                         'extruderOffset[2].Y': int(profile.getPreferenceFloat('extruder_offset_y2') * 1000),
229                         'extruderOffset[3].X': int(profile.getPreferenceFloat('extruder_offset_x3') * 1000),
230                         'extruderOffset[3].Y': int(profile.getPreferenceFloat('extruder_offset_y3') * 1000),
231                 }
232                 if profile.getProfileSetting('platform_adhesion') == 'Brim':
233                         settings['skirtDistance'] = 0
234                         settings['skirtLineCount'] = int(profile.getProfileSettingFloat('brim_line_count'))
235                 elif profile.getProfileSetting('platform_adhesion') == 'Raft':
236                         settings['skirtDistance'] = 0
237                         settings['skirtLineCount'] = 0
238                         settings['raftMargin'] = int(profile.getProfileSettingFloat('raft_margin') * 1000);
239                         settings['raftBaseThickness'] = int(profile.getProfileSettingFloat('raft_base_thickness') * 1000);
240                         settings['raftBaseLinewidth'] = int(profile.getProfileSettingFloat('raft_base_linewidth') * 1000);
241                         settings['raftInterfaceThickness'] = int(profile.getProfileSettingFloat('raft_interface_thickness') * 1000);
242                         settings['raftInterfaceLinewidth'] = int(profile.getProfileSettingFloat('raft_interface_linewidth') * 1000);
243                 else:
244                         settings['skirtDistance'] = int(profile.getProfileSettingFloat('skirt_gap') * 1000)
245                         settings['skirtLineCount'] = int(profile.getProfileSettingFloat('skirt_line_count'))
246                 if settings['layerThickness'] <= 0:
247                         settings['layerThickness'] = 1000
248                 return settings
249
250         def _runSliceProcess(self, cmdList):
251                 kwargs = {}
252                 if subprocess.mswindows:
253                         su = subprocess.STARTUPINFO()
254                         su.dwFlags |= subprocess.STARTF_USESHOWWINDOW
255                         su.wShowWindow = subprocess.SW_HIDE
256                         kwargs['startupinfo'] = su
257                         kwargs['creationflags'] = 0x00004000 #BELOW_NORMAL_PRIORITY_CLASS
258                 return subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)