chiark / gitweb /
Add plugins, fix re-slicing when you edit the start/end gcode.
[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__), '../..', 'SteamEngine.exe'))
19         if hasattr(sys, 'frozen'):
20                 return os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../..', 'SteamEngine'))
21         return os.path.abspath(os.path.join(os.path.dirname(__file__), '../..', 'SteamEngine'))
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                 return '%02d:%02d' % (int(self._printTimeSeconds / 60 / 60), int(self._printTimeSeconds / 60) % 60)
86
87         def getFilamentAmount(self):
88                 return '%0.2fm %0.0fgram' % (float(self._filamentMM) / 1000.0, self.getFilamentWeight() * 1000.0)
89
90         def runSlicer(self, scene):
91                 self.abortSlicer()
92                 self._callback(-1.0, False)
93
94                 commandList = [getEngineFilename(), '-vv']
95                 for k, v in self._engineSettings().iteritems():
96                         commandList += ['-s', '%s=%s' % (k, str(v))]
97                 commandList += ['-o', self._exportFilename]
98                 commandList += ['-b', self._binaryStorageFilename]
99                 self._objCount = 0
100                 with open(self._binaryStorageFilename, "wb") as f:
101                         order = scene.printOrder()
102                         if order is None:
103                                 pos = numpy.array([profile.getPreferenceFloat('machine_width') * 1000 / 2, profile.getPreferenceFloat('machine_depth') * 1000 / 2])
104                                 commandList += ['-s', 'posx=%d' % int(pos[0]), '-s', 'posy=%d' % int(pos[1])]
105
106                                 vertexTotal = 0
107                                 for obj in scene.objects():
108                                         if scene.checkPlatform(obj):
109                                                 for mesh in obj._meshList:
110                                                         vertexTotal += mesh.vertexCount
111
112                                 f.write(numpy.array([vertexTotal], numpy.int32).tostring())
113                                 for obj in scene.objects():
114                                         if scene.checkPlatform(obj):
115                                                 for mesh in obj._meshList:
116                                                         vertexes = (numpy.matrix(mesh.vertexes, copy = False) * numpy.matrix(obj._matrix, numpy.float32)).getA()
117                                                         vertexes -= obj._drawOffset
118                                                         vertexes += numpy.array([obj.getPosition()[0], obj.getPosition()[1], 0.0])
119                                                         f.write(vertexes.tostring())
120
121                                 commandList += ['#']
122                                 self._objCount = 1
123                         else:
124                                 for n in order:
125                                         obj = scene.objects()[n]
126                                         for mesh in obj._meshList:
127                                                 f.write(numpy.array([mesh.vertexCount], numpy.int32).tostring())
128                                                 f.write(mesh.vertexes.tostring())
129                                         pos = obj.getPosition() * 1000
130                                         pos += numpy.array([profile.getPreferenceFloat('machine_width') * 1000 / 2, profile.getPreferenceFloat('machine_depth') * 1000 / 2])
131                                         commandList += ['-m', ','.join(map(str, obj._matrix.getA().flatten()))]
132                                         commandList += ['-s', 'posx=%d' % int(pos[0]), '-s', 'posy=%d' % int(pos[1])]
133                                         commandList += ['#' * len(obj._meshList)]
134                                         self._objCount += 1
135                 if self._objCount > 0:
136                         try:
137                                 self._process = self._runSliceProcess(commandList)
138                                 self._thread = threading.Thread(target=self._watchProcess)
139                                 self._thread.daemon = True
140                                 self._thread.start()
141                         except OSError:
142                                 traceback.print_exc()
143
144         def _watchProcess(self):
145                 self._callback(0.0, False)
146                 self._sliceLog = []
147                 self._printTimeSeconds = None
148                 self._filamentMM = None
149
150                 line = self._process.stdout.readline()
151                 objectNr = 0
152                 while len(line):
153                         line = line.strip()
154                         if line.startswith('Progress:'):
155                                 line = line.split(':')
156                                 if line[1] == 'process':
157                                         objectNr += 1
158                                 elif line[1] in self._progressSteps:
159                                         progressValue = float(line[2]) / float(line[3])
160                                         progressValue /= len(self._progressSteps)
161                                         progressValue += 1.0 / len(self._progressSteps) * self._progressSteps.index(line[1])
162
163                                         progressValue /= self._objCount
164                                         progressValue += 1.0 / self._objCount * objectNr
165                                         try:
166                                                 self._callback(progressValue, False)
167                                         except:
168                                                 pass
169                         elif line.startswith('Print time:'):
170                                 self._printTimeSeconds = int(line.split(':')[1].strip())
171                         elif line.startswith('Filament:'):
172                                 self._filamentMM = int(line.split(':')[1].strip())
173                         else:
174                                 self._sliceLog.append(line.strip())
175                         line = self._process.stdout.readline()
176                 for line in self._process.stderr:
177                         self._sliceLog.append(line.strip())
178                 returnCode = self._process.wait()
179                 try:
180                         if returnCode == 0:
181                                 profile.runPostProcessingPlugins(self._exportFilename)
182                                 self._callback(1.0, True)
183                         else:
184                                 self._callback(-1.0, False)
185                 except:
186                         pass
187                 self._process = None
188
189         def _engineSettings(self):
190                 settings = {
191                         'layerThickness': int(profile.getProfileSettingFloat('layer_height') * 1000),
192                         'initialLayerThickness': int(profile.getProfileSettingFloat('bottom_thickness') * 1000) if profile.getProfileSettingFloat('bottom_thickness') > 0.0 else int(profile.getProfileSettingFloat('layer_height') * 1000),
193                         'filamentDiameter': int(profile.getProfileSettingFloat('filament_diameter') * 1000),
194                         'filamentFlow': int(profile.getProfileSettingFloat('filament_flow')),
195                         'extrusionWidth': int(profile.calculateEdgeWidth() * 1000),
196                         'insetCount': int(profile.calculateLineCount()),
197                         'downSkinCount': int(profile.calculateSolidLayerCount()) if profile.getProfileSetting('solid_bottom') == 'True' else 0,
198                         'upSkinCount': int(profile.calculateSolidLayerCount()) if profile.getProfileSetting('solid_top') == 'True' else 0,
199                         'sparseInfillLineDistance': int(100 * profile.calculateEdgeWidth() * 1000 / profile.getProfileSettingFloat('fill_density')) if profile.getProfileSettingFloat('fill_density') > 0 else 9999999999,
200                         'initialSpeedupLayers': int(4),
201                         'initialLayerSpeed': int(profile.getProfileSettingFloat('bottom_layer_speed')),
202                         'printSpeed': int(profile.getProfileSettingFloat('print_speed')),
203                         'moveSpeed': int(profile.getProfileSettingFloat('travel_speed')),
204                         'fanOnLayerNr': int(profile.getProfileSettingFloat('fan_layer')),
205                         'supportAngle': int(-1) if profile.getProfileSetting('support') == 'None' else int(60),
206                         'supportEverywhere': int(1) if profile.getProfileSetting('support') == 'Everywhere' else int(0),
207                         'retractionAmount': int(profile.getProfileSettingFloat('retraction_amount') * 1000),
208                         'retractionSpeed': int(profile.getProfileSettingFloat('retraction_speed')),
209                         'objectSink': int(profile.getProfileSettingFloat('object_sink') * 1000),
210                         'minimalLayerTime': int(profile.getProfileSettingFloat('cool_min_layer_time')),
211                         'minimalFeedrate': int(profile.getProfileSettingFloat('cool_min_feedrate')),
212                         'coolHeadLift': 1 if profile.getProfileSetting('cool_head_lift') == 'True' else 0,
213                         'startCode': profile.getAlterationFileContents('start.gcode'),
214                         'endCode': profile.getAlterationFileContents('end.gcode'),
215                 }
216                 if profile.getProfileSetting('platform_adhesion') == 'Brim':
217                         settings['skirtDistance'] = 0.0
218                         settings['skirtLineCount'] = int(profile.getProfileSettingFloat('brim_line_count'))
219                 elif profile.getProfileSetting('platform_adhesion') == 'Raft':
220                         settings['skirtDistance'] = 0
221                         settings['skirtLineCount'] = 0
222                 else:
223                         settings['skirtDistance'] = int(profile.getProfileSettingFloat('skirt_gap') * 1000)
224                         settings['skirtLineCount'] = int(profile.getProfileSettingFloat('skirt_line_count'))
225                 return settings
226
227         def _runSliceProcess(self, cmdList):
228                 kwargs = {}
229                 if subprocess.mswindows:
230                         su = subprocess.STARTUPINFO()
231                         su.dwFlags |= subprocess.STARTF_USESHOWWINDOW
232                         su.wShowWindow = subprocess.SW_HIDE
233                         kwargs['startupinfo'] = su
234                         kwargs['creationflags'] = 0x00004000 #BELOW_NORMAL_PRIORITY_CLASS
235                 return subprocess.Popen(cmdList, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)