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