chiark / gitweb /
Add back the ultimaker platform, and made the platform mesh simpler.
[cura.git] / Cura / slice / cura_sf / skeinforge_application / skeinforge_plugins / craft_plugins / raft.py
1 """
2 This page is in the table of contents.
3 Raft is a plugin to create a raft, elevate the nozzle and set the temperature.  A raft is a flat base structure on top of which your object is being build and has a few different purposes. It fills irregularities like scratches and pits in your printbed and gives you a nice base parallel to the printheads movement. It also glues your object to the bed so to prevent warping in bigger object.  The rafts base layer performs these tricks while the sparser interface layer(s) help you removing the object from the raft after printing.  It is based on the Nophead's reusable raft, which has a base layer running one way, and a couple of perpendicular layers above.  Each set of layers can be set to a different temperature.  There is the option of having the extruder orbit the raft for a while, so the heater barrel has time to reach a different temperature, without ooze accumulating around the nozzle.
4
5 The raft manual page is at:
6 http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Raft
7
8 The important values for the raft settings are the temperatures of the raft, the first layer and the next layers.  These will be different for each material.  The default settings for ABS, HDPE, PCL & PLA are extrapolated from Nophead's experiments.
9
10 You don't necessarily need a raft and especially small object will print fine on a flat bed without one, sometimes its even better when you need a water tight base to print directly on the bed.  If you want to only set the temperature or only create support material or only elevate the nozzle without creating a raft, set the Base Layers and Interface Layers to zero.
11
12 <gallery perRow="1">
13 Image:Raft.jpg|Raft
14 </gallery>
15
16 Example of a raft on the left with the interface layers partially removed exposing the base layer. Notice that the first line of the base is rarely printed well because of the startup time of the extruder. On the right you see an object with its raft still attached.
17
18 The Raft panel has some extra settings, it probably made sense to have them there but they have not that much to do with the actual Raft. First are the Support material settings. Since close to all RepRap style printers have no second extruder for support material Skeinforge offers the option to print support structures with the same material set at a different speed and temperature. The idea is that the support sticks less to the actual object when it is extruded around the minimum possible working temperature. This results in a temperature change EVERY layer so build time will increase seriously.
19
20 Allan Ecker aka The Masked Retriever's has written two quicktips for raft which follow below.
21 "Skeinforge Quicktip: The Raft, Part 1" at:
22 http://blog.thingiverse.com/2009/07/14/skeinforge-quicktip-the-raft-part-1/
23 "Skeinforge Quicktip: The Raft, Part II" at:
24 http://blog.thingiverse.com/2009/08/04/skeinforge-quicktip-the-raft-part-ii/
25
26 Nophead has written about rafts on his blog:
27 http://hydraraptor.blogspot.com/2009/07/thoughts-on-rafts.html
28
29 More pictures of rafting in action are available from the Metalab blog at:
30 http://reprap.soup.io/?search=rafting
31
32 ==Operation==
33 Default: On
34
35 When it is on, the functions described below will work, when it is off, nothing will be done, so no temperatures will be set, nozzle will not be lifted..
36
37 ==Settings==
38 ===Add Raft, Elevate Nozzle, Orbit===
39 Default: On
40
41 When selected, the script will also create a raft, elevate the nozzle, orbit and set the altitude of the bottom of the raft.  It also turns on support generation.
42
43 ===Base===
44 Base layer is the part of the raft that touches the bed.
45
46 ====Base Feed Rate Multiplier====
47 Default is one.
48
49 Defines the base feed rate multiplier.  The greater the 'Base Feed Rate Multiplier', the thinner the base, the lower the 'Base Feed Rate Multiplier', the thicker the base.
50
51 ====Base Flow Rate Multiplier====
52 Default is one.
53
54 Defines the base flow rate multiplier.  The greater the 'Base Flow Rate Multiplier', the thicker the base, the lower the 'Base Flow Rate Multiplier', the thinner the base.
55
56 ====Base Infill Density====
57 Default is 0.5.
58
59 Defines the infill density ratio of the base of the raft.
60
61 ====Base Layer Height over Layer Thickness====
62 Default is two.
63
64 Defines the ratio of the height & width of the base layer compared to the height and width of the object infill.  The feed rate will be slower for raft layers which have thicker extrusions than the object infill.
65
66 ====Base Layers====
67 Default is one.
68
69 Defines the number of base layers.
70
71 ====Base Nozzle Lift over Base Layer Thickness====
72 Default is 0.4.
73
74 Defines the amount the nozzle is above the center of the base extrusion divided by the base layer thickness.
75
76 ===Initial Circling===
77 Default is off.
78
79 When selected, the extruder will initially circle around until it reaches operating temperature.
80
81 ===Infill Overhang over Extrusion Width===
82 Default is 0.05.
83
84 Defines the ratio of the infill overhang over the the extrusion width of the raft.
85
86 ===Interface===
87 ====Interface Feed Rate Multiplier====
88 Default is one.
89
90 Defines the interface feed rate multiplier.  The greater the 'Interface Feed Rate Multiplier', the thinner the interface, the lower the 'Interface Feed Rate Multiplier', the thicker the interface.
91
92 ====Interface Flow Rate Multiplier====
93 Default is one.
94
95 Defines the interface flow rate multiplier.  The greater the 'Interface Flow Rate Multiplier', the thicker the interface, the lower the 'Interface Flow Rate Multiplier', the thinner the interface.
96
97 ====Interface Infill Density====
98 Default is 0.5.
99
100 Defines the infill density ratio of the interface of the raft.
101
102 ====Interface Layer Thickness over Extrusion Height====
103 Default is one.
104
105 Defines the ratio of the height & width of the interface layer compared to the height and width of the object infill.  The feed rate will be slower for raft layers which have thicker extrusions than the object infill.
106
107 ====Interface Layers====
108 Default is two.
109
110 Defines the number of interface layers to print.
111
112 ====Interface Nozzle Lift over Interface Layer Thickness====
113 Default is 0.45.
114
115 Defines the amount the nozzle is above the center of the interface extrusion divided by the interface layer thickness.
116
117 ===Name of Alteration Files===
118 If support material is generated, raft looks for alteration files in the alterations folder in the .skeinforge folder in the home directory.  Raft does not care if the text file names are capitalized, but some file systems do not handle file name cases properly, so to be on the safe side you should give them lower case names.  If it doesn't find the file it then looks in the alterations folder in the skeinforge_plugins folder.
119
120 ====Name of Support End File====
121 Default is support_end.gcode.
122
123 If support material is generated and if there is a file with the name of the "Name of Support End File" setting, it will be added to the end of the support gcode.
124
125 ====Name of Support Start File====
126 If support material is generated and if there is a file with the name of the "Name of Support Start File" setting, it will be added to the start of the support gcode.
127
128 ===Operating Nozzle Lift over Layer Thickness===
129 Default is 0.5.
130
131 Defines the amount the nozzle is above the center of the operating extrusion divided by the layer height.
132
133 ===Raft Size===
134 The raft fills a rectangle whose base size is the rectangle around the bottom layer of the object expanded on each side by the 'Raft Margin' plus the 'Raft Additional Margin over Length (%)' percentage times the length of the side.
135
136 ====Raft Additional Margin over Length====
137 Default is 1 percent.
138
139 ====Raft Margin====
140 Default is three millimeters.
141
142 ===Support===
143 Good articles on support material are at:
144 http://davedurant.wordpress.com/2010/07/31/skeinforge-support-part-1/
145 http://davedurant.wordpress.com/2010/07/31/skeinforge-support-part-2/
146
147 ====Support Cross Hatch====
148 Default is off.
149
150 When selected, the support material will cross hatched.  Cross hatching the support makes it stronger and harder to remove, which is why the default is off.
151
152 ====Support Flow Rate over Operating Flow Rate====
153 Default: 0.9.
154
155 Defines the ratio of the flow rate when the support is extruded over the operating flow rate.  With a number less than one, the support flow rate will be smaller so the support will be thinner and easier to remove.
156
157 ====Support Gap over Perimeter Extrusion Width====
158 Default: 0.5.
159
160 Defines the gap between the support material and the object over the edge extrusion width.
161
162 ====Support Material Choice====
163 Default is 'None' because the raft takes time to generate.
164
165 =====Empty Layers Only=====
166 When selected, support material will be only on the empty layers.  This is useful when making identical objects in a stack.
167
168 =====Everywhere=====
169 When selected, support material will be added wherever there are overhangs, even inside the object.  Because support material inside objects is hard or impossible to remove, this option should only be chosen if the object has a cavity that needs support and there is some way to extract the support material.
170
171 =====Exterior Only=====
172 When selected, support material will be added only the exterior of the object.  This is the best option for most objects which require support material.
173
174 =====None=====
175 When selected, raft will not add support material.
176
177 ====Support Minimum Angle====
178 Default is sixty degrees.
179
180 Defines the minimum angle that a surface overhangs before support material is added.  If angle is lower then this value the support will be generated.  This angle is defined from the vertical, so zero is a vertical wall, ten is a wall with a bit of overhang, thirty is the typical safe angle for filament extrusion, sixty is a really high angle for extrusion and ninety is an unsupported horizontal ceiling.
181
182 ==Examples==
183 The following examples raft the file Screw Holder Bottom.stl.  The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and raft.py.
184
185 > python raft.py
186 This brings up the raft dialog.
187
188 > python raft.py Screw Holder Bottom.stl
189 The raft tool is parsing the file:
190 Screw Holder Bottom.stl
191 ..
192 The raft tool has created the file:
193 Screw Holder Bottom_raft.gcode
194
195 """
196
197 from __future__ import absolute_import
198
199 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
200 from fabmetheus_utilities.geometry.solids import triangle_mesh
201 from fabmetheus_utilities.vector3 import Vector3
202 from fabmetheus_utilities import archive
203 from fabmetheus_utilities import euclidean
204 from fabmetheus_utilities import gcodec
205 from fabmetheus_utilities import intercircle
206 from fabmetheus_utilities import settings
207 from skeinforge_application.skeinforge_utilities import skeinforge_craft
208 from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
209 from skeinforge_application.skeinforge_utilities import skeinforge_profile
210 import math
211 import os
212 import sys
213
214
215 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
216 __date__ = '$Date: 2008/21/04 $'
217 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
218
219
220 #maybe later wide support
221 #raft outline temperature http://hydraraptor.blogspot.com/2008/09/screw-top-pot.html
222 def getCraftedText( fileName, text='', repository=None):
223         'Raft the file or text.'
224         return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository)
225
226 def getCraftedTextFromText(gcodeText, repository=None):
227         'Raft a gcode linear move text.'
228         if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'raft'):
229                 return gcodeText
230         if repository == None:
231                 repository = settings.getReadRepository( RaftRepository() )
232         if not repository.activateRaft.value:
233                 return gcodeText
234         return RaftSkein().getCraftedGcode(gcodeText, repository)
235
236 def getCrossHatchPointLine( crossHatchPointLineTable, y ):
237         'Get the cross hatch point line.'
238         if not crossHatchPointLineTable.has_key(y):
239                 crossHatchPointLineTable[ y ] = {}
240         return crossHatchPointLineTable[ y ]
241
242 def getEndpointsFromYIntersections( x, yIntersections ):
243         'Get endpoints from the y intersections.'
244         endpoints = []
245         for yIntersectionIndex in xrange( 0, len( yIntersections ), 2 ):
246                 firstY = yIntersections[ yIntersectionIndex ]
247                 secondY = yIntersections[ yIntersectionIndex + 1 ]
248                 if firstY != secondY:
249                         firstComplex = complex( x, firstY )
250                         secondComplex = complex( x, secondY )
251                         endpointFirst = euclidean.Endpoint()
252                         endpointSecond = euclidean.Endpoint().getFromOtherPoint( endpointFirst, secondComplex )
253                         endpointFirst.getFromOtherPoint( endpointSecond, firstComplex )
254                         endpoints.append( endpointFirst )
255                         endpoints.append( endpointSecond )
256         return endpoints
257
258 def getExtendedLineSegment(extensionDistance, lineSegment, loopXIntersections):
259         'Get extended line segment.'
260         pointBegin = lineSegment[0].point
261         pointEnd = lineSegment[1].point
262         segment = pointEnd - pointBegin
263         segmentLength = abs(segment)
264         if segmentLength <= 0.0:
265                 print('This should never happen in getExtendedLineSegment in raft, the segment should have a length greater than zero.')
266                 print(lineSegment)
267                 return None
268         segmentExtend = segment * extensionDistance / segmentLength
269         lineSegment[0].point -= segmentExtend
270         lineSegment[1].point += segmentExtend
271         for loopXIntersection in loopXIntersections:
272                 setExtendedPoint(lineSegment[0], pointBegin, loopXIntersection)
273                 setExtendedPoint(lineSegment[1], pointEnd, loopXIntersection)
274         return lineSegment
275
276 def getLoopsBySegmentsDictionary(segmentsDictionary, width):
277         'Get loops from a horizontal segments dictionary.'
278         points = []
279         for endpoint in getVerticalEndpoints(segmentsDictionary, width, 0.1 * width, width):
280                 points.append(endpoint.point)
281         for endpoint in euclidean.getEndpointsFromSegmentTable(segmentsDictionary):
282                 points.append(endpoint.point)
283         return triangle_mesh.getDescendingAreaOrientedLoops(points, points, width + width)
284
285 def getNewRepository():
286         'Get new repository.'
287         return RaftRepository()
288
289 def getVerticalEndpoints(horizontalSegmentsTable, horizontalStep, verticalOverhang, verticalStep):
290         'Get vertical endpoints.'
291         interfaceSegmentsTableKeys = horizontalSegmentsTable.keys()
292         interfaceSegmentsTableKeys.sort()
293         verticalTableTable = {}
294         for interfaceSegmentsTableKey in interfaceSegmentsTableKeys:
295                 interfaceSegments = horizontalSegmentsTable[interfaceSegmentsTableKey]
296                 for interfaceSegment in interfaceSegments:
297                         begin = int(round(interfaceSegment[0].point.real / verticalStep))
298                         end = int(round(interfaceSegment[1].point.real / verticalStep))
299                         for stepIndex in xrange(begin, end + 1):
300                                 if stepIndex not in verticalTableTable:
301                                         verticalTableTable[stepIndex] = {}
302                                 verticalTableTable[stepIndex][interfaceSegmentsTableKey] = None
303         verticalTableTableKeys = verticalTableTable.keys()
304         verticalTableTableKeys.sort()
305         verticalEndpoints = []
306         for verticalTableTableKey in verticalTableTableKeys:
307                 verticalTable = verticalTableTable[verticalTableTableKey]
308                 verticalTableKeys = verticalTable.keys()
309                 verticalTableKeys.sort()
310                 xIntersections = []
311                 for verticalTableKey in verticalTableKeys:
312                         y = verticalTableKey * horizontalStep
313                         if verticalTableKey - 1 not in verticalTableKeys:
314                                 xIntersections.append(y - verticalOverhang)
315                         if verticalTableKey + 1 not in verticalTableKeys:
316                                 xIntersections.append(y + verticalOverhang)
317                 for segment in euclidean.getSegmentsFromXIntersections(xIntersections, verticalTableTableKey * verticalStep):
318                         for endpoint in segment:
319                                 endpoint.point = complex(endpoint.point.imag, endpoint.point.real)
320                                 verticalEndpoints.append(endpoint)
321         return verticalEndpoints
322
323 def setExtendedPoint( lineSegmentEnd, pointOriginal, x ):
324         'Set the point in the extended line segment.'
325         if min( lineSegmentEnd.point.real, pointOriginal.real ) < x < max( lineSegmentEnd.point.real, pointOriginal.real ):
326                 lineSegmentEnd.point = complex( x, pointOriginal.imag )
327
328 def writeOutput(fileName, shouldAnalyze=True):
329         'Raft a gcode linear move file.'
330         skeinforge_craft.writeChainTextWithNounMessage(fileName, 'raft', shouldAnalyze)
331
332
333 class RaftRepository(object):
334         'A class to handle the raft settings.'
335         def __init__(self):
336                 'Set the default settings, execute title & settings fileName.'
337                 skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.raft.html', self)
338                 self.fileNameInput = settings.FileNameInput().getFromFileName(
339                         fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Raft', self, '')
340                 self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute(
341                         'http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Raft')
342                 self.activateRaft = settings.BooleanSetting().getFromValue('Activate Raft', self, True)
343                 self.addRaftElevateNozzleOrbitSetAltitude = settings.BooleanSetting().getFromValue(
344                         'Add Raft, Elevate Nozzle, Orbit:', self, True)
345                 settings.LabelSeparator().getFromRepository(self)
346                 settings.LabelDisplay().getFromName('- Base -', self)
347                 self.baseFeedRateMultiplier = settings.FloatSpin().getFromValue(0.7, 'Base Feed Rate Multiplier (ratio):', self, 1.1, 1.0)
348                 self.baseFlowRateMultiplier = settings.FloatSpin().getFromValue(0.7, 'Base Flow Rate Multiplier (ratio):', self, 1.1, 1.0)
349                 self.baseInfillDensity = settings.FloatSpin().getFromValue(0.3, 'Base Infill Density (ratio):', self, 0.9, 0.5)
350                 self.baseLayerThicknessOverLayerThickness = settings.FloatSpin().getFromValue(
351                         1.0, 'Base Layer Thickness over Layer Thickness:', self, 3.0, 2.0)
352                 self.baseLayers = settings.IntSpin().getFromValue(0, 'Base Layers (integer):', self, 3, 0)
353                 self.baseNozzleLiftOverBaseLayerThickness = settings.FloatSpin().getFromValue(
354                         0.2, 'Base Nozzle Lift over Base Layer Thickness (ratio):', self, 0.8, 0.4)
355                 settings.LabelSeparator().getFromRepository(self)
356                 self.initialCircling = settings.BooleanSetting().getFromValue('Initial Circling:', self, False)
357                 self.infillOverhangOverExtrusionWidth = settings.FloatSpin().getFromValue(
358                         0.0, 'Infill Overhang over Extrusion Width (ratio):', self, 0.5, 0.05)
359                 settings.LabelSeparator().getFromRepository(self)
360                 settings.LabelDisplay().getFromName('- Interface -', self)
361                 self.interfaceFeedRateMultiplier = settings.FloatSpin().getFromValue(
362                         0.7, 'Interface Feed Rate Multiplier (ratio):', self, 1.1, 1.0)
363                 self.interfaceFlowRateMultiplier = settings.FloatSpin().getFromValue(
364                         0.7, 'Interface Flow Rate Multiplier (ratio):', self, 1.1, 1.0)
365                 self.interfaceInfillDensity = settings.FloatSpin().getFromValue(
366                         0.3, 'Interface Infill Density (ratio):', self, 0.9, 0.5)
367                 self.interfaceLayerThicknessOverLayerThickness = settings.FloatSpin().getFromValue(
368                         1.0, 'Interface Layer Thickness over Layer Thickness:', self, 3.0, 1.0)
369                 self.interfaceLayers = settings.IntSpin().getFromValue(
370                         0, 'Interface Layers (integer):', self, 3, 0)
371                 self.interfaceNozzleLiftOverInterfaceLayerThickness = settings.FloatSpin().getFromValue(
372                         0.25, 'Interface Nozzle Lift over Interface Layer Thickness (ratio):', self, 0.85, 0.45)
373                 settings.LabelSeparator().getFromRepository(self)
374                 settings.LabelDisplay().getFromName('- Name of Alteration Files -', self)
375                 self.nameOfSupportEndFile = settings.StringSetting().getFromValue('Name of Support End File:', self, 'support_end.gcode')
376                 self.nameOfSupportStartFile = settings.StringSetting().getFromValue(
377                         'Name of Support Start File:', self, 'support_start.gcode')
378                 settings.LabelSeparator().getFromRepository(self)
379                 self.operatingNozzleLiftOverLayerThickness = settings.FloatSpin().getFromValue(
380                         0.3, 'Operating Nozzle Lift over Layer Thickness (ratio):', self, 0.7, 0.5)
381                 settings.LabelSeparator().getFromRepository(self)
382                 settings.LabelDisplay().getFromName('- Raft Size -', self)
383                 self.raftAdditionalMarginOverLengthPercent = settings.FloatSpin().getFromValue(
384                         0.5, 'Raft Additional Margin over Length (%):', self, 1.5, 1.0)
385                 self.raftMargin = settings.FloatSpin().getFromValue(
386                         1.0, 'Raft Margin (mm):', self, 5.0, 3.0)
387                 settings.LabelSeparator().getFromRepository(self)
388                 settings.LabelDisplay().getFromName('- Support -', self)
389                 self.supportCrossHatch = settings.BooleanSetting().getFromValue('Support Cross Hatch', self, False)
390                 self.supportFlowRateOverOperatingFlowRate = settings.FloatSpin().getFromValue(
391                         0.7, 'Support Flow Rate over Operating Flow Rate (ratio):', self, 1.1, 1.0)
392                 self.supportGapOverPerimeterExtrusionWidth = settings.FloatSpin().getFromValue(
393                         0.5, 'Support Gap over Perimeter Extrusion Width (ratio):', self, 1.5, 1.0)
394                 self.supportMaterialChoice = settings.MenuButtonDisplay().getFromName('Support Material Choice: ', self)
395                 self.supportChoiceNone = settings.MenuRadio().getFromMenuButtonDisplay(self.supportMaterialChoice, 'None', self, True)
396                 self.supportChoiceEmptyLayersOnly = settings.MenuRadio().getFromMenuButtonDisplay(self.supportMaterialChoice, 'Empty Layers Only', self, False)
397                 self.supportChoiceEverywhere = settings.MenuRadio().getFromMenuButtonDisplay(self.supportMaterialChoice, 'Everywhere', self, False)
398                 self.supportChoiceExteriorOnly = settings.MenuRadio().getFromMenuButtonDisplay(self.supportMaterialChoice, 'Exterior Only', self, False)
399                 self.supportMinimumAngle = settings.FloatSpin().getFromValue(40.0, 'Support Minimum Angle (degrees):', self, 80.0, 60.0)
400                 self.executeTitle = 'Raft'
401                 self.supportMargin = settings.FloatSpin().getFromValue(
402                         1.0, 'Support Margin (mm):', self, 5.0, 3.0)
403                 self.supportOffsetX = settings.FloatSpin().getFromValue(0.0, 'Support Offset X (mm):', self, 100.0, 0.0)
404                 self.supportOffsetY = settings.FloatSpin().getFromValue(0.0, 'Support Offset Y (mm):', self, 100.0, 0.0)
405
406         def execute(self):
407                 'Raft button has been clicked.'
408                 fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
409                 for fileName in fileNames:
410                         writeOutput(fileName)
411
412
413 class RaftSkein(object):
414         'A class to raft a skein of extrusions.'
415         def __init__(self):
416                 self.addLineLayerStart = True
417                 self.baseTemperature = None
418                 self.beginLoop = None
419                 self.boundaryLayers = []
420                 self.coolingRate = None
421                 self.distanceFeedRate = gcodec.DistanceFeedRate()
422                 self.edgeWidth = 0.6
423                 self.extrusionStart = True
424                 self.extrusionTop = 0.0
425                 self.feedRateMinute = 961.0
426                 self.heatingRate = None
427                 self.insetTable = {}
428                 self.interfaceTemperature = None
429                 self.isEdgePath = False
430                 self.isNestedRing = True
431                 self.isStartupEarly = False
432                 self.layerIndex = - 1
433                 self.layerStarted = False
434                 self.layerHeight = 0.4
435                 self.lineIndex = 0
436                 self.lines = None
437                 self.objectFirstLayerInfillTemperature = None
438                 self.objectFirstLayerPerimeterTemperature = None
439                 self.objectNextLayersTemperature = None
440                 self.oldFlowRate = None
441                 self.oldLocation = None
442                 self.oldTemperatureOutputString = None
443                 self.operatingFeedRateMinute = None
444                 self.operatingFlowRate = None
445                 self.operatingLayerEndLine = '(<operatingLayerEnd> </operatingLayerEnd>)'
446                 self.operatingJump = None
447                 self.orbitalFeedRatePerSecond = 2.01
448                 self.sharpestProduct = 0.94
449                 self.supportFlowRate = None
450                 self.supportLayers = []
451                 self.supportLayersTemperature = None
452                 self.supportedLayersTemperature = None
453                 self.travelFeedRateMinute = None
454
455         def addBaseLayer(self):
456                 'Add a base layer.'
457                 baseLayerThickness = self.layerHeight * self.baseLayerThicknessOverLayerThickness
458                 zCenter = self.extrusionTop + 0.5 * baseLayerThickness
459                 z = zCenter + baseLayerThickness * self.repository.baseNozzleLiftOverBaseLayerThickness.value
460                 if len(self.baseEndpoints) < 1:
461                         print('This should never happen, the base layer has a size of zero.')
462                         return
463                 self.addLayerFromEndpoints(
464                         self.baseEndpoints,
465                         self.repository.baseFeedRateMultiplier.value,
466                         self.repository.baseFlowRateMultiplier.value,
467                         baseLayerThickness,
468                         self.baseLayerThicknessOverLayerThickness,
469                         self.baseStep,
470                         z)
471
472         def addBaseSegments(self, baseExtrusionWidth):
473                 'Add the base segments.'
474                 baseOverhang = self.repository.infillOverhangOverExtrusionWidth.value * baseExtrusionWidth
475                 self.baseEndpoints = getVerticalEndpoints(self.interfaceSegmentsTable, self.interfaceStep, baseOverhang, self.baseStep)
476
477         def addEmptyLayerSupport( self, boundaryLayerIndex ):
478                 'Add support material to a layer if it is empty.'
479                 supportLayer = SupportLayer([])
480                 self.supportLayers.append(supportLayer)
481                 if len( self.boundaryLayers[ boundaryLayerIndex ].loops ) > 0:
482                         return
483                 aboveXIntersectionsTable = {}
484                 euclidean.addXIntersectionsFromLoopsForTable( self.getInsetLoopsAbove(boundaryLayerIndex), aboveXIntersectionsTable, self.interfaceStep )
485                 belowXIntersectionsTable = {}
486                 euclidean.addXIntersectionsFromLoopsForTable( self.getInsetLoopsBelow(boundaryLayerIndex), belowXIntersectionsTable, self.interfaceStep )
487                 supportLayer.xIntersectionsTable = euclidean.getIntersectionOfXIntersectionsTables( [ aboveXIntersectionsTable, belowXIntersectionsTable ] )
488
489         def addFlowRate(self, flowRate):
490                 'Add a flow rate value if different.'
491                 if flowRate != None:
492                         self.distanceFeedRate.addLine('M108 S' + euclidean.getFourSignificantFigures(flowRate))
493
494         def addInterfaceLayer(self):
495                 'Add an interface layer.'
496                 interfaceLayerThickness = self.layerHeight * self.interfaceLayerThicknessOverLayerThickness
497                 zCenter = self.extrusionTop + 0.5 * interfaceLayerThickness
498                 z = zCenter + interfaceLayerThickness * self.repository.interfaceNozzleLiftOverInterfaceLayerThickness.value
499                 if len(self.interfaceEndpoints) < 1:
500                         print('This should never happen, the interface layer has a size of zero.')
501                         return
502                 self.addLayerFromEndpoints(
503                         self.interfaceEndpoints,
504                         self.repository.interfaceFeedRateMultiplier.value,
505                         self.repository.interfaceFlowRateMultiplier.value,
506                         interfaceLayerThickness,
507                         self.interfaceLayerThicknessOverLayerThickness,
508                         self.interfaceStep,
509                         z)
510
511         def addInterfaceTables(self, interfaceExtrusionWidth):
512                 'Add interface tables.'
513                 overhang = self.repository.infillOverhangOverExtrusionWidth.value * interfaceExtrusionWidth
514                 self.interfaceEndpoints = []
515                 self.interfaceIntersectionsTableKeys = self.interfaceIntersectionsTable.keys()
516                 self.interfaceSegmentsTable = {}
517                 for yKey in self.interfaceIntersectionsTableKeys:
518                         self.interfaceIntersectionsTable[yKey].sort()
519                         y = yKey * self.interfaceStep
520                         lineSegments = euclidean.getSegmentsFromXIntersections(self.interfaceIntersectionsTable[yKey], y)
521                         xIntersectionIndexList = []
522                         for lineSegmentIndex in xrange(len(lineSegments)):
523                                 lineSegment = lineSegments[lineSegmentIndex]
524                                 endpointBegin = lineSegment[0]
525                                 endpointEnd = lineSegment[1]
526                                 endpointBegin.point = complex(self.baseStep * math.floor(endpointBegin.point.real / self.baseStep) - overhang, y)
527                                 endpointEnd.point = complex(self.baseStep * math.ceil(endpointEnd.point.real / self.baseStep) + overhang, y)
528                                 if endpointEnd.point.real > endpointBegin.point.real:
529                                         euclidean.addXIntersectionIndexesFromSegment(lineSegmentIndex, lineSegment, xIntersectionIndexList)
530                         xIntersections = euclidean.getJoinOfXIntersectionIndexes(xIntersectionIndexList)
531                         joinedSegments = euclidean.getSegmentsFromXIntersections(xIntersections, y)
532                         if len(joinedSegments) > 0:
533                                 self.interfaceSegmentsTable[yKey] = joinedSegments
534                         for joinedSegment in joinedSegments:
535                                 self.interfaceEndpoints += joinedSegment
536
537         def addLayerFromEndpoints(
538                 self,
539                 endpoints,
540                 feedRateMultiplier,
541                 flowRateMultiplier,
542                 layerLayerThickness,
543                 layerThicknessRatio,
544                 step,
545                 z):
546                 'Add a layer from endpoints and raise the extrusion top.'
547                 layerThicknessRatioSquared = layerThicknessRatio * layerThicknessRatio
548                 feedRateMinute = self.feedRateMinute * feedRateMultiplier / layerThicknessRatioSquared
549                 if len(endpoints) < 1:
550                         return
551                 aroundPixelTable = {}
552                 aroundWidth = 0.34321 * step
553                 paths = euclidean.getPathsFromEndpoints(endpoints, 1.5 * step, aroundPixelTable, self.sharpestProduct, aroundWidth)
554                 self.addLayerLine(z)
555                 if self.operatingFlowRate != None:
556                         self.addFlowRate(flowRateMultiplier * self.operatingFlowRate)
557                 for path in paths:
558                         simplifiedPath = euclidean.getSimplifiedPath(path, step)
559                         self.distanceFeedRate.addGcodeFromFeedRateThreadZ(feedRateMinute, simplifiedPath, self.travelFeedRateMinute, z)
560                 self.extrusionTop += layerLayerThickness
561                 self.addFlowRate(self.oldFlowRate)
562
563         def addLayerLine(self, z):
564                 'Add the layer gcode line and close the last layer gcode block.'
565                 if self.layerStarted:
566                         self.distanceFeedRate.addLine('(</layer>)')
567                 self.distanceFeedRate.addLine('(<layer> %s )' % self.distanceFeedRate.getRounded(z)) # Indicate that a new layer is starting.
568                 if self.beginLoop != None:
569                         zBegin = self.extrusionTop + self.layerHeight
570                         intercircle.addOrbitsIfLarge(self.distanceFeedRate, self.beginLoop, self.orbitalFeedRatePerSecond, self.temperatureChangeTimeBeforeRaft, zBegin)
571                         self.beginLoop = None
572                 self.layerStarted = True
573
574         def addOperatingOrbits(self, boundaryLoops, pointComplex, temperatureChangeTime, z):
575                 'Add the orbits before the operating layers.'
576                 if len(boundaryLoops) < 1:
577                         return
578                 insetBoundaryLoops = intercircle.getInsetLoopsFromLoops(boundaryLoops, self.edgeWidth)
579                 if len(insetBoundaryLoops) < 1:
580                         insetBoundaryLoops = boundaryLoops
581                 largestLoop = euclidean.getLargestLoop(insetBoundaryLoops)
582                 if pointComplex != None:
583                         largestLoop = euclidean.getLoopStartingClosest(self.edgeWidth, pointComplex, largestLoop)
584                 intercircle.addOrbitsIfLarge(self.distanceFeedRate, largestLoop, self.orbitalFeedRatePerSecond, temperatureChangeTime, z)
585
586         def addRaft(self):
587                 'Add the raft.'
588                 self.baseLayerThicknessOverLayerThickness = self.repository.baseLayerThicknessOverLayerThickness.value
589                 baseExtrusionWidth = self.edgeWidth * self.baseLayerThicknessOverLayerThickness
590                 self.baseStep = baseExtrusionWidth / self.repository.baseInfillDensity.value
591                 self.interfaceLayerThicknessOverLayerThickness = self.repository.interfaceLayerThicknessOverLayerThickness.value
592                 interfaceExtrusionWidth = self.edgeWidth * self.interfaceLayerThicknessOverLayerThickness
593                 self.interfaceStep = interfaceExtrusionWidth / self.repository.interfaceInfillDensity.value
594                 self.setCornersZ()
595                 self.cornerMinimumComplex = self.cornerMinimum.dropAxis()
596                 originalExtent = self.cornerMaximumComplex - self.cornerMinimumComplex
597                 self.raftOutsetRadius = self.repository.raftMargin.value + self.repository.raftAdditionalMarginOverLengthPercent.value * 0.01 * max(originalExtent.real, originalExtent.imag)
598                 self.supportOutsetRadius = self.repository.supportMargin.value
599                 self.setBoundaryLayers()
600                 if len(self.boundaryLayers) < 1:
601                         print('this should never happen, there are no boundary layers in addRaft')
602                         return
603                 outsetSeparateLoops = intercircle.getInsetSeparateLoopsFromLoops(self.boundaryLayers[0].loops, -self.raftOutsetRadius, 0.8)
604                 self.interfaceIntersectionsTable = {}
605                 euclidean.addXIntersectionsFromLoopsForTable(outsetSeparateLoops, self.interfaceIntersectionsTable, self.interfaceStep)
606                 if len(self.supportLayers) > 0:
607                         supportIntersectionsTable = self.supportLayers[0].xIntersectionsTable
608                         euclidean.joinXIntersectionsTables(supportIntersectionsTable, self.interfaceIntersectionsTable)
609                 self.addInterfaceTables(interfaceExtrusionWidth)
610                 self.addRaftPerimeters()
611                 self.baseIntersectionsTable = {}
612                 complexRadius = complex(self.raftOutsetRadius, self.raftOutsetRadius)
613                 self.complexHigh = complexRadius + self.cornerMaximumComplex
614                 self.complexLow = self.cornerMinimumComplex - complexRadius
615                 self.beginLoop = euclidean.getSquareLoopWiddershins(self.cornerMinimumComplex, self.cornerMaximumComplex)
616                 if not intercircle.orbitsAreLarge(self.beginLoop, self.temperatureChangeTimeBeforeRaft):
617                         self.beginLoop = None
618                 if self.repository.baseLayers.value > 0:
619                         self.addTemperatureLineIfDifferent(self.baseTemperature)
620                         self.addBaseSegments(baseExtrusionWidth)
621                 for baseLayerIndex in xrange(self.repository.baseLayers.value):
622                         self.addBaseLayer()
623                 if self.repository.interfaceLayers.value > 0:
624                         self.addTemperatureLineIfDifferent(self.interfaceTemperature)
625                 self.interfaceIntersectionsTableKeys.sort()
626                 for interfaceLayerIndex in xrange(self.repository.interfaceLayers.value):
627                         self.addInterfaceLayer()
628                 self.operatingJump = self.extrusionTop + self.layerHeight * self.repository.operatingNozzleLiftOverLayerThickness.value
629                 for boundaryLayer in self.boundaryLayers:
630                         if self.operatingJump != None:
631                                 boundaryLayer.z += self.operatingJump
632                 if self.repository.baseLayers.value > 0 or self.repository.interfaceLayers.value > 0:
633                         boundaryZ = self.boundaryLayers[0].z
634                         if self.layerStarted:
635                                 self.distanceFeedRate.addLine('(</layer>)')
636                                 self.layerStarted = False
637                         self.distanceFeedRate.addLine('(<raftLayerEnd> </raftLayerEnd>)')
638                         self.addLayerLine(boundaryZ)
639                         temperatureChangeTimeBeforeFirstLayer = self.getTemperatureChangeTime(self.objectFirstLayerPerimeterTemperature)
640                         self.addTemperatureLineIfDifferent(self.objectFirstLayerPerimeterTemperature)
641                         largestOutsetLoop = intercircle.getLargestInsetLoopFromLoop(euclidean.getLargestLoop(outsetSeparateLoops), -self.raftOutsetRadius)
642                         intercircle.addOrbitsIfLarge(self.distanceFeedRate, largestOutsetLoop, self.orbitalFeedRatePerSecond, temperatureChangeTimeBeforeFirstLayer, boundaryZ)
643                         self.addLineLayerStart = False
644
645         def addRaftedLine( self, splitLine ):
646                 'Add elevated gcode line with operating feed rate.'
647                 self.oldLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
648                 self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine)
649                 z = self.oldLocation.z
650                 if self.operatingJump != None:
651                         z += self.operatingJump
652                 temperature = self.objectNextLayersTemperature
653                 if self.layerIndex == 0:
654                         if self.isEdgePath:
655                                 temperature = self.objectFirstLayerPerimeterTemperature
656                         else:
657                                 temperature = self.objectFirstLayerInfillTemperature
658                 self.addTemperatureLineIfDifferent(temperature)
659                 self.distanceFeedRate.addGcodeMovementZWithFeedRate(self.feedRateMinute, self.oldLocation.dropAxis(), z)
660
661         def addRaftPerimeters(self):
662                 'Add raft edges if there is a raft.'
663                 interfaceOutset = self.halfEdgeWidth * self.interfaceLayerThicknessOverLayerThickness
664                 for supportLayer in self.supportLayers:
665                         supportSegmentTable = supportLayer.supportSegmentTable
666                         if len(supportSegmentTable) > 0:
667                                 outset = interfaceOutset
668                                 self.addRaftPerimetersByLoops(getLoopsBySegmentsDictionary(supportSegmentTable, self.interfaceStep), outset)
669                 if self.repository.baseLayers.value < 1 and self.repository.interfaceLayers.value < 1:
670                         return
671                 overhangMultiplier = 1.0 + self.repository.infillOverhangOverExtrusionWidth.value + self.repository.infillOverhangOverExtrusionWidth.value
672                 outset = self.halfEdgeWidth
673                 if self.repository.interfaceLayers.value > 0:
674                         outset = max(interfaceOutset * overhangMultiplier, outset)
675                 if self.repository.baseLayers.value > 0:
676                         outset = max(self.halfEdgeWidth * self.baseLayerThicknessOverLayerThickness * overhangMultiplier, outset)
677                 self.addRaftPerimetersByLoops(getLoopsBySegmentsDictionary(self.interfaceSegmentsTable, self.interfaceStep), outset)
678
679         def addRaftPerimetersByLoops(self, loops, outset):
680                 'Add raft edges to the gcode for loops.'
681                 loops = intercircle.getInsetSeparateLoopsFromLoops(loops, -outset)
682                 for loop in loops:
683                         self.distanceFeedRate.addLine('(<raftPerimeter>)')
684                         for point in loop:
685                                 roundedX = self.distanceFeedRate.getRounded(point.real)
686                                 roundedY = self.distanceFeedRate.getRounded(point.imag)
687                                 self.distanceFeedRate.addTagBracketedLine('raftPoint', 'X%s Y%s' % (roundedX, roundedY))
688                         self.distanceFeedRate.addLine('(</raftPerimeter>)')
689
690         def addSegmentTablesToSupportLayers(self):
691                 'Add segment tables to the support layers.'
692                 for supportLayer in self.supportLayers:
693                         supportLayer.supportSegmentTable = {}
694                         xIntersectionsTable = supportLayer.xIntersectionsTable
695                         for xIntersectionsTableKey in xIntersectionsTable:
696                                 y = xIntersectionsTableKey * self.interfaceStep
697                                 supportLayer.supportSegmentTable[ xIntersectionsTableKey ] = euclidean.getSegmentsFromXIntersections( xIntersectionsTable[ xIntersectionsTableKey ], y )
698
699         def addSupportLayerTemperature(self, endpoints, z):
700                 'Add support layer and temperature before the object layer.'
701                 self.distanceFeedRate.addLinesSetAbsoluteDistanceMode(self.supportStartLines)
702                 self.distanceFeedRate.addLine('(<supportLayer>)')
703                 self.addTemperatureOrbits(endpoints, self.supportedLayersTemperature, z)
704                 aroundPixelTable = {}
705                 aroundWidth = 0.34321 * self.interfaceStep
706                 boundaryLoops = self.boundaryLayers[self.layerIndex].loops
707                 halfSupportOutset = 0.5 * self.supportOutset
708                 aroundBoundaryLoops = intercircle.getAroundsFromLoops(boundaryLoops, halfSupportOutset)
709                 for aroundBoundaryLoop in aroundBoundaryLoops:
710                         euclidean.addLoopToPixelTable(aroundBoundaryLoop, aroundPixelTable, aroundWidth)
711                 paths = euclidean.getPathsFromEndpoints(endpoints, 1.5 * self.interfaceStep, aroundPixelTable, self.sharpestProduct, aroundWidth)
712                 feedRateMinuteMultiplied = self.operatingFeedRateMinute
713                 supportFlowRateMultiplied = self.supportFlowRate
714                 if self.layerIndex == 0:
715                         feedRateMinuteMultiplied *= self.objectFirstLayerFeedRateInfillMultiplier
716                         if supportFlowRateMultiplied != None:
717                                 supportFlowRateMultiplied = self.operatingFlowRate * self.objectFirstLayerFlowRateInfillMultiplier
718                 self.addFlowRate(supportFlowRateMultiplied)
719                 for path in paths:
720                         path = map(lambda p: p + complex(self.supportOffsetX, self.supportOffsetY), path)
721                         self.distanceFeedRate.addGcodeFromFeedRateThreadZ(feedRateMinuteMultiplied, path, self.travelFeedRateMinute, z)
722                 self.addFlowRate(self.oldFlowRate)
723                 self.addTemperatureOrbits(endpoints, self.supportLayersTemperature, z)
724                 self.distanceFeedRate.addLinesSetAbsoluteDistanceMode(self.supportEndLines)
725                 self.distanceFeedRate.addLine('(</supportLayer>)')
726
727         def addSupportSegmentTable( self, layerIndex ):
728                 'Add support segments from the boundary layers.'
729                 aboveLayer = self.boundaryLayers[ layerIndex + 1 ]
730                 aboveLoops = aboveLayer.loops
731                 supportLayer = self.supportLayers[layerIndex]
732                 if len( aboveLoops ) < 1:
733                         return
734                 boundaryLayer = self.boundaryLayers[layerIndex]
735                 rise = aboveLayer.z - boundaryLayer.z
736                 outsetSupportLoops = intercircle.getInsetSeparateLoopsFromLoops(boundaryLayer.loops, -self.minimumSupportRatio * rise)
737                 numberOfSubSteps = 4
738                 subStepSize = self.interfaceStep / float( numberOfSubSteps )
739                 aboveIntersectionsTable = {}
740                 euclidean.addXIntersectionsFromLoopsForTable( aboveLoops, aboveIntersectionsTable, subStepSize )
741                 outsetIntersectionsTable = {}
742                 euclidean.addXIntersectionsFromLoopsForTable( outsetSupportLoops, outsetIntersectionsTable, subStepSize )
743                 euclidean.subtractXIntersectionsTable( aboveIntersectionsTable, outsetIntersectionsTable )
744                 for aboveIntersectionsTableKey in aboveIntersectionsTable.keys():
745                         supportIntersectionsTableKey = int( round( float( aboveIntersectionsTableKey ) / numberOfSubSteps ) )
746                         xIntersectionIndexList = []
747                         if supportIntersectionsTableKey in supportLayer.xIntersectionsTable:
748                                 euclidean.addXIntersectionIndexesFromXIntersections( 0, xIntersectionIndexList, supportLayer.xIntersectionsTable[ supportIntersectionsTableKey ] )
749                         euclidean.addXIntersectionIndexesFromXIntersections( 1, xIntersectionIndexList, aboveIntersectionsTable[ aboveIntersectionsTableKey ] )
750                         supportLayer.xIntersectionsTable[ supportIntersectionsTableKey ] = euclidean.getJoinOfXIntersectionIndexes( xIntersectionIndexList )
751
752         def addTemperatureLineIfDifferent(self, temperature):
753                 'Add a line of temperature if different.'
754                 if temperature == None:
755                         return
756                 temperatureOutputString = euclidean.getRoundedToThreePlaces(temperature)
757                 if temperatureOutputString == self.oldTemperatureOutputString:
758                         return
759                 if temperatureOutputString != None:
760                         self.distanceFeedRate.addLine('M104 S' + temperatureOutputString) # Set temperature.
761                 self.oldTemperatureOutputString = temperatureOutputString
762
763         def addTemperatureOrbits( self, endpoints, temperature, z ):
764                 'Add the temperature and orbits around the support layer.'
765                 if self.layerIndex < 0:
766                         return
767                 boundaryLoops = self.boundaryLayers[self.layerIndex].loops
768                 temperatureTimeChange = self.getTemperatureChangeTime( temperature )
769                 self.addTemperatureLineIfDifferent( temperature )
770                 if len( boundaryLoops ) < 1:
771                         layerCornerHigh = complex(-987654321.0, -987654321.0)
772                         layerCornerLow = complex(987654321.0, 987654321.0)
773                         for endpoint in endpoints:
774                                 layerCornerHigh = euclidean.getMaximum( layerCornerHigh, endpoint.point )
775                                 layerCornerLow = euclidean.getMinimum( layerCornerLow, endpoint.point )
776                         squareLoop = euclidean.getSquareLoopWiddershins( layerCornerLow, layerCornerHigh )
777                         intercircle.addOrbitsIfLarge( self.distanceFeedRate, squareLoop, self.orbitalFeedRatePerSecond, temperatureTimeChange, z )
778                         return
779                 edgeInset = 0.4 * self.edgeWidth
780                 insetBoundaryLoops = intercircle.getInsetLoopsFromLoops(boundaryLoops, edgeInset)
781                 if len( insetBoundaryLoops ) < 1:
782                         insetBoundaryLoops = boundaryLoops
783                 largestLoop = euclidean.getLargestLoop( insetBoundaryLoops )
784                 intercircle.addOrbitsIfLarge( self.distanceFeedRate, largestLoop, self.orbitalFeedRatePerSecond, temperatureTimeChange, z )
785
786         def addToFillXIntersectionIndexTables( self, supportLayer ):
787                 'Add fill segments from the boundary layers.'
788                 supportLoops = supportLayer.supportLoops
789                 supportLayer.fillXIntersectionsTable = {}
790                 if len(supportLoops) < 1:
791                         return
792                 euclidean.addXIntersectionsFromLoopsForTable( supportLoops, supportLayer.fillXIntersectionsTable, self.interfaceStep )
793
794         def extendXIntersections( self, loops, radius, xIntersectionsTable ):
795                 'Extend the support segments.'
796                 xIntersectionsTableKeys = xIntersectionsTable.keys()
797                 for xIntersectionsTableKey in xIntersectionsTableKeys:
798                         lineSegments = euclidean.getSegmentsFromXIntersections( xIntersectionsTable[ xIntersectionsTableKey ], xIntersectionsTableKey )
799                         xIntersectionIndexList = []
800                         loopXIntersections = []
801                         euclidean.addXIntersectionsFromLoops( loops, loopXIntersections, xIntersectionsTableKey )
802                         for lineSegmentIndex in xrange( len( lineSegments ) ):
803                                 lineSegment = lineSegments[ lineSegmentIndex ]
804                                 extendedLineSegment = getExtendedLineSegment( radius, lineSegment, loopXIntersections )
805                                 if extendedLineSegment != None:
806                                         euclidean.addXIntersectionIndexesFromSegment( lineSegmentIndex, extendedLineSegment, xIntersectionIndexList )
807                         xIntersections = euclidean.getJoinOfXIntersectionIndexes( xIntersectionIndexList )
808                         if len( xIntersections ) > 0:
809                                 xIntersectionsTable[ xIntersectionsTableKey ] = xIntersections
810                         else:
811                                 del xIntersectionsTable[ xIntersectionsTableKey ]
812
813         def getCraftedGcode(self, gcodeText, repository):
814                 'Parse gcode text and store the raft gcode.'
815                 self.repository = repository
816                 self.minimumSupportRatio = math.tan( math.radians( repository.supportMinimumAngle.value ) )
817                 self.supportEndLines = settings.getAlterationFileLines(repository.nameOfSupportEndFile.value)
818                 self.supportStartLines = settings.getAlterationFileLines(repository.nameOfSupportStartFile.value)
819                 self.supportOffsetX = repository.supportOffsetX.value
820                 self.supportOffsetY = repository.supportOffsetY.value
821                 self.lines = archive.getTextLines(gcodeText)
822                 self.parseInitialization()
823                 self.temperatureChangeTimeBeforeRaft = 0.0
824                 if self.repository.initialCircling.value:
825                         maxBaseInterfaceTemperature = max(self.baseTemperature, self.interfaceTemperature)
826                         firstMaxTemperature = max(maxBaseInterfaceTemperature, self.objectFirstLayerPerimeterTemperature)
827                         self.temperatureChangeTimeBeforeRaft = self.getTemperatureChangeTime(firstMaxTemperature)
828                 if repository.addRaftElevateNozzleOrbitSetAltitude.value:
829                         self.addRaft()
830                 self.addTemperatureLineIfDifferent( self.objectFirstLayerPerimeterTemperature )
831                 for line in self.lines[self.lineIndex :]:
832                         self.parseLine(line)
833                 return gcodec.getGcodeWithoutDuplication('M108', self.distanceFeedRate.output.getvalue())
834
835         def getElevatedBoundaryLine( self, splitLine ):
836                 'Get elevated boundary gcode line.'
837                 location = gcodec.getLocationFromSplitLine(None, splitLine)
838                 if self.operatingJump != None:
839                         location.z += self.operatingJump
840                 return self.distanceFeedRate.getBoundaryLine( location )
841
842         def getInsetLoops( self, boundaryLayerIndex ):
843                 'Inset the support loops if they are not already inset.'
844                 if boundaryLayerIndex not in self.insetTable:
845                         self.insetTable[ boundaryLayerIndex ] = intercircle.getInsetSeparateLoopsFromLoops(self.boundaryLayers[ boundaryLayerIndex ].loops, self.quarterEdgeWidth)
846                 return self.insetTable[ boundaryLayerIndex ]
847
848         def getInsetLoopsAbove( self, boundaryLayerIndex ):
849                 'Get the inset loops above the boundary layer index.'
850                 for aboveLayerIndex in xrange( boundaryLayerIndex + 1, len(self.boundaryLayers) ):
851                         if len( self.boundaryLayers[ aboveLayerIndex ].loops ) > 0:
852                                 return self.getInsetLoops( aboveLayerIndex )
853                 return []
854
855         def getInsetLoopsBelow( self, boundaryLayerIndex ):
856                 'Get the inset loops below the boundary layer index.'
857                 for belowLayerIndex in xrange( boundaryLayerIndex - 1, - 1, - 1 ):
858                         if len( self.boundaryLayers[ belowLayerIndex ].loops ) > 0:
859                                 return self.getInsetLoops( belowLayerIndex )
860                 return []
861
862         def getStepsUntilEnd( self, begin, end, stepSize ):
863                 'Get steps from the beginning until the end.'
864                 step = begin
865                 steps = []
866                 while step < end:
867                         steps.append( step )
868                         step += stepSize
869                 return steps
870
871         def getSupportEndpoints(self):
872                 'Get the support layer segments.'
873                 if len(self.supportLayers) <= self.layerIndex:
874                         return []
875                 supportSegmentTable = self.supportLayers[self.layerIndex].supportSegmentTable
876                 if self.layerIndex % 2 == 1 and self.repository.supportCrossHatch.value:
877                         return getVerticalEndpoints(supportSegmentTable, self.interfaceStep, 0.1 * self.edgeWidth, self.interfaceStep)
878                 return euclidean.getEndpointsFromSegmentTable(supportSegmentTable)
879
880         def getTemperatureChangeTime( self, temperature ):
881                 'Get the temperature change time.'
882                 if temperature == None:
883                         return 0.0
884                 oldTemperature = 25.0 # typical chamber temperature
885                 if self.oldTemperatureOutputString != None:
886                         oldTemperature = float( self.oldTemperatureOutputString )
887                 if temperature == oldTemperature:
888                         return 0.0
889                 if temperature > oldTemperature:
890                         return ( temperature - oldTemperature ) / self.heatingRate
891                 return ( oldTemperature - temperature ) / abs( self.coolingRate )
892
893         def parseInitialization(self):
894                 'Parse gcode initialization and store the parameters.'
895                 for self.lineIndex in xrange(len(self.lines)):
896                         line = self.lines[self.lineIndex]
897                         splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
898                         firstWord = gcodec.getFirstWord(splitLine)
899                         self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
900                         if firstWord == '(<baseTemperature>':
901                                 self.baseTemperature = float(splitLine[1])
902                         elif firstWord == '(<coolingRate>':
903                                 self.coolingRate = float(splitLine[1])
904                         elif firstWord == '(<edgeWidth>':
905                                 self.edgeWidth = float(splitLine[1])
906                                 self.halfEdgeWidth = 0.5 * self.edgeWidth
907                                 self.quarterEdgeWidth = 0.25 * self.edgeWidth
908                                 self.supportOutset = self.edgeWidth + self.edgeWidth * self.repository.supportGapOverPerimeterExtrusionWidth.value
909                         elif firstWord == '(</extruderInitialization>)':
910                                 self.distanceFeedRate.addTagBracketedProcedure('raft')
911                         elif firstWord == '(<heatingRate>':
912                                 self.heatingRate = float(splitLine[1])
913                         elif firstWord == '(<interfaceTemperature>':
914                                 self.interfaceTemperature = float(splitLine[1])
915                         elif firstWord == '(<layer>':
916                                 return
917                         elif firstWord == '(<layerHeight>':
918                                 self.layerHeight = float(splitLine[1])
919                         elif firstWord == 'M108':
920                                 self.oldFlowRate = float(splitLine[1][1 :])
921                         elif firstWord == '(<objectFirstLayerFeedRateInfillMultiplier>':
922                                 self.objectFirstLayerFeedRateInfillMultiplier = float(splitLine[1])
923                         elif firstWord == '(<objectFirstLayerFlowRateInfillMultiplier>':
924                                 self.objectFirstLayerFlowRateInfillMultiplier = float(splitLine[1])
925                         elif firstWord == '(<objectFirstLayerInfillTemperature>':
926                                 self.objectFirstLayerInfillTemperature = float(splitLine[1])
927                         elif firstWord == '(<objectFirstLayerPerimeterTemperature>':
928                                 self.objectFirstLayerPerimeterTemperature = float(splitLine[1])
929                         elif firstWord == '(<objectNextLayersTemperature>':
930                                 self.objectNextLayersTemperature = float(splitLine[1])
931                         elif firstWord == '(<orbitalFeedRatePerSecond>':
932                                 self.orbitalFeedRatePerSecond = float(splitLine[1])
933                         elif firstWord == '(<operatingFeedRatePerSecond>':
934                                 self.operatingFeedRateMinute = 60.0 * float(splitLine[1])
935                                 self.feedRateMinute = self.operatingFeedRateMinute
936                         elif firstWord == '(<operatingFlowRate>':
937                                 self.operatingFlowRate = float(splitLine[1])
938                                 self.oldFlowRate = self.operatingFlowRate
939                                 self.supportFlowRate = self.operatingFlowRate * self.repository.supportFlowRateOverOperatingFlowRate.value
940                         elif firstWord == '(<sharpestProduct>':
941                                 self.sharpestProduct = float(splitLine[1])
942                         elif firstWord == '(<supportLayersTemperature>':
943                                 self.supportLayersTemperature = float(splitLine[1])
944                         elif firstWord == '(<supportedLayersTemperature>':
945                                 self.supportedLayersTemperature = float(splitLine[1])
946                         elif firstWord == '(<travelFeedRatePerSecond>':
947                                 self.travelFeedRateMinute = 60.0 * float(splitLine[1])
948                         self.distanceFeedRate.addLine(line)
949
950         def parseLine(self, line):
951                 'Parse a gcode line and add it to the raft skein.'
952                 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
953                 if len(splitLine) < 1:
954                         return
955                 firstWord = splitLine[0]
956                 if firstWord == 'G1':
957                         if self.extrusionStart:
958                                 self.addRaftedLine(splitLine)
959                                 return
960                 elif firstWord == 'M101':
961                         if self.isStartupEarly:
962                                 self.isStartupEarly = False
963                                 return
964                 elif firstWord == 'M108':
965                         self.oldFlowRate = float(splitLine[1][1 :])
966                 elif firstWord == '(<boundaryPoint>':
967                         line = self.getElevatedBoundaryLine(splitLine)
968                 elif firstWord == '(</crafting>)':
969                         self.extrusionStart = False
970                         self.distanceFeedRate.addLine( self.operatingLayerEndLine )
971                 elif firstWord == '(<layer>':
972                         self.layerIndex += 1
973                         settings.printProgress(self.layerIndex, 'raft')
974                         boundaryLayer = None
975                         layerZ = self.extrusionTop + float(splitLine[1])
976                         if len(self.boundaryLayers) > 0:
977                                 boundaryLayer = self.boundaryLayers[self.layerIndex]
978                                 layerZ = boundaryLayer.z
979                         if self.operatingJump != None:
980                                 line = '(<layer> %s )' % self.distanceFeedRate.getRounded( layerZ )
981                         if self.layerStarted and self.addLineLayerStart:
982                                 self.distanceFeedRate.addLine('(</layer>)')
983                         self.layerStarted = False
984                         if self.layerIndex > len(self.supportLayers) + 1:
985                                 self.distanceFeedRate.addLine( self.operatingLayerEndLine )
986                                 self.operatingLayerEndLine = ''
987                         if self.addLineLayerStart:
988                                 self.distanceFeedRate.addLine(line)
989                         self.addLineLayerStart = True
990                         line = ''
991                         endpoints = self.getSupportEndpoints()
992                         if self.layerIndex == 1:
993                                 if len(endpoints) < 1:
994                                         temperatureChangeTimeBeforeNextLayers = self.getTemperatureChangeTime( self.objectNextLayersTemperature )
995                                         self.addTemperatureLineIfDifferent( self.objectNextLayersTemperature )
996                                         if self.repository.addRaftElevateNozzleOrbitSetAltitude.value and boundaryLayer != None and len( boundaryLayer.loops ) > 0:
997                                                 self.addOperatingOrbits( boundaryLayer.loops, euclidean.getXYComplexFromVector3( self.oldLocation ), temperatureChangeTimeBeforeNextLayers, layerZ )
998                         if len(endpoints) > 0:
999                                 self.addSupportLayerTemperature( endpoints, layerZ )
1000                 elif firstWord == '(<edge>' or firstWord == '(<edgePath>)':
1001                         self.isEdgePath = True
1002                 elif firstWord == '(</edge>)' or firstWord == '(</edgePath>)':
1003                         self.isEdgePath = False
1004                 self.distanceFeedRate.addLine(line)
1005
1006         def setBoundaryLayers(self):
1007                 'Set the boundary layers.'
1008                 if self.repository.supportChoiceNone.value:
1009                         return
1010                 if len(self.boundaryLayers) < 2:
1011                         return
1012                 if self.repository.supportChoiceEmptyLayersOnly.value:
1013                         supportLayer = SupportLayer([])
1014                         self.supportLayers.append(supportLayer)
1015                         for boundaryLayerIndex in xrange(1, len(self.boundaryLayers) -1):
1016                                 self.addEmptyLayerSupport(boundaryLayerIndex)
1017                         self.truncateSupportSegmentTables()
1018                         self.addSegmentTablesToSupportLayers()
1019                         return
1020                 for boundaryLayer in self.boundaryLayers:
1021                         # thresholdRadius of 0.8 is needed to avoid the ripple inset bug http://hydraraptor.blogspot.com/2010/12/crackers.html
1022                         supportLoops = intercircle.getInsetSeparateLoopsFromLoops(boundaryLayer.loops, -self.supportOutset, 0.8)
1023                         supportLayer = SupportLayer(supportLoops)
1024                         self.supportLayers.append(supportLayer)
1025                 for supportLayerIndex in xrange(len(self.supportLayers) - 1):
1026                         self.addSupportSegmentTable(supportLayerIndex)
1027                 self.truncateSupportSegmentTables()
1028                 for supportLayerIndex in xrange(len(self.supportLayers) - 1):
1029                         boundaryLoops = self.boundaryLayers[supportLayerIndex].loops
1030                         self.extendXIntersections( boundaryLoops, self.supportOutset, self.supportLayers[supportLayerIndex].xIntersectionsTable)
1031                 for supportLayer in self.supportLayers:
1032                         self.addToFillXIntersectionIndexTables(supportLayer)
1033                 if self.repository.supportChoiceExteriorOnly.value:
1034                         for supportLayerIndex in xrange(1, len(self.supportLayers)):
1035                                 self.subtractJoinedFill(supportLayerIndex)
1036                 for supportLayer in self.supportLayers:
1037                         euclidean.subtractXIntersectionsTable(supportLayer.xIntersectionsTable, supportLayer.fillXIntersectionsTable)
1038                 for supportLayerIndex in xrange(len(self.supportLayers) - 2, -1, -1):
1039                         xIntersectionsTable = self.supportLayers[supportLayerIndex].xIntersectionsTable
1040                         aboveXIntersectionsTable = self.supportLayers[supportLayerIndex + 1].xIntersectionsTable
1041                         euclidean.joinXIntersectionsTables(aboveXIntersectionsTable, xIntersectionsTable)
1042                 for supportLayerIndex in xrange(len(self.supportLayers)):
1043                         supportLayer = self.supportLayers[supportLayerIndex]
1044                         self.extendXIntersections(supportLayer.supportLoops, self.supportOutsetRadius, supportLayer.xIntersectionsTable)
1045                 for supportLayer in self.supportLayers:
1046                         euclidean.subtractXIntersectionsTable(supportLayer.xIntersectionsTable, supportLayer.fillXIntersectionsTable)
1047                 self.addSegmentTablesToSupportLayers()
1048
1049         def setCornersZ(self):
1050                 'Set maximum and minimum corners and z.'
1051                 boundaryLoop = None
1052                 boundaryLayer = None
1053                 layerIndex = - 1
1054                 self.cornerMaximumComplex = complex(-912345678.0, -912345678.0)
1055                 self.cornerMinimum = Vector3(912345678.0, 912345678.0, 912345678.0)
1056                 self.firstLayerLoops = []
1057                 for line in self.lines[self.lineIndex :]:
1058                         splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
1059                         firstWord = gcodec.getFirstWord(splitLine)
1060                         if firstWord == '(</boundaryPerimeter>)':
1061                                 boundaryLoop = None
1062                         elif firstWord == '(<boundaryPoint>':
1063                                 location = gcodec.getLocationFromSplitLine(None, splitLine)
1064                                 if boundaryLoop == None:
1065                                         boundaryLoop = []
1066                                         boundaryLayer.loops.append(boundaryLoop)
1067                                 boundaryLoop.append(location.dropAxis())
1068                                 self.cornerMaximumComplex = euclidean.getMaximum(self.cornerMaximumComplex, location.dropAxis())
1069                                 self.cornerMinimum.minimize(location)
1070                         elif firstWord == '(<layer>':
1071                                 z = float(splitLine[1])
1072                                 boundaryLayer = euclidean.LoopLayer(z)
1073                                 self.boundaryLayers.append(boundaryLayer)
1074                         elif firstWord == '(<layer>':
1075                                 layerIndex += 1
1076                                 if self.repository.supportChoiceNone.value:
1077                                         if layerIndex > 1:
1078                                                 return
1079
1080         def subtractJoinedFill( self, supportLayerIndex ):
1081                 'Join the fill then subtract it from the support layer table.'
1082                 supportLayer = self.supportLayers[supportLayerIndex]
1083                 fillXIntersectionsTable = supportLayer.fillXIntersectionsTable
1084                 belowFillXIntersectionsTable = self.supportLayers[ supportLayerIndex - 1 ].fillXIntersectionsTable
1085                 euclidean.joinXIntersectionsTables( belowFillXIntersectionsTable, supportLayer.fillXIntersectionsTable )
1086                 euclidean.subtractXIntersectionsTable( supportLayer.xIntersectionsTable, supportLayer.fillXIntersectionsTable )
1087
1088         def truncateSupportSegmentTables(self):
1089                 'Truncate the support segments after the last support segment which contains elements.'
1090                 for supportLayerIndex in xrange( len(self.supportLayers) - 1, - 1, - 1 ):
1091                         if len( self.supportLayers[supportLayerIndex].xIntersectionsTable ) > 0:
1092                                 self.supportLayers = self.supportLayers[ : supportLayerIndex + 1 ]
1093                                 return
1094                 self.supportLayers = []
1095
1096
1097 class SupportLayer(object):
1098         'Support loops with segment tables.'
1099         def __init__( self, supportLoops ):
1100                 self.supportLoops = supportLoops
1101                 self.supportSegmentTable = {}
1102                 self.xIntersectionsTable = {}
1103
1104         def __repr__(self):
1105                 'Get the string representation of this loop layer.'
1106                 return '%s' % ( self.supportLoops )
1107
1108
1109 def main():
1110         'Display the raft dialog.'
1111         if len(sys.argv) > 1:
1112                 writeOutput(' '.join(sys.argv[1 :]))
1113         else:
1114                 settings.startMainLoopFromConstructor(getNewRepository())
1115
1116 if __name__ == '__main__':
1117         main()