2 This page is in the table of contents.
3 Cool is a craft tool to cool the shape.
5 Cool works well with a stepper extruder, it does not work well with a DC motor extruder.
7 If enabled, before each layer that takes less then "Minimum Layer Time" to print the tool head will orbit around the printed area for 'Minimum Layer Time' minus 'the time it takes to print the layer' before it starts printing the layer. This is great way to let layers with smaller area cool before you start printing on top of them (so you do not overheat the area).
9 The cool manual page is at:
10 http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Cool
12 Allan Ecker aka The Masked Retriever's has written the "Skeinforge Quicktip: Cool" at:
13 http://blog.thingiverse.com/2009/07/28/skeinforge-quicktip-cool/
16 The default 'Activate Cool' checkbox is on. When it is on, the functions described below will work, when it is off, the functions will not be called.
20 Default is one degree Celcius.
22 If the layer is a bridge layer, then cool will lower the temperature by 'Bridge Cool' degrees Celcius.
25 Default is 'Slow Down'.
28 When selected, cool will add orbits with the extruder off to give the layer time to cool, so that the next layer is not extruded on a molten base. The orbits will be around the largest island on that layer. Orbit should only be chosen if you can not upgrade to a stepper extruder.
31 When selected, cool will slow down the extruder so that it will take the minimum layer time to extrude the layer. DC motors do not operate properly at slow flow rates, so if you have a DC motor extruder, you should upgrade to a stepper extruder, but if you can't do that, you can try using the 'Orbit' option.
34 Default is 2 degrees Celcius.
36 If it takes less time to extrude the layer than the minimum layer time, then cool will lower the temperature by the 'Maximum Cool' setting times the layer time over the minimum layer time.
38 ===Minimum Layer Time===
39 Default is 60 seconds.
41 Defines the minimum amount of time the extruder will spend on a layer, this is an important setting.
43 ===Minimum Orbital Radius===
44 Default is 10 millimeters.
46 When the orbit cool type is selected, if the area of the largest island is as large as the square of the "Minimum Orbital Radius" then the orbits will be just within the island. If the island is smaller, then the orbits will be in a square of the "Minimum Orbital Radius" around the center of the island. This is so that the hot extruder does not stay too close to small islands.
48 ===Name of Alteration Files===
49 Cool looks for alteration files in the alterations folder in the .skeinforge folder in the home directory. Cool 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. The cool start and end text idea is from:
50 http://makerhahn.blogspot.com/2008/10/yay-minimug.html
52 ====Name of Cool End File====
53 Default is cool_end.gcode.
55 If there is a file with the name of the "Name of Cool End File" setting, it will be added to the end of the orbits.
57 ====Name of Cool Start File====
58 Default is cool_start.gcode.
60 If there is a file with the name of the "Name of Cool Start File" setting, it will be added to the start of the orbits.
63 Default is 2 millimeters.
65 When the orbit cool type is selected, the orbits will be outset around the largest island by 'Orbital Outset' millimeters. If 'Orbital Outset' is negative, the orbits will be inset instead.
67 ===Turn Fan On at Beginning===
70 When selected, cool will turn the fan on at the beginning of the fabrication by adding the M106 command.
72 ===Turn Fan Off at Ending===
75 When selected, cool will turn the fan off at the ending of the fabrication by adding the M107 command.
78 The following examples cool the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and cool.py.
81 This brings up the cool dialog.
83 > python cool.py Screw Holder Bottom.stl
84 The cool tool is parsing the file:
85 Screw Holder Bottom.stl
87 The cool tool has created the file:
88 .. Screw Holder Bottom_cool.gcode
92 from __future__ import absolute_import
93 #Init has to be imported first because it has code to workaround the python bug where relative imports don't work if the module is imported as a main module.
96 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
97 from fabmetheus_utilities import archive
98 from fabmetheus_utilities import euclidean
99 from fabmetheus_utilities import gcodec
100 from fabmetheus_utilities import intercircle
101 from fabmetheus_utilities import settings
102 from skeinforge_application.skeinforge_utilities import skeinforge_craft
103 from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
104 from skeinforge_application.skeinforge_utilities import skeinforge_profile
109 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
110 __date__ = '$Date: 2008/21/04 $'
111 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
114 def getCraftedText(fileName, text, repository=None):
115 'Cool a gcode linear move text.'
116 return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository)
118 def getCraftedTextFromText(gcodeText, repository=None):
119 'Cool a gcode linear move text.'
120 if gcodec.isProcedureDoneOrFileIsEmpty(gcodeText, 'cool'):
122 if repository == None:
123 repository = settings.getReadRepository(CoolRepository())
124 if not repository.activateCool.value:
126 return CoolSkein().getCraftedGcode(gcodeText, repository)
128 def getNewRepository():
129 'Get new repository.'
130 return CoolRepository()
132 def writeOutput(fileName, shouldAnalyze=True):
133 'Cool a gcode linear move file. Chain cool the gcode if it is not already cooled.'
134 skeinforge_craft.writeChainTextWithNounMessage(fileName, 'cool', shouldAnalyze)
137 class CoolRepository:
138 'A class to handle the cool settings.'
140 'Set the default settings, execute title & settings fileName.'
141 skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.cool.html', self )
142 self.fileNameInput = settings.FileNameInput().getFromFileName(
143 fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Cool', self, '')
144 self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute(
145 'http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Cool')
146 self.activateCool = settings.BooleanSetting().getFromValue('Activate Cool', self, True)
147 self.bridgeCool = settings.FloatSpin().getFromValue(0.0, 'Bridge Cool (Celcius):', self, 10.0, 1.0)
148 self.coolType = settings.MenuButtonDisplay().getFromName('Cool Type:', self)
149 self.orbit = settings.MenuRadio().getFromMenuButtonDisplay(self.coolType, 'Orbit', self, False)
150 self.slowDown = settings.MenuRadio().getFromMenuButtonDisplay(self.coolType, 'Slow Down', self, True)
151 self.maximumCool = settings.FloatSpin().getFromValue(0.0, 'Maximum Cool (Celcius):', self, 10.0, 2.0)
152 self.minimumLayerTime = settings.FloatSpin().getFromValue(0.0, 'Minimum Layer Time (seconds):', self, 120.0, 10.0)
153 self.minimumOrbitalRadius = settings.FloatSpin().getFromValue(
154 0.0, 'Minimum Orbital Radius (millimeters):', self, 20.0, 10.0)
155 settings.LabelSeparator().getFromRepository(self)
156 settings.LabelDisplay().getFromName('- Name of Alteration Files -', self )
157 self.nameOfCoolEndFile = settings.StringSetting().getFromValue('Name of Cool End File:', self, 'cool_end.gcode')
158 self.nameOfCoolStartFile = settings.StringSetting().getFromValue('Name of Cool Start File:', self, 'cool_start.gcode')
159 settings.LabelSeparator().getFromRepository(self)
160 self.orbitalOutset = settings.FloatSpin().getFromValue(1.0, 'Orbital Outset (millimeters):', self, 5.0, 2.0)
161 self.turnFanOnAtBeginning = settings.BooleanSetting().getFromValue('Turn Fan On at Beginning', self, True)
162 self.turnFanOffAtEnding = settings.BooleanSetting().getFromValue('Turn Fan Off at Ending', self, True)
163 self.executeTitle = 'Cool'
165 self.minimumFeedRate = settings.FloatSpin().getFromValue(0.0, 'Minimum feed rate (mm/s):', self, 10.0, 5.0)
168 'Cool button has been clicked.'
169 fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(
170 self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
171 for fileName in fileNames:
172 writeOutput(fileName)
176 'A class to cool a skein of extrusions.'
178 self.boundaryLayer = None
179 self.coolTemperature = None
180 self.distanceFeedRate = gcodec.DistanceFeedRate()
181 self.feedRateMinute = 960.0
183 self.isBridgeLayer = False
184 self.isExtruderActive = False
185 self.layerCount = settings.LayerCount()
188 self.multiplier = 1.0
189 self.oldFlowRate = None
190 self.oldFlowRateString = None
191 self.oldLocation = None
192 self.oldTemperature = None
195 def addCoolOrbits(self, remainingOrbitTime):
196 'Add the minimum radius cool orbits.'
197 if len(self.boundaryLayer.loops) < 1:
199 insetBoundaryLoops = self.boundaryLayer.loops
200 if abs(self.repository.orbitalOutset.value) > 0.1 * abs(self.edgeWidth):
201 insetBoundaryLoops = intercircle.getInsetLoopsFromLoops(self.boundaryLayer.loops, -self.repository.orbitalOutset.value)
202 if len(insetBoundaryLoops) < 1:
203 insetBoundaryLoops = self.boundaryLayer.loops
204 largestLoop = euclidean.getLargestLoop(insetBoundaryLoops)
205 loopArea = euclidean.getAreaLoopAbsolute(largestLoop)
206 if loopArea < self.minimumArea:
207 center = 0.5 * (euclidean.getMaximumByComplexPath(largestLoop) + euclidean.getMinimumByComplexPath(largestLoop))
208 centerXBounded = max(center.real, self.boundingRectangle.cornerMinimum.real)
209 centerXBounded = min(centerXBounded, self.boundingRectangle.cornerMaximum.real)
210 centerYBounded = max(center.imag, self.boundingRectangle.cornerMinimum.imag)
211 centerYBounded = min(centerYBounded, self.boundingRectangle.cornerMaximum.imag)
212 center = complex(centerXBounded, centerYBounded)
213 maximumCorner = center + self.halfCorner
214 minimumCorner = center - self.halfCorner
215 largestLoop = euclidean.getSquareLoopWiddershins(minimumCorner, maximumCorner)
216 pointComplex = euclidean.getXYComplexFromVector3(self.oldLocation)
217 if pointComplex != None:
218 largestLoop = euclidean.getLoopStartingClosest(self.edgeWidth, pointComplex, largestLoop)
219 intercircle.addOrbitsIfLarge(
220 self.distanceFeedRate, largestLoop, self.orbitalFeedRatePerSecond, remainingOrbitTime, self.highestZ)
222 def addCoolTemperature(self, remainingOrbitTime):
223 'Parse a gcode line and add it to the cool skein.'
224 layerCool = self.repository.maximumCool.value * remainingOrbitTime / self.repository.minimumLayerTime.value
225 if self.isBridgeLayer:
226 layerCool = max(self.repository.bridgeCool.value, layerCool)
227 if self.oldTemperature != None and layerCool != 0.0:
228 self.coolTemperature = self.oldTemperature - layerCool
229 self.addTemperature(self.coolTemperature)
231 def addFlowRate(self, flowRate):
232 'Add a multipled line of flow rate if different.'
234 self.distanceFeedRate.addLine('M108 S' + euclidean.getFourSignificantFigures(flowRate))
236 def addGcodeFromFeedRateMovementZ(self, feedRateMinute, point, z):
237 'Add a movement to the output.'
238 self.distanceFeedRate.addLine(self.distanceFeedRate.getLinearGcodeMovementWithFeedRate(feedRateMinute, point, z))
240 def addOrbitsIfNecessary(self, remainingOrbitTime):
241 'Parse a gcode line and add it to the cool skein.'
242 if remainingOrbitTime > 0.0 and self.boundaryLayer != None:
243 self.addCoolOrbits(remainingOrbitTime)
245 def addTemperature(self, temperature):
246 'Add a line of temperature.'
247 self.distanceFeedRate.addLine('M104 S' + euclidean.getRoundedToThreePlaces(temperature))
249 def getCoolMove(self, line, location, splitLine):
250 'Get cool line according to time spent on layer.'
251 self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine)
252 return self.distanceFeedRate.getLineWithFeedRate(max(self.minFeedrate, self.multiplier * self.feedRateMinute), line, splitLine)
254 def getCraftedGcode(self, gcodeText, repository):
255 'Parse gcode text and store the cool gcode.'
256 self.repository = repository
257 self.coolEndLines = settings.getAlterationFileLines(repository.nameOfCoolEndFile.value)
258 self.coolStartLines = settings.getAlterationFileLines(repository.nameOfCoolStartFile.value)
259 self.halfCorner = complex(repository.minimumOrbitalRadius.value, repository.minimumOrbitalRadius.value)
260 self.lines = archive.getTextLines(gcodeText)
261 self.minimumArea = 4.0 * repository.minimumOrbitalRadius.value * repository.minimumOrbitalRadius.value
262 self.minFeedrate = repository.minimumFeedRate.value * 60
263 self.parseInitialization()
264 self.boundingRectangle = gcodec.BoundingRectangle().getFromGcodeLines(self.lines[self.lineIndex :], 0.5 * self.edgeWidth)
265 margin = 0.2 * self.edgeWidth
266 halfCornerMargin = self.halfCorner + complex(margin, margin)
267 self.boundingRectangle.cornerMaximum -= halfCornerMargin
268 self.boundingRectangle.cornerMinimum += halfCornerMargin
269 for self.lineIndex in xrange(self.lineIndex, len(self.lines)):
270 line = self.lines[self.lineIndex]
272 if repository.turnFanOffAtEnding.value:
273 self.distanceFeedRate.addLine('M107')
274 return gcodec.getGcodeWithoutDuplication('M108', self.distanceFeedRate.output.getvalue())
276 def getLayerTime(self):
277 'Get the time the extruder spends on the layer.'
278 feedRateMinute = self.feedRateMinute
280 lastThreadLocation = self.oldLocation
281 for lineIndex in xrange(self.lineIndex, len(self.lines)):
282 line = self.lines[lineIndex]
283 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
284 firstWord = gcodec.getFirstWord(splitLine)
285 if firstWord == 'G1':
286 location = gcodec.getLocationFromSplitLine(lastThreadLocation, splitLine)
287 feedRateMinute = gcodec.getFeedRateMinute(feedRateMinute, splitLine)
288 if lastThreadLocation != None:
289 feedRateSecond = feedRateMinute / 60.0
290 layerTime += location.distance(lastThreadLocation) / feedRateSecond
291 lastThreadLocation = location
292 elif firstWord == '(<bridgeRotation>':
293 self.isBridgeLayer = True
294 elif firstWord == '(</layer>)':
298 def getLayerTimeActive(self):
299 'Get the time the extruder spends on the layer while active.'
300 feedRateMinute = self.feedRateMinute
301 isExtruderActive = self.isExtruderActive
303 lastThreadLocation = self.oldLocation
304 for lineIndex in xrange(self.lineIndex, len(self.lines)):
305 line = self.lines[lineIndex]
306 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
307 firstWord = gcodec.getFirstWord(splitLine)
308 if firstWord == 'G1':
309 location = gcodec.getLocationFromSplitLine(lastThreadLocation, splitLine)
310 feedRateMinute = gcodec.getFeedRateMinute(feedRateMinute, splitLine)
311 if lastThreadLocation != None and isExtruderActive:
312 feedRateSecond = feedRateMinute / 60.0
313 layerTime += location.distance(lastThreadLocation) / feedRateSecond
314 lastThreadLocation = location
315 elif firstWord == 'M101':
316 isExtruderActive = True
317 elif firstWord == 'M103':
318 isExtruderActive = False
319 elif firstWord == '(<bridgeRotation>':
320 self.isBridgeLayer = True
321 elif firstWord == '(</layer>)':
325 def parseInitialization(self):
326 'Parse gcode initialization and store the parameters.'
327 for self.lineIndex in xrange(len(self.lines)):
328 line = self.lines[self.lineIndex]
329 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
330 firstWord = gcodec.getFirstWord(splitLine)
331 self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
332 if firstWord == 'M108':
333 self.oldFlowRate = float(splitLine[1][1 :])
334 elif firstWord == '(<edgeWidth>':
335 self.edgeWidth = float(splitLine[1])
336 if self.repository.turnFanOnAtBeginning.value:
337 self.distanceFeedRate.addLine('M106')
338 elif firstWord == '(</extruderInitialization>)':
339 self.distanceFeedRate.addTagBracketedProcedure('cool')
341 elif firstWord == '(<operatingFlowRate>':
342 self.oldFlowRate = float(splitLine[1])
343 elif firstWord == '(<orbitalFeedRatePerSecond>':
344 self.orbitalFeedRatePerSecond = float(splitLine[1])
345 self.distanceFeedRate.addLine(line)
347 def parseLine(self, line):
348 'Parse a gcode line and add it to the cool skein.'
349 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
350 if len(splitLine) < 1:
352 firstWord = splitLine[0]
353 if firstWord == 'G1':
354 location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
355 self.highestZ = max(location.z, self.highestZ)
356 if self.isExtruderActive:
357 line = self.getCoolMove(line, location, splitLine)
358 self.oldLocation = location
359 elif firstWord == 'M101':
360 self.isExtruderActive = True
361 elif firstWord == 'M103':
362 self.isExtruderActive = False
363 elif firstWord == 'M104':
364 self.oldTemperature = gcodec.getDoubleAfterFirstLetter(splitLine[1])
365 elif firstWord == 'M108':
366 self.oldFlowRate = float(splitLine[1][1 :])
367 self.addFlowRate(self.multiplier * self.oldFlowRate)
369 elif firstWord == '(<boundaryPoint>':
370 self.boundaryLoop.append(gcodec.getLocationFromSplitLine(None, splitLine).dropAxis())
371 elif firstWord == '(<layer>':
372 self.layerCount.printProgressIncrement('cool')
373 self.distanceFeedRate.addLine(line)
374 self.distanceFeedRate.addLinesSetAbsoluteDistanceMode(self.coolStartLines)
375 layerTime = self.getLayerTime()
376 remainingOrbitTime = max(self.repository.minimumLayerTime.value - layerTime, 0.0)
377 self.addCoolTemperature(remainingOrbitTime)
378 if self.repository.orbit.value:
379 self.addOrbitsIfNecessary(remainingOrbitTime)
381 self.setMultiplier(remainingOrbitTime)
382 if self.oldFlowRate != None:
383 self.addFlowRate(self.multiplier * self.oldFlowRate)
384 z = float(splitLine[1])
385 self.boundaryLayer = euclidean.LoopLayer(z)
386 self.highestZ = max(z, self.highestZ)
387 self.distanceFeedRate.addLinesSetAbsoluteDistanceMode(self.coolEndLines)
389 elif firstWord == '(</layer>)':
390 self.isBridgeLayer = False
391 self.multiplier = 1.0
392 if self.coolTemperature != None:
393 self.addTemperature(self.oldTemperature)
394 self.coolTemperature = None
395 if self.oldFlowRate != None:
396 self.addFlowRate(self.oldFlowRate)
397 elif firstWord == '(<nestedRing>)':
398 self.boundaryLoop = []
399 self.boundaryLayer.loops.append(self.boundaryLoop)
400 self.distanceFeedRate.addLine(line)
402 def setMultiplier(self, remainingOrbitTime):
403 'Set the feed and flow rate multiplier.'
404 layerTimeActive = self.getLayerTimeActive()
405 self.multiplier = min(1.0, layerTimeActive / (remainingOrbitTime + layerTimeActive))
410 'Display the cool dialog.'
411 if len(sys.argv) > 1:
412 writeOutput(' '.join(sys.argv[1 :]))
414 settings.startMainLoopFromConstructor(getNewRepository())
416 if __name__ == '__main__':