""" Comb is a script to comb the extrusion hair of a gcode file. The default 'Activate Comb' checkbox is on. When it is on, the functions described below will work, when it is off, the functions will not be called. Comb bends the extruder travel paths around holes in the carve, to avoid stringers. It moves the extruder to the inside of outer perimeters before turning the extruder on so any start up ooze will be inside the shape. It jitters the loop end position to a different place on each layer to prevent the a ridge from forming. The 'Arrival Inset Follow Distance over Extrusion Width' is the ratio of the amount before the start of the outer perimeter the extruder will be moved to. A high value means the extruder will move way before the beginning of the perimeter and a low value means the extruder will be moved just before the beginning. The "Jitter Over Extrusion Width (ratio)" is the ratio of the amount the loop ends will be jittered. A high value means the loops will start all over the place and a low value means loops will start at roughly the same place on each layer. The 'Minimum Perimeter Departure Distance over Extrusion Width' is the ratio of the minimum distance that the extruder will travel and loop before leaving an outer perimeter. A high value means the extruder will loop many times before leaving, so that the ooze will finish within the perimeter, a low value means the extruder will not loop and a stringer might be created from the outer perimeter. To run comb, in a shell type: > python comb.py The following examples comb the files Screw Holder Bottom.gcode & Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.gcode, Screw Holder Bottom.stl and comb.py. The comb function will comb if 'Activate Comb' is true, which can be set in the dialog or by changing the preferences file 'comb.csv' in the '.skeinforge' folder in your home directory with a text editor or a spreadsheet program set to separate tabs. The functions writeOutput and getCombChainGcode check to see if the text has been combed, if not they call getTowerChainGcode in tower.py to tower the text; once they have the towered text, then they comb. Pictures of combing in action are available from the Metalab blog at: http://reprap.soup.io/?search=combing > python comb.py This brings up the dialog, after clicking 'Comb', the following is printed: File Screw Holder Bottom.stl is being chain combed. The combed file is saved as Screw Holder Bottom_comb.gcode >python Python 2.5.1 (r251:54863, Sep 22 2007, 01:43:31) [GCC 4.2.1 (SUSE Linux)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import comb >>> comb.main() This brings up the comb dialog. >>> comb.writeOutput() Screw Holder Bottom.stl File Screw Holder Bottom.stl is being chain combed. The combed file is saved as Screw Holder Bottom_comb.gcode >>> comb.getCombGcode(" ( GCode generated by May 8, 2008 carve.py ) ( Extruder Initialization ) .. many lines of gcode .. ") >>> comb.getCombChainGcode(" ( GCode generated by May 8, 2008 carve.py ) ( Extruder Initialization ) .. many lines of gcode .. ") """ from __future__ import absolute_import #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. import __init__ from skeinforge_tools.skeinforge_utilities import euclidean from skeinforge_tools.skeinforge_utilities import gcodec from skeinforge_tools.skeinforge_utilities import intercircle from skeinforge_tools.skeinforge_utilities import preferences from skeinforge_tools import analyze from skeinforge_tools.skeinforge_utilities import interpret from skeinforge_tools import polyfile from skeinforge_tools import tower import cStringIO import math import sys import time __author__ = "Enrique Perez (perez_enrique@yahoo.com)" __date__ = "$Date: 2008/21/04 $" __license__ = "GPL 3.0" #patched over falling tower comb bug if location.z < self.getBetweens()[ 0 ][ 0 ].z + 0.5 * self.extrusionWidth, but a real solution would be nice #addLoopsBeforeLeavingPerimeter or something before crossing bug, seen on layer 8 of Screw holder def getCombChainGcode( fileName, gcodeText, combPreferences = None ): "Comb a gcode linear move text. Chain comb the gcode if it is not already combed." gcodeText = gcodec.getGcodeFileText( fileName, gcodeText ) if not gcodec.isProcedureDone( gcodeText, 'tower' ): gcodeText = tower.getTowerChainGcode( fileName, gcodeText ) return getCombGcode( gcodeText, combPreferences ) def getCombGcode( gcodeText, combPreferences = None ): "Comb a gcode linear move text." if gcodeText == '': return '' if gcodec.isProcedureDone( gcodeText, 'comb' ): return gcodeText if combPreferences == None: combPreferences = CombPreferences() preferences.readPreferences( combPreferences ) if not combPreferences.activateComb.value: return gcodeText skein = CombSkein() skein.parseGcode( combPreferences, gcodeText ) return skein.output.getvalue() def isLoopNumberEqual( betweenX, betweenXIndex, loopNumber ): "Determine if the loop number is equal." if betweenXIndex >= len( betweenX ): return False return betweenX[ betweenXIndex ].index == loopNumber def writeOutput( fileName = '' ): "Comb a gcode linear move file. Chain comb the gcode if it is not already combed. If no fileName is specified, comb the first unmodified gcode file in this folder." if fileName == '': unmodified = interpret.getGNUTranslatorFilesUnmodified() if len( unmodified ) == 0: print( "There are no unmodified gcode files in this folder." ) return fileName = unmodified[ 0 ] combPreferences = CombPreferences() preferences.readPreferences( combPreferences ) startTime = time.time() print( 'File ' + gcodec.getSummarizedFilename( fileName ) + ' is being chain combed.' ) suffixFilename = fileName[ : fileName.rfind( '.' ) ] + '_comb.gcode' combGcode = getCombChainGcode( fileName, '', combPreferences ) if combGcode == '': return gcodec.writeFileText( suffixFilename, combGcode ) print( 'The combed file is saved as ' + gcodec.getSummarizedFilename( suffixFilename ) ) analyze.writeOutput( suffixFilename, combGcode ) print( 'It took ' + str( int( round( time.time() - startTime ) ) ) + ' seconds to comb the file.' ) class CombPreferences: "A class to handle the comb preferences." def __init__( self ): "Set the default preferences, execute title & preferences fileName." #Set the default preferences. self.archive = [] self.activateComb = preferences.BooleanPreference().getFromValue( 'Activate Comb', True ) self.archive.append( self.activateComb ) self.arrivalInsetFollowDistanceOverExtrusionWidth = preferences.FloatPreference().getFromValue( 'Arrival Inset Follow Distance over Extrusion Width (ratio):', 3.0 ) self.archive.append( self.arrivalInsetFollowDistanceOverExtrusionWidth ) self.jitterOverExtrusionWidth = preferences.FloatPreference().getFromValue( 'Jitter Over Extrusion Width (ratio):', 2.0 ) self.archive.append( self.jitterOverExtrusionWidth ) self.minimumPerimeterDepartureDistanceOverExtrusionWidth = preferences.FloatPreference().getFromValue( 'Minimum Perimeter Departure Distance over Extrusion Width (ratio):', 30.0 ) self.archive.append( self.minimumPerimeterDepartureDistanceOverExtrusionWidth ) self.fileNameInput = preferences.Filename().getFromFilename( interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File to be Combed', '' ) self.archive.append( self.fileNameInput ) #Create the archive, title of the execute button, title of the dialog & preferences fileName. self.executeTitle = 'Comb' self.saveTitle = 'Save Preferences' preferences.setHelpPreferencesFileNameTitleWindowPosition( self, 'skeinforge_tools.comb.html' ) def execute( self ): "Comb button has been clicked." fileNames = polyfile.getFileOrDirectoryTypesUnmodifiedGcode( self.fileNameInput.value, interpret.getImportPluginFilenames(), self.fileNameInput.wasCancelled ) for fileName in fileNames: writeOutput( fileName ) class CombSkein: "A class to comb a skein of extrusions." def __init__( self ): self.beforeLoopLocation = None self.betweenTable = {} self.boundaryLoop = None self.fillInset = 0.18 self.isPerimeter = False self.layer = None self.layers = [] self.layerTable = {} self.layerZ = None self.lineIndex = 0 self.lines = None self.nextLayerZ = None self.oldZ = None self.perimeter = None self.pointTable = {} self.initializeMoreParameters() def addGcodeFromThreadZ( self, thread, z ): "Add a gcode thread to the output." if len( thread ) > 0: self.addGcodeMovementZ( self.travelFeedratePerMinute, thread[ 0 ], z ) else: print( "zero length vertex positions array which was skipped over, this should never happen" ) if len( thread ) < 2: return self.addLine( 'M101' ) self.addGcodePathZ( self.feedrateMinute, thread[ 1 : ], z ) def addGcodeMovementZ( self, feedratePerMinute, point, z ): "Add a movement to the output." if feedratePerMinute == None: feedratePerMinute = self.operatingFeedratePerMinute self.addLine( "G1 X%s Y%s Z%s F%s" % ( self.getRounded( point.real ), self.getRounded( point.imag ), self.getRounded( z ), self.getRounded( feedratePerMinute ) ) ) def addGcodePathZ( self, feedratePerMinute, path, z ): "Add a gcode path, without modifying the extruder, to the output." for point in path: self.addGcodeMovementZ( feedratePerMinute, point, z ) def addIfTravel( self, splitLine ): "Add travel move around loops if the extruder is off." location = gcodec.getLocationFromSplitLine( self.oldLocation, splitLine ) if not self.extruderActive and self.oldLocation != None: if len( self.getBetweens() ) > 0: highestZ = max( location.z, self.oldLocation.z ) self.addGcodePathZ( self.travelFeedratePerMinute, self.getAroundBetweenPath( location ), highestZ ) self.oldLocation = location def addLine( self, line ): "Add a line of text and a newline to the output." self.output.write( line + "\n" ) def addPathBeforeEnd( self, aroundBetweenPath, location, loop ): "Add the path before the end of the loop." halfFillInset = 0.5 * self.fillInset if self.arrivalInsetFollowDistance < halfFillInset: return locationComplex = location.dropAxis( 2 ) closestInset = None closestDistanceIndex = euclidean.DistanceIndex( 999999999999999999.0, - 1 ) loop = euclidean.getAwayPoints( loop, self.extrusionWidth ) circleNodes = intercircle.getCircleNodesFromLoop( loop, self.fillInset ) centers = [] centers = intercircle.getCentersFromCircleNodes( circleNodes ) for center in centers: inset = intercircle.getInsetFromClockwiseLoop( center, halfFillInset ) if euclidean.isLargeSameDirection( inset, center, self.fillInset ): if euclidean.isPathInsideLoop( loop, inset ) == euclidean.isWiddershins( loop ): distanceIndex = euclidean.getNearestDistanceIndex( locationComplex, inset ) if distanceIndex.distance < closestDistanceIndex.distance: closestInset = inset closestDistanceIndex = distanceIndex if closestInset == None: return extrusionHalfWidth = 0.5 * self.extrusionWidth closestInset = euclidean.getLoopStartingNearest( extrusionHalfWidth, locationComplex, closestInset ) if euclidean.getPolygonLength( closestInset ) < 0.2 * self.arrivalInsetFollowDistance: return closestInset.append( closestInset[ 0 ] ) closestInset = euclidean.getSimplifiedPath( closestInset, self.extrusionWidth ) closestInset.reverse() pathBeforeArrival = euclidean.getClippedAtEndLoopPath( self.arrivalInsetFollowDistance, closestInset ) pointBeforeArrival = pathBeforeArrival[ - 1 ] aroundBetweenPath.append( pointBeforeArrival ) if self.arrivalInsetFollowDistance <= halfFillInset: return aroundBetweenPath += euclidean.getClippedAtEndLoopPath( halfFillInset, closestInset )[ len( pathBeforeArrival ) - 1 : ] def addPathBetween( self, aroundBetweenPath, betweenFirst, betweenSecond, isLeavingPerimeter, loopFirst ): "Add a path between the perimeter and the fill." clockwisePath = [ betweenFirst ] widdershinsPath = [ betweenFirst ] nearestFirstDistanceIndex = euclidean.getNearestDistanceIndex( betweenFirst, loopFirst ) nearestSecondDistanceIndex = euclidean.getNearestDistanceIndex( betweenSecond, loopFirst ) firstBeginIndex = ( nearestFirstDistanceIndex.index + 1 ) % len( loopFirst ) secondBeginIndex = ( nearestSecondDistanceIndex.index + 1 ) % len( loopFirst ) loopBeforeLeaving = euclidean.getAroundLoop( firstBeginIndex, firstBeginIndex, loopFirst ) if nearestFirstDistanceIndex.index == nearestSecondDistanceIndex.index: nearestPoint = euclidean.getNearestPointOnSegment( loopFirst[ nearestSecondDistanceIndex.index ], loopFirst[ secondBeginIndex ], betweenSecond ) widdershinsPath += [ nearestPoint ] clockwisePath += [ nearestPoint ] if euclidean.getPathLength( widdershinsPath ) < self.minimumPerimeterDepartureDistance: widdershinsPath = [ betweenFirst ] + loopBeforeLeaving + [ nearestPoint ] reversedLoop = loopBeforeLeaving[ : ] reversedLoop.reverse() clockwisePath = [ betweenFirst ] + reversedLoop + [ nearestPoint ] else: widdershinsLoop = euclidean.getAroundLoop( firstBeginIndex, secondBeginIndex, loopFirst ) widdershinsPath += widdershinsLoop clockwiseLoop = euclidean.getAroundLoop( secondBeginIndex, firstBeginIndex, loopFirst ) clockwiseLoop.reverse() clockwisePath += clockwiseLoop clockwisePath.append( betweenSecond ) widdershinsPath.append( betweenSecond ) if euclidean.getPathLength( widdershinsPath ) > euclidean.getPathLength( clockwisePath ): loopBeforeLeaving.reverse() widdershinsPath = clockwisePath if isLeavingPerimeter: totalDistance = euclidean.getPathLength( widdershinsPath ) loopLength = euclidean.getPolygonLength( loopBeforeLeaving ) while totalDistance < self.minimumPerimeterDepartureDistance: widdershinsPath = [ betweenFirst ] + loopBeforeLeaving + widdershinsPath[ 1 : ] totalDistance += loopLength aroundBetweenPath += widdershinsPath def addTailoredLoopPath( self ): "Add a clipped and jittered loop path." loop = self.loopPath.path[ : - 1 ] jitterDistance = self.layerJitter + self.arrivalInsetFollowDistance if self.beforeLoopLocation != None: extrusionHalfWidth = 0.5 * self.extrusionWidth loop = euclidean.getLoopStartingNearest( extrusionHalfWidth, self.beforeLoopLocation, loop ) if jitterDistance != 0.0: loop = self.getJitteredLoop( jitterDistance, loop ) loop = euclidean.getAwayPoints( loop, 0.2 * self.fillInset ) self.loopPath.path = loop + [ loop[ 0 ] ] self.addGcodeFromThreadZ( self.loopPath.path, self.loopPath.z ) self.loopPath = None def addToLoop( self, location ): "Add a location to loop." if self.layer == None: if not self.oldZ in self.layerTable: self.layerTable[ self.oldZ ] = [] self.layer = self.layerTable[ self.oldZ ] if self.boundaryLoop == None: self.boundaryLoop = [] #starting with an empty array because a closed loop does not have to restate its beginning self.layer.append( self.boundaryLoop ) if self.boundaryLoop != None: self.boundaryLoop.append( location.dropAxis( 2 ) ) def getAroundBetweenPath( self, location ): "Insert paths around and between the perimeter and the fill." aroundBetweenPath = [] outerPerimeter = None if str( location ) in self.pointTable: perimeter = self.pointTable[ str( location ) ] if euclidean.isWiddershins( perimeter ): outerPerimeter = perimeter nextBeginning = self.getOutloopLocation( location ) pathEnd = self.getOutloopLocation( self.oldLocation ) self.insertPathsBetween( aroundBetweenPath, nextBeginning, pathEnd ) if outerPerimeter != None: self.addPathBeforeEnd( aroundBetweenPath, location, outerPerimeter ) return aroundBetweenPath def getBetweens( self ): "Set betweens for the layer." if self.layerZ in self.betweenTable: return self.betweenTable[ self.layerZ ] if self.layerZ not in self.layerTable: return [] halfFillInset = 0.5 * self.fillInset betweens = [] for boundaryLoop in self.layerTable[ self.layerZ ]: circleNodes = intercircle.getCircleNodesFromLoop( boundaryLoop, self.fillInset ) centers = intercircle.getCentersFromCircleNodes( circleNodes ) for center in centers: inset = intercircle.getSimplifiedInsetFromClockwiseLoop( center, halfFillInset ) if euclidean.isLargeSameDirection( inset, center, self.fillInset ): if euclidean.isPathInsideLoop( boundaryLoop, inset ) == euclidean.isWiddershins( boundaryLoop ): betweens.append( inset ) self.betweenTable[ self.layerZ ] = betweens return betweens def getJitteredLoop( self, jitterDistance, jitterLoop ): "Get a jittered loop path." loopLength = euclidean.getPolygonLength( jitterLoop ) lastLength = 0.0 pointIndex = 0 totalLength = 0.0 jitterPosition = ( jitterDistance + 256.0 * loopLength ) % loopLength while totalLength < jitterPosition and pointIndex < len( jitterLoop ): firstPoint = jitterLoop[ pointIndex ] secondPoint = jitterLoop[ ( pointIndex + 1 ) % len( jitterLoop ) ] pointIndex += 1 lastLength = totalLength totalLength += abs( firstPoint - secondPoint ) remainingLength = jitterPosition - lastLength pointIndex = pointIndex % len( jitterLoop ) ultimateJitteredPoint = jitterLoop[ pointIndex ] penultimateJitteredPointIndex = ( pointIndex + len( jitterLoop ) - 1 ) % len( jitterLoop ) penultimateJitteredPoint = jitterLoop[ penultimateJitteredPointIndex ] segment = ultimateJitteredPoint - penultimateJitteredPoint segmentLength = abs( segment ) originalOffsetLoop = euclidean.getAroundLoop( pointIndex, pointIndex, jitterLoop ) if segmentLength <= 0.0: return [ penultimateJitteredPoint ] + originalOffsetLoop[ - 1 ] newUltimatePoint = penultimateJitteredPoint + segment * remainingLength / segmentLength return [ newUltimatePoint ] + originalOffsetLoop def getOutloopLocation( self, point ): "Get location outside of loop." pointComplex = point.dropAxis( 2 ) if str( point ) not in self.pointTable: return pointComplex closestBetween = None closestDistanceIndex = euclidean.DistanceIndex( 999999999999999999.0, - 1 ) for between in self.getBetweens(): distanceIndex = euclidean.getNearestDistanceIndex( pointComplex, between ) if distanceIndex.distance < closestDistanceIndex.distance: closestBetween = between closestDistanceIndex = distanceIndex if closestBetween == None: print( 'This should never happen, closestBetween should always exist.' ) print( point ) print( self.getBetweens() ) return pointComplex closestIndex = closestDistanceIndex.index segmentBegin = closestBetween[ closestIndex ] segmentEnd = closestBetween[ ( closestIndex + 1 ) % len( closestBetween ) ] nearestPoint = euclidean.getNearestPointOnSegment( segmentBegin, segmentEnd, pointComplex ) distanceToNearestPoint = abs( pointComplex - nearestPoint ) nearestMinusOld = 1.5 * ( nearestPoint - pointComplex ) return pointComplex + nearestMinusOld def getRounded( self, number ): "Get number rounded to the number of carried decimal places as a string." return euclidean.getRoundedToDecimalPlacesString( self.decimalPlacesCarried, number ) def getStartIndex( self, xIntersections ): "Get the start index of the intersections." startIndex = 0 while startIndex < len( xIntersections ) - 1: xIntersectionFirst = xIntersections[ startIndex ] xIntersectionSecond = xIntersections[ startIndex + 1 ] loopFirst = self.getBetweens()[ xIntersectionFirst.index ] loopSecond = self.getBetweens()[ xIntersectionSecond.index ] if loopFirst == loopSecond: return startIndex % 2 startIndex += 1 return 0 def initializeMoreParameters( self ): "Add a movement to the output." self.extruderActive = False self.feedrateMinute = None self.isLoopPerimeter = False self.layerGolden = 0.0 self.loopPath = None self.oldLocation = None self.output = cStringIO.StringIO() def insertPathsBetween( self, aroundBetweenPath, nextBeginning, pathEnd ): "Insert paths between the perimeter and the fill." betweenX = [] switchX = [] segment = euclidean.getNormalized( nextBeginning - pathEnd ) segmentYMirror = complex( segment.real, - segment.imag ) pathEndRotated = segmentYMirror * pathEnd nextBeginningRotated = segmentYMirror * nextBeginning y = pathEndRotated.imag for betweenIndex in xrange( len( self.getBetweens() ) ): between = self.getBetweens()[ betweenIndex ] betweenRotated = euclidean.getPointsRoundZAxis( segmentYMirror, between ) euclidean.addXIntersectionIndexes( betweenRotated, betweenIndex, switchX, y ) switchX.sort() maximumX = max( pathEndRotated.real, nextBeginningRotated.real ) minimumX = min( pathEndRotated.real, nextBeginningRotated.real ) for xIntersection in switchX: if xIntersection.x > minimumX and xIntersection.x < maximumX: betweenX.append( xIntersection ) betweenXIndex = self.getStartIndex( betweenX ) while betweenXIndex < len( betweenX ) - 1: betweenXFirst = betweenX[ betweenXIndex ] betweenXSecond = betweenX[ betweenXIndex + 1 ] loopFirst = self.getBetweens()[ betweenXFirst.index ] betweenFirst = segment * complex( betweenXFirst.x, y ) betweenSecond = segment * complex( betweenXSecond.x, y ) isLeavingPerimeter = False if betweenXSecond.index != betweenXFirst.index: isLeavingPerimeter = True self.addPathBetween( aroundBetweenPath, betweenFirst, betweenSecond, isLeavingPerimeter, loopFirst ) betweenXIndex += 2 def isNextExtruderOn( self ): "Determine if there is an extruder on command before a move command." line = self.lines[ self.lineIndex ] splitLine = line.split() for afterIndex in xrange( self.lineIndex + 1, len( self.lines ) ): line = self.lines[ afterIndex ] splitLine = line.split() firstWord = gcodec.getFirstWord( splitLine ) if firstWord == 'G1' or firstWord == 'M103': return False elif firstWord == 'M101': return True return False def linearMove( self, splitLine ): "Add to loop path if this is a loop or path." location = gcodec.getLocationFromSplitLine( self.oldLocation, splitLine ) self.feedrateMinute = gcodec.getFeedrateMinute( self.feedrateMinute, splitLine ) if self.isLoopPerimeter: if self.isNextExtruderOn(): self.loopPath = euclidean.PathZ( location.z ) if self.oldLocation != None: self.beforeLoopLocation = self.oldLocation.dropAxis( 2 ) if self.loopPath != None: self.loopPath.path.append( location.dropAxis( 2 ) ) self.oldLocation = location def parseAddJitter( self, line ): "Parse a gcode line, jitter it and add it to the comb skein." splitLine = line.split() if len( splitLine ) < 1: return firstWord = splitLine[ 0 ] if firstWord == 'G1': self.linearMove( splitLine ) elif firstWord == 'M103': self.isLoopPerimeter = False if self.loopPath != None: self.addTailoredLoopPath() elif firstWord == '(<layer>': self.layerGolden += 0.61803398874989479 self.layerJitter = self.jitter * ( math.fmod( self.layerGolden, 1.0 ) - 0.5 ) elif firstWord == '(<loop>)' or firstWord == '(<perimeter>)': self.isLoopPerimeter = True if self.loopPath == None: self.addLine( line ) def parseAddTravel( self, line ): "Parse a gcode line and add it to the comb skein." splitLine = line.split() if len( splitLine ) < 1: return firstWord = splitLine[ 0 ] if firstWord == 'G1': self.addIfTravel( splitLine ) self.layerZ = self.nextLayerZ elif firstWord == 'M101': self.extruderActive = True elif firstWord == 'M103': self.extruderActive = False elif firstWord == '(<layer>': self.nextLayerZ = float( splitLine[ 1 ] ) if self.layerZ == None: self.layerZ = self.nextLayerZ self.addLine( line ) def parseGcode( self, combPreferences, gcodeText ): "Parse gcode text and store the comb gcode." self.lines = gcodec.getTextLines( gcodeText ) self.parseInitialization( combPreferences ) for self.lineIndex in xrange( self.lineIndex, len( self.lines ) ): line = self.lines[ self.lineIndex ] self.parseAddJitter( line ) self.lines = gcodec.getTextLines( self.output.getvalue() ) self.initializeMoreParameters() for self.lineIndex in xrange( len( self.lines ) ): line = self.lines[ self.lineIndex ] self.parseLine( combPreferences, line ) for self.lineIndex in xrange( len( self.lines ) ): line = self.lines[ self.lineIndex ] self.parseAddTravel( line ) def parseInitialization( self, combPreferences ): "Parse gcode initialization and store the parameters." for self.lineIndex in xrange( len( self.lines ) ): line = self.lines[ self.lineIndex ] splitLine = line.split() firstWord = gcodec.getFirstWord( splitLine ) if firstWord == '(<decimalPlacesCarried>': self.decimalPlacesCarried = int( splitLine[ 1 ] ) elif firstWord == '(</extruderInitialization>)': self.addLine( '(<procedureDone> comb </procedureDone>)' ) return elif firstWord == '(<extrusionWidth>': self.extrusionWidth = float( splitLine[ 1 ] ) self.arrivalInsetFollowDistance = combPreferences.arrivalInsetFollowDistanceOverExtrusionWidth.value * self.extrusionWidth self.jitter = combPreferences.jitterOverExtrusionWidth.value * self.extrusionWidth self.minimumPerimeterDepartureDistance = combPreferences.minimumPerimeterDepartureDistanceOverExtrusionWidth.value * self.extrusionWidth elif firstWord == '(<operatingFeedratePerSecond>': self.operatingFeedratePerMinute = 60.0 * float( splitLine[ 1 ] ) elif firstWord == '(<fillInset>': self.fillInset = float( splitLine[ 1 ] ) elif firstWord == '(<travelFeedratePerSecond>': self.travelFeedratePerMinute = 60.0 * float( splitLine[ 1 ] ) self.addLine( line ) def parseLine( self, combPreferences, line ): "Parse a gcode line." splitLine = line.split() if len( splitLine ) < 1: return firstWord = splitLine[ 0 ] if firstWord == 'G1': if self.isPerimeter: location = gcodec.getLocationFromSplitLine( None, splitLine ) self.pointTable[ str( location ) ] = self.boundaryLoop elif firstWord == 'M103': self.boundaryLoop = None self.isPerimeter = False elif firstWord == '(<boundaryPoint>': location = gcodec.getLocationFromSplitLine( None, splitLine ) self.addToLoop( location ) elif firstWord == '(<layer>': self.boundaryLoop = None self.layer = None self.oldZ = float( splitLine[ 1 ] ) elif firstWord == '(<perimeter>)': self.isPerimeter = True def main( hashtable = None ): "Display the comb dialog." if len( sys.argv ) > 1: writeOutput( ' '.join( sys.argv[ 1 : ] ) ) else: preferences.displayDialog( CombPreferences() ) if __name__ == "__main__": main()