3 This page is in the table of contents.
4 Dimension adds Adrian's extruder distance E value so firmware does not have to calculate it on it's own and can set the extruder speed in relation to the distance that needs to be extruded. Some printers don't support this. Extruder distance is described at:
6 http://blog.reprap.org/2009/05/4d-printing.html
8 and in Erik de Bruijn's conversion script page at:
10 http://objects.reprap.org/wiki/3D-to-5D-Gcode.php
12 The dimension manual page is at:
14 http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Dimension
16 Nophead wrote an excellent article on how to set the filament parameters:
18 http://hydraraptor.blogspot.com/2011/03/spot-on-flow-rate.html
21 The default 'Activate Dimension' checkbox is off. When it is on, the functions described below will work, when it is off, the functions will not be called.
24 ===Extrusion Distance Format Choice===
25 Default is 'Absolute Extrusion Distance' because in Adrian's description the distance is absolute. In future, because the relative distances are smaller than the cumulative absolute distances, hopefully the firmware will be able to use relative distance.
27 ====Absolute Extrusion Distance====
28 When selected, the extrusion distance output will be the total extrusion distance to that gcode line.
30 ====Relative Extrusion Distance====
31 When selected, the extrusion distance output will be the extrusion distance from the last gcode line.
33 ===Extruder Retraction Speed===
36 Defines the extruder retraction feed rate. A high value will allow the retraction operation to complete before much material oozes out. If your extruder can handle it, this value should be much larger than your feed rate.
38 As an example, I have a feed rate of 48 mm/s and a 'Extruder Retraction Speed' of 150 mm/s.
41 ====Filament Diameter====
42 Default is 2.8 millimeters.
44 Defines the filament diameter.
46 ====Filament Packing Density====
47 Default is 0.85. This is for ABS.
49 Defines the effective filament packing density.
51 The default value is so low for ABS because ABS is relatively soft and with a pinch wheel extruder the teeth of the pinch dig in farther, so it sees a smaller effective diameter. With a hard plastic like PLA the teeth of the pinch wheel don't dig in as far, so it sees a larger effective diameter, so feeds faster, so for PLA the value should be around 0.97. This is with Wade's hobbed bolt. The effect is less significant with larger pinch wheels.
53 Overall, you'll have to find the optimal filament packing density by experiment.
55 ===Maximum E Value before Reset===
58 Defines the maximum E value before it is reset with the 'G92 E0' command line. The reason it is reset only after the maximum E value is reached is because at least one firmware takes time to reset. The problem with waiting until the E value is high before resetting is that more characters are sent. So if your firmware takes a lot of time to reset, set this parameter to a high value, if it doesn't set this parameter to a low value or even zero.
60 ===Minimum Travel for Retraction===
61 Default: 1.0 millimeter
63 Defines the minimum distance that the extruder head has to travel from the end of one thread to the beginning of another, in order to trigger the extruder retraction. Setting this to a high value means the extruder will retract only occasionally, setting it to a low value means the extruder will retract most of the time.
65 ===Retract Within Island===
68 When selected, retraction will work even when the next thread is within the same island. If it is not selected, retraction will only work when crossing a boundary.
70 ===Retraction Distance===
73 Defines the amount the extruder retracts (sucks back) the extruded filament whenever an extruder stop is commanded. Using this seems to help prevent stringing. e.g. If set to 10 the extruder reverses the distance required to pull back 10mm of filament. In fact this does not actually happen but if you set this distance by trial and error you can get to a point where there is very little ooze from the extruder when it stops which is not normally the case.
75 ===Restart Extra Distance===
78 Defines the restart extra distance when the thread restarts. The restart distance will be the retraction distance plus the restart extra distance.
80 If this is greater than zero when the extruder starts this distance is added to the retract value giving extra filament. It can be a negative value in which case it is subtracted from the retraction distance. On some Repstrap machines a negative value can stop the build up of plastic that can occur at the start of edges.
83 The following examples dimension the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and dimension.py.
86 This brings up the dimension dialog.
88 > python dimension.py Screw Holder Bottom.stl
89 The dimension tool is parsing the file:
90 Screw Holder Bottom.stl
92 The dimension tool has created the file:
93 .. Screw Holder Bottom_dimension.gcode
97 #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.
100 from datetime import date
101 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
102 from fabmetheus_utilities.geometry.solids import triangle_mesh
103 from fabmetheus_utilities import archive
104 from fabmetheus_utilities import euclidean
105 from fabmetheus_utilities import gcodec
106 from fabmetheus_utilities import intercircle
107 from fabmetheus_utilities import settings
108 from skeinforge_application.skeinforge_utilities import skeinforge_craft
109 from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
110 from skeinforge_application.skeinforge_utilities import skeinforge_profile
116 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
117 __date__ = '$Date: 2008/02/05 $'
118 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
121 def getCraftedText( fileName, gcodeText = '', repository=None):
122 'Dimension a gcode file or text.'
123 return getCraftedTextFromText( archive.getTextIfEmpty(fileName, gcodeText), repository )
125 def getCraftedTextFromText(gcodeText, repository=None):
126 'Dimension a gcode text.'
127 if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'dimension'):
129 if repository == None:
130 repository = settings.getReadRepository( DimensionRepository() )
131 if not repository.activateDimension.value:
133 return DimensionSkein().getCraftedGcode(gcodeText, repository)
135 def getNewRepository():
136 'Get new repository.'
137 return DimensionRepository()
139 def writeOutput(fileName, shouldAnalyze=True):
140 'Dimension a gcode file.'
141 skeinforge_craft.writeChainTextWithNounMessage(fileName, 'dimension', shouldAnalyze)
144 class DimensionRepository:
145 'A class to handle the dimension settings.'
147 'Set the default settings, execute title & settings fileName.'
148 skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.dimension.html', self )
149 self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Dimension', self, '')
150 self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Dimension')
151 self.activateDimension = settings.BooleanSetting().getFromValue('Activate Dimension', self, True )
152 extrusionDistanceFormatLatentStringVar = settings.LatentStringVar()
153 self.extrusionDistanceFormatChoiceLabel = settings.LabelDisplay().getFromName('Extrusion Distance Format Choice: ', self )
154 settings.Radio().getFromRadio( extrusionDistanceFormatLatentStringVar, 'Absolute Extrusion Distance', self, True )
155 self.relativeExtrusionDistance = settings.Radio().getFromRadio( extrusionDistanceFormatLatentStringVar, 'Relative Extrusion Distance', self, False )
156 self.extruderRetractionSpeed = settings.FloatSpin().getFromValue( 4.0, 'Extruder Retraction Speed (mm/s):', self, 34.0, 13.3 )
157 settings.LabelSeparator().getFromRepository(self)
158 settings.LabelDisplay().getFromName('- Filament -', self )
159 self.filamentDiameter = settings.FloatSpin().getFromValue(1.0, 'Filament Diameter (mm):', self, 6.0, 2.89)
160 self.filamentPackingDensity = settings.FloatSpin().getFromValue(0.7, 'Filament Packing Density (ratio):', self, 1.0, 1.0)
161 settings.LabelSeparator().getFromRepository(self)
162 self.maximumEValueBeforeReset = settings.FloatSpin().getFromValue(0.0, 'Maximum E Value before Reset (float):', self, 999999.9, 91234.0)
163 self.minimumTravelForRetraction = settings.FloatSpin().getFromValue(0.0, 'Minimum Travel for Retraction (millimeters):', self, 2.0, 1.0)
164 self.retractWithinIsland = settings.BooleanSetting().getFromValue('Retract Within Island', self, False)
165 self.retractionDistance = settings.FloatSpin().getFromValue( 0.0, 'Retraction Distance (millimeters):', self, 100.0, 0.0 )
166 self.restartExtraDistance = settings.FloatSpin().getFromValue( 0.0, 'Restart Extra Distance (millimeters):', self, 100.0, 0.0 )
167 self.executeTitle = 'Dimension'
170 'Dimension button has been clicked.'
171 fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
172 for fileName in fileNames:
173 writeOutput(fileName)
176 class DimensionSkein:
177 'A class to dimension a skein of extrusions.'
180 self.absoluteDistanceMode = True
181 self.boundaryLayers = []
182 self.distanceFeedRate = gcodec.DistanceFeedRate()
183 self.feedRateMinute = None
184 self.isExtruderActive = False
187 self.maximumZFeedRatePerSecond = None
188 self.oldLocation = None
189 self.operatingFlowRate = None
190 self.retractionRatio = 1.0
191 self.totalExtrusionDistance = 0.0
192 self.travelFeedRatePerSecond = None
193 self.zDistanceRatio = 5.0
195 def addLinearMoveExtrusionDistanceLine(self, extrusionDistance):
196 'Get the extrusion distance string from the extrusion distance.'
197 if self.repository.extruderRetractionSpeed.value != 0.0 and extrusionDistance != 0.0:
198 self.distanceFeedRate.output.write('G1 F%s\n' % self.extruderRetractionSpeedMinuteString)
199 self.distanceFeedRate.output.write('G1%s\n' % self.getExtrusionDistanceStringFromExtrusionDistance(extrusionDistance))
200 self.distanceFeedRate.output.write('G1 F%s\n' % self.distanceFeedRate.getRounded(self.feedRateMinute))
202 def getCraftedGcode(self, gcodeText, repository):
203 'Parse gcode text and store the dimension gcode.'
204 self.repository = repository
205 filamentRadius = 0.5 * repository.filamentDiameter.value
206 filamentPackingArea = math.pi * filamentRadius * filamentRadius * repository.filamentPackingDensity.value
207 self.minimumTravelForRetraction = self.repository.minimumTravelForRetraction.value
208 self.doubleMinimumTravelForRetraction = self.minimumTravelForRetraction + self.minimumTravelForRetraction
209 self.lines = archive.getTextLines(gcodeText)
210 self.parseInitialization()
211 if not self.repository.retractWithinIsland.value:
212 self.parseBoundaries()
213 self.flowScaleSixty = 60.0 * self.layerHeight * self.edgeWidth / filamentPackingArea
214 self.restartDistance = self.repository.retractionDistance.value + self.repository.restartExtraDistance.value
215 self.extruderRetractionSpeedMinuteString = self.distanceFeedRate.getRounded(60.0 * self.repository.extruderRetractionSpeed.value)
216 if self.maximumZFeedRatePerSecond != None and self.travelFeedRatePerSecond != None:
217 self.zDistanceRatio = self.travelFeedRatePerSecond / self.maximumZFeedRatePerSecond
218 for lineIndex in xrange(self.lineIndex, len(self.lines)):
219 self.parseLine( lineIndex )
220 return self.distanceFeedRate.output.getvalue()
222 def getDimensionedArcMovement(self, line, splitLine):
223 'Get a dimensioned arc movement.'
224 if self.oldLocation == None:
226 relativeLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
227 self.oldLocation += relativeLocation
228 distance = gcodec.getArcDistance(relativeLocation, splitLine)
229 return line + self.getExtrusionDistanceString(distance, splitLine)
231 def getDimensionedLinearMovement( self, line, splitLine ):
232 'Get a dimensioned linear movement.'
234 if self.absoluteDistanceMode:
235 location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
236 if self.oldLocation != None:
237 distance = abs( location - self.oldLocation )
238 self.oldLocation = location
240 if self.oldLocation == None:
241 print('Warning: There was no absolute location when the G91 command was parsed, so the absolute location will be set to the origin.')
242 self.oldLocation = Vector3()
243 location = gcodec.getLocationFromSplitLine(None, splitLine)
244 distance = abs( location )
245 self.oldLocation += location
246 return line + self.getExtrusionDistanceString( distance, splitLine )
248 def getDistanceToNextThread(self, lineIndex):
249 'Get the travel distance to the next thread.'
250 if self.oldLocation == None:
253 location = self.oldLocation
254 for afterIndex in xrange(lineIndex + 1, len(self.lines)):
255 line = self.lines[afterIndex]
256 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
257 firstWord = gcodec.getFirstWord(splitLine)
258 if firstWord == 'G1':
260 if not self.repository.retractWithinIsland.value:
261 locationEnclosureIndex = self.getSmallestEnclosureIndex(location.dropAxis())
262 if locationEnclosureIndex != self.getSmallestEnclosureIndex(self.oldLocation.dropAxis()):
264 locationMinusOld = location - self.oldLocation
265 xyTravel = abs(locationMinusOld.dropAxis())
266 zTravelMultiplied = locationMinusOld.z * self.zDistanceRatio
267 return math.sqrt(xyTravel * xyTravel + zTravelMultiplied * zTravelMultiplied)
268 location = gcodec.getLocationFromSplitLine(location, splitLine)
269 elif firstWord == 'M101':
271 elif firstWord == 'M103':
275 def getExtrusionDistanceString( self, distance, splitLine ):
276 'Get the extrusion distance string.'
277 self.feedRateMinute = gcodec.getFeedRateMinute( self.feedRateMinute, splitLine )
278 if not self.isExtruderActive:
283 print('Warning, the distance is less than zero in getExtrusionDistanceString in dimension; so there will not be an E value')
287 if self.operatingFlowRate == None:
288 return self.getExtrusionDistanceStringFromExtrusionDistance(self.flowScaleSixty / 60.0 * distance)
290 scaledFlowRate = self.flowRate * self.flowScaleSixty
291 return self.getExtrusionDistanceStringFromExtrusionDistance(scaledFlowRate / self.feedRateMinute * distance)
293 def getExtrusionDistanceStringFromExtrusionDistance(self, extrusionDistance):
294 'Get the extrusion distance string from the extrusion distance.'
295 if self.repository.relativeExtrusionDistance.value:
296 return ' E' + self.distanceFeedRate.getRounded(extrusionDistance)
297 self.totalExtrusionDistance += extrusionDistance
298 return ' E' + self.distanceFeedRate.getRounded(self.totalExtrusionDistance)
300 def getRetractionRatio(self, lineIndex):
301 'Get the retraction ratio.'
302 distanceToNextThread = self.getDistanceToNextThread(lineIndex)
303 if distanceToNextThread == None:
305 if distanceToNextThread >= self.doubleMinimumTravelForRetraction:
307 if distanceToNextThread <= self.minimumTravelForRetraction:
309 return (distanceToNextThread - self.minimumTravelForRetraction) / self.minimumTravelForRetraction
311 def getSmallestEnclosureIndex(self, point):
312 'Get the index of the smallest boundary loop which encloses the point.'
313 boundaryLayer = self.boundaryLayers[self.layerIndex]
314 for loopIndex, loop in enumerate(boundaryLayer.loops):
315 if euclidean.isPointInsideLoop(loop, point):
319 def parseBoundaries(self):
320 'Parse the boundaries and add them to the boundary layers.'
323 for line in self.lines[self.lineIndex :]:
324 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
325 firstWord = gcodec.getFirstWord(splitLine)
326 if firstWord == '(</boundaryPerimeter>)':
328 elif firstWord == '(<boundaryPoint>':
329 location = gcodec.getLocationFromSplitLine(None, splitLine)
330 if boundaryLoop == None:
332 boundaryLayer.loops.append(boundaryLoop)
333 boundaryLoop.append(location.dropAxis())
334 elif firstWord == '(<layer>':
335 boundaryLayer = euclidean.LoopLayer(float(splitLine[1]))
336 self.boundaryLayers.append(boundaryLayer)
337 for boundaryLayer in self.boundaryLayers:
338 triangle_mesh.sortLoopsInOrderOfArea(False, boundaryLayer.loops)
340 def parseInitialization(self):
341 'Parse gcode initialization and store the parameters.'
342 for self.lineIndex in xrange(len(self.lines)):
343 line = self.lines[self.lineIndex]
344 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
345 firstWord = gcodec.getFirstWord(splitLine)
346 self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
347 if firstWord == '(</extruderInitialization>)':
348 self.distanceFeedRate.addTagBracketedProcedure('dimension')
350 elif firstWord == '(<layerHeight>':
351 self.layerHeight = float(splitLine[1])
352 elif firstWord == '(<maximumZDrillFeedRatePerSecond>':
353 self.maximumZFeedRatePerSecond = float(splitLine[1])
354 elif firstWord == '(<maximumZFeedRatePerSecond>':
355 self.maximumZFeedRatePerSecond = float(splitLine[1])
356 elif firstWord == '(<operatingFeedRatePerSecond>':
357 self.feedRateMinute = 60.0 * float(splitLine[1])
358 elif firstWord == '(<operatingFlowRate>':
359 self.operatingFlowRate = float(splitLine[1])
360 self.flowRate = self.operatingFlowRate
361 elif firstWord == '(<edgeWidth>':
362 self.edgeWidth = float(splitLine[1])
363 elif firstWord == '(<travelFeedRatePerSecond>':
364 self.travelFeedRatePerSecond = float(splitLine[1])
365 self.distanceFeedRate.addLine(line)
367 def parseLine( self, lineIndex ):
368 'Parse a gcode line and add it to the dimension skein.'
369 line = self.lines[lineIndex].lstrip()
370 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
371 if len(splitLine) < 1:
373 firstWord = splitLine[0]
374 if firstWord == 'G2' or firstWord == 'G3':
375 line = self.getDimensionedArcMovement( line, splitLine )
376 if firstWord == 'G1':
377 line = self.getDimensionedLinearMovement( line, splitLine )
378 if firstWord == 'G90':
379 self.absoluteDistanceMode = True
380 elif firstWord == 'G91':
381 self.absoluteDistanceMode = False
382 elif firstWord == '(<layer>':
384 settings.printProgress(self.layerIndex, 'dimension')
385 elif firstWord == 'M101':
386 self.addLinearMoveExtrusionDistanceLine(self.restartDistance * self.retractionRatio)
387 if self.totalExtrusionDistance > self.repository.maximumEValueBeforeReset.value:
388 if not self.repository.relativeExtrusionDistance.value:
389 self.distanceFeedRate.addLine('G92 E0')
390 self.totalExtrusionDistance = 0.0
391 self.isExtruderActive = True
392 elif firstWord == 'M103':
393 self.retractionRatio = self.getRetractionRatio(lineIndex)
394 self.addLinearMoveExtrusionDistanceLine(-self.repository.retractionDistance.value * self.retractionRatio)
395 self.isExtruderActive = False
396 elif firstWord == 'M108':
397 self.flowRate = float( splitLine[1][1 :] )
398 self.distanceFeedRate.addLine(line)
402 'Display the dimension dialog.'
403 if len(sys.argv) > 1:
404 writeOutput(' '.join(sys.argv[1 :]))
406 settings.startMainLoopFromConstructor(getNewRepository())
408 if __name__ == '__main__':