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
96 from __future__ import absolute_import
98 from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret
99 from fabmetheus_utilities.geometry.solids import triangle_mesh
100 from fabmetheus_utilities import archive
101 from fabmetheus_utilities import euclidean
102 from fabmetheus_utilities import gcodec
103 from fabmetheus_utilities import intercircle
104 from fabmetheus_utilities import settings
105 from skeinforge_application.skeinforge_utilities import skeinforge_craft
106 from skeinforge_application.skeinforge_utilities import skeinforge_polyfile
107 from skeinforge_application.skeinforge_utilities import skeinforge_profile
112 __author__ = 'Enrique Perez (perez_enrique@yahoo.com)'
113 __date__ = '$Date: 2008/02/05 $'
114 __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
117 def getCraftedText( fileName, gcodeText = '', repository=None):
118 'Dimension a gcode file or text.'
119 return getCraftedTextFromText( archive.getTextIfEmpty(fileName, gcodeText), repository )
121 def getCraftedTextFromText(gcodeText, repository=None):
122 'Dimension a gcode text.'
123 if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'dimension'):
125 if repository == None:
126 repository = settings.getReadRepository( DimensionRepository() )
127 if not repository.activateDimension.value:
129 return DimensionSkein().getCraftedGcode(gcodeText, repository)
131 def getNewRepository():
132 'Get new repository.'
133 return DimensionRepository()
135 def writeOutput(fileName, shouldAnalyze=True):
136 'Dimension a gcode file.'
137 skeinforge_craft.writeChainTextWithNounMessage(fileName, 'dimension', shouldAnalyze)
140 class DimensionRepository(object):
141 'A class to handle the dimension settings.'
143 'Set the default settings, execute title & settings fileName.'
144 skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.dimension.html', self )
145 self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Dimension', self, '')
146 self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Dimension')
147 self.activateDimension = settings.BooleanSetting().getFromValue('Activate Dimension', self, True )
148 extrusionDistanceFormatLatentStringVar = settings.LatentStringVar()
149 self.extrusionDistanceFormatChoiceLabel = settings.LabelDisplay().getFromName('Extrusion Distance Format Choice: ', self )
150 settings.Radio().getFromRadio( extrusionDistanceFormatLatentStringVar, 'Absolute Extrusion Distance', self, True )
151 self.relativeExtrusionDistance = settings.Radio().getFromRadio( extrusionDistanceFormatLatentStringVar, 'Relative Extrusion Distance', self, False )
152 self.extruderRetractionSpeed = settings.FloatSpin().getFromValue( 4.0, 'Extruder Retraction Speed (mm/s):', self, 34.0, 13.3 )
153 settings.LabelSeparator().getFromRepository(self)
154 settings.LabelDisplay().getFromName('- Filament -', self )
155 self.filamentDiameter = settings.FloatSpin().getFromValue(1.0, 'Filament Diameter (mm):', self, 6.0, 2.89)
156 self.filamentPackingDensity = settings.FloatSpin().getFromValue(0.7, 'Filament Packing Density (ratio):', self, 1.0, 1.0)
157 settings.LabelSeparator().getFromRepository(self)
158 self.maximumEValueBeforeReset = settings.FloatSpin().getFromValue(0.0, 'Maximum E Value before Reset (float):', self, 999999.9, 91234.0)
159 self.minimumTravelForRetraction = settings.FloatSpin().getFromValue(0.0, 'Minimum Travel for Retraction (millimeters):', self, 2.0, 1.0)
160 self.retractWithinIsland = settings.BooleanSetting().getFromValue('Retract Within Island', self, False)
161 self.retractionDistance = settings.FloatSpin().getFromValue( 0.0, 'Retraction Distance (millimeters):', self, 100.0, 0.0 )
162 self.restartExtraDistance = settings.FloatSpin().getFromValue( 0.0, 'Restart Extra Distance (millimeters):', self, 100.0, 0.0 )
163 self.executeTitle = 'Dimension'
166 'Dimension button has been clicked.'
167 fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled)
168 for fileName in fileNames:
169 writeOutput(fileName)
172 class DimensionSkein(object):
173 'A class to dimension a skein of extrusions.'
176 self.absoluteDistanceMode = True
177 self.boundaryLayers = []
178 self.distanceFeedRate = gcodec.DistanceFeedRate()
179 self.feedRateMinute = None
180 self.isExtruderActive = False
183 self.maximumZFeedRatePerSecond = None
184 self.oldLocation = None
185 self.operatingFlowRate = None
186 self.retractionRatio = 1.0
187 self.totalExtrusionDistance = 0.0
188 self.travelFeedRatePerSecond = None
189 self.addRetraction = True
190 self.reverseRetraction = False
191 self.maxDistancePerMove = 30
193 def addLinearMoveExtrusionDistanceLine(self, extrusionDistance):
194 'Get the extrusion distance string from the extrusion distance.'
195 if self.repository.extruderRetractionSpeed.value != 0.0 and extrusionDistance != 0.0:
196 self.distanceFeedRate.output.write('G1 F%s\n' % self.extruderRetractionSpeedMinuteString)
197 self.distanceFeedRate.output.write('G1%s\n' % self.getExtrusionDistanceStringFromExtrusionDistance(extrusionDistance))
198 self.distanceFeedRate.output.write('G1 F%s\n' % self.distanceFeedRate.getRounded(self.feedRateMinute))
200 def getCraftedGcode(self, gcodeText, repository):
201 'Parse gcode text and store the dimension gcode.'
202 self.repository = repository
203 filamentRadius = 0.5 * repository.filamentDiameter.value
204 filamentPackingArea = math.pi * filamentRadius * filamentRadius * repository.filamentPackingDensity.value
205 self.minimumTravelForRetraction = self.repository.minimumTravelForRetraction.value
206 self.doubleMinimumTravelForRetraction = self.minimumTravelForRetraction + self.minimumTravelForRetraction
207 self.lines = archive.getTextLines(gcodeText)
208 self.parseInitialization()
209 if not self.repository.retractWithinIsland.value:
210 self.parseBoundaries()
211 self.flowScaleSixty = 60.0 * self.layerHeight * self.edgeWidth / filamentPackingArea
212 self.restartDistance = self.repository.retractionDistance.value + self.repository.restartExtraDistance.value
213 self.extruderRetractionSpeedMinuteString = self.distanceFeedRate.getRounded(60.0 * self.repository.extruderRetractionSpeed.value)
214 for lineIndex in xrange(self.lineIndex, len(self.lines)):
215 self.parseLine( lineIndex )
216 return self.distanceFeedRate.output.getvalue()
218 def getDimensionedArcMovement(self, line, splitLine):
219 'Get a dimensioned arc movement.'
220 if self.oldLocation == None:
222 relativeLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
223 self.oldLocation += relativeLocation
224 distance = gcodec.getArcDistance(relativeLocation, splitLine)
225 return line + self.getExtrusionDistanceString(distance, splitLine)
227 def getDimensionedLinearMovement( self, line, splitLine ):
228 'Get a dimensioned linear movement.'
230 if self.absoluteDistanceMode:
231 location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine)
232 if self.oldLocation != None:
233 distance = abs( location - self.oldLocation )
234 if distance > self.maxDistancePerMove * 1.1:
236 while distance > self.maxDistancePerMove * 1.1:
237 self.oldLocation.z = location.z
238 self.oldLocation += (location - self.oldLocation) / distance * self.maxDistancePerMove
239 distance -= self.maxDistancePerMove
240 e = self.getExtrusionDistanceString(self.maxDistancePerMove, splitLine)
241 extra += self.distanceFeedRate.getLinearGcodeMovementWithFeedRate(self.feedRateMinute, self.oldLocation.dropAxis(), self.oldLocation.z) + e + '\n'
243 self.oldLocation = location
245 if self.oldLocation is None:
246 print('Warning: There was no absolute location when the G91 command was parsed, so the absolute location will be set to the origin.')
247 self.oldLocation = Vector3()
248 location = gcodec.getLocationFromSplitLine(None, splitLine)
249 distance = abs( location )
250 self.oldLocation += location
251 return line + self.getExtrusionDistanceString( distance, splitLine )
253 def getDistanceToNextThread(self, lineIndex):
254 'Get the travel distance to the next thread.'
255 if self.oldLocation == None:
258 location = self.oldLocation
259 for afterIndex in xrange(lineIndex + 1, len(self.lines)):
260 line = self.lines[afterIndex]
261 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
262 firstWord = gcodec.getFirstWord(splitLine)
263 if firstWord == 'G1':
265 if not self.repository.retractWithinIsland.value:
266 locationEnclosureIndex = self.getSmallestEnclosureIndex(location.dropAxis())
267 if locationEnclosureIndex == self.getSmallestEnclosureIndex(self.oldLocation.dropAxis()):
269 locationMinusOld = location - self.oldLocation
270 return abs(locationMinusOld)
271 location = gcodec.getLocationFromSplitLine(location, splitLine)
272 elif firstWord == 'M101':
274 elif firstWord == 'M103':
278 def getExtrusionDistanceString( self, distance, splitLine ):
279 'Get the extrusion distance string.'
280 self.feedRateMinute = gcodec.getFeedRateMinute( self.feedRateMinute, splitLine )
281 if not self.isExtruderActive:
286 print('Warning, the distance is less than zero in getExtrusionDistanceString in dimension; so there will not be an E value')
290 if self.operatingFlowRate == None:
291 return self.getExtrusionDistanceStringFromExtrusionDistance(self.flowScaleSixty / 60.0 * distance)
293 scaledFlowRate = self.flowRate * self.flowScaleSixty
294 return self.getExtrusionDistanceStringFromExtrusionDistance(scaledFlowRate / self.feedRateMinute * distance)
296 def getExtrusionDistanceStringFromExtrusionDistance(self, extrusionDistance):
297 'Get the extrusion distance string from the extrusion distance.'
298 if self.repository.relativeExtrusionDistance.value:
299 return ' E' + self.distanceFeedRate.getRounded(extrusionDistance)
300 self.totalExtrusionDistance += extrusionDistance
301 return ' E' + self.distanceFeedRate.getRounded(self.totalExtrusionDistance)
303 def getRetractionRatio(self, lineIndex):
304 'Get the retraction ratio.'
305 distanceToNextThread = self.getDistanceToNextThread(lineIndex)
306 if distanceToNextThread == None:
308 if distanceToNextThread >= self.doubleMinimumTravelForRetraction:
310 if distanceToNextThread <= self.minimumTravelForRetraction:
312 return (distanceToNextThread - self.minimumTravelForRetraction) / self.minimumTravelForRetraction
314 def getSmallestEnclosureIndex(self, point):
315 'Get the index of the smallest boundary loop which encloses the point.'
316 boundaryLayer = self.boundaryLayers[self.layerIndex]
317 for loopIndex, loop in enumerate(boundaryLayer.loops):
318 if euclidean.isPointInsideLoop(loop, point):
322 def parseBoundaries(self):
323 'Parse the boundaries and add them to the boundary layers.'
326 for line in self.lines[self.lineIndex :]:
327 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
328 firstWord = gcodec.getFirstWord(splitLine)
329 if firstWord == '(</boundaryPerimeter>)':
331 elif firstWord == '(<boundaryPoint>':
332 location = gcodec.getLocationFromSplitLine(None, splitLine)
333 if boundaryLoop == None:
335 boundaryLayer.loops.append(boundaryLoop)
336 boundaryLoop.append(location.dropAxis())
337 elif firstWord == '(<layer>':
338 boundaryLayer = euclidean.LoopLayer(float(splitLine[1]))
339 self.boundaryLayers.append(boundaryLayer)
340 for boundaryLayer in self.boundaryLayers:
341 triangle_mesh.sortLoopsInOrderOfArea(False, boundaryLayer.loops)
343 def parseInitialization(self):
344 'Parse gcode initialization and store the parameters.'
345 for self.lineIndex in xrange(len(self.lines)):
346 line = self.lines[self.lineIndex]
347 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
348 firstWord = gcodec.getFirstWord(splitLine)
349 self.distanceFeedRate.parseSplitLine(firstWord, splitLine)
350 if firstWord == '(</extruderInitialization>)':
351 self.distanceFeedRate.addTagBracketedProcedure('dimension')
353 elif firstWord == '(<layerHeight>':
354 self.layerHeight = float(splitLine[1])
355 elif firstWord == '(<maximumZDrillFeedRatePerSecond>':
356 self.maximumZFeedRatePerSecond = float(splitLine[1])
357 elif firstWord == '(<maximumZFeedRatePerSecond>':
358 self.maximumZFeedRatePerSecond = float(splitLine[1])
359 elif firstWord == '(<operatingFeedRatePerSecond>':
360 self.feedRateMinute = 60.0 * float(splitLine[1])
361 elif firstWord == '(<operatingFlowRate>':
362 self.operatingFlowRate = float(splitLine[1])
363 self.flowRate = self.operatingFlowRate
364 elif firstWord == '(<edgeWidth>':
365 self.edgeWidth = float(splitLine[1])
366 elif firstWord == '(<travelFeedRatePerSecond>':
367 self.travelFeedRatePerSecond = float(splitLine[1])
368 self.distanceFeedRate.addLine(line)
370 def parseLine( self, lineIndex ):
371 'Parse a gcode line and add it to the dimension skein.'
372 line = self.lines[lineIndex].lstrip()
373 splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line)
374 if len(splitLine) < 1:
376 firstWord = splitLine[0]
377 if firstWord == 'G2' or firstWord == 'G3':
378 line = self.getDimensionedArcMovement( line, splitLine )
379 if firstWord == 'G1':
380 line = self.getDimensionedLinearMovement( line, splitLine )
381 if firstWord == 'G90':
382 self.absoluteDistanceMode = True
383 elif firstWord == 'G91':
384 self.absoluteDistanceMode = False
385 elif firstWord == '(<layer>':
387 settings.printProgress(self.layerIndex, 'dimension')
388 elif firstWord == '(</layer>)' or firstWord == '(<supportLayer>)' or firstWord == '(</supportLayer>)':
389 if self.totalExtrusionDistance > 0.0 and not self.repository.relativeExtrusionDistance.value:
390 self.distanceFeedRate.addLine('G92 E0')
391 self.totalExtrusionDistance = 0.0
392 elif firstWord == 'M101':
393 if self.retractionRatio > 0.0:
394 self.addLinearMoveExtrusionDistanceLine(self.restartDistance * self.retractionRatio)
395 if self.totalExtrusionDistance > self.repository.maximumEValueBeforeReset.value:
396 if not self.repository.relativeExtrusionDistance.value:
397 self.distanceFeedRate.addLine('G92 E0')
398 self.totalExtrusionDistance = 0.0
399 self.isExtruderActive = True
400 elif firstWord == 'M103':
401 self.retractionRatio = self.getRetractionRatio(lineIndex)
402 if self.retractionRatio > 0.0:
403 self.addLinearMoveExtrusionDistanceLine(-self.repository.retractionDistance.value * self.retractionRatio)
404 self.isExtruderActive = False
405 elif firstWord == 'M108':
406 self.flowRate = float( splitLine[1][1 :] )
407 self.distanceFeedRate.addLine(line)
411 'Display the dimension dialog.'
412 if len(sys.argv) > 1:
413 writeOutput(' '.join(sys.argv[1 :]))
415 settings.startMainLoopFromConstructor(getNewRepository())
417 if __name__ == '__main__':