#! /usr/bin/env python """ This page is in the table of contents. Dimension adds Adrian's extruder distance E value to the gcode movement lines, as described at: http://blog.reprap.org/2009/05/4d-printing.html and in Erik de Bruijn's conversion script page at: http://objects.reprap.org/wiki/3D-to-5D-Gcode.php The dimension manual page is at: http://www.bitsfrombytes.com/wiki/index.php?title=Skeinforge_Dimension ==Operation== 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. ==Settings== ===Extrusion Distance Format Choice=== Default is 'Relative Extrusion Distance'. In Adrian's description the distance is absolute, but since the relative distances are smaller than the cumulative absolute distances, I chose to make the default relative. ====Absolute Extrusion Distance==== When selected, the extrusion distance output will be the total extrusion distance to that gcode line. ====Relative Extrusion Distance==== When selected, the extrusion distance output will be the extrusion distance from the last gcode line. ==Examples== 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. > python dimension.py This brings up the dimension dialog. > python dimension.py Screw Holder Bottom.stl The dimension tool is parsing the file: Screw Holder Bottom.stl .. The dimension tool has created the file: .. Screw Holder Bottom_dimension.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 dimension >>> dimension.main() This brings up the dimension dialog. >>> dimension.writeOutput( 'Screw Holder Bottom.stl' ) The dimension tool is parsing the file: Screw Holder Bottom.stl .. The dimension tool has created the file: .. Screw Holder Bottom_dimension.gcode """ from __future__ import absolute_import try: import psyco psyco.full() except: pass #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 datetime import date from skeinforge_tools import profile from skeinforge_tools.meta_plugins import polyfile from skeinforge_tools.skeinforge_utilities import consecution 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 interpret from skeinforge_tools.skeinforge_utilities import settings import math import os import sys __author__ = "Enrique Perez (perez_enrique@yahoo.com)" __date__ = "$Date: 2008/28/04 $" __license__ = "GPL 3.0" def getCraftedText( fileName, gcodeText = '', repository = None ): "Dimension a gcode file or text." return getCraftedTextFromText( gcodec.getTextIfEmpty( fileName, gcodeText ), repository ) def getCraftedTextFromText( gcodeText, repository = None ): "Dimension a gcode text." if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'dimension' ): return gcodeText if repository == None: repository = settings.getReadRepository( DimensionRepository() ) if not repository.activateDimension.value: return gcodeText return DimensionSkein().getCraftedGcode( gcodeText, repository ) def getNewRepository(): "Get the repository constructor." return DimensionRepository() def writeOutput( fileName = '' ): "Dimension a gcode file." fileName = interpret.getFirstTranslatorFileNameUnmodified( fileName ) if fileName != '': consecution.writeChainTextWithNounMessage( fileName, 'dimension' ) class DimensionRepository: "A class to handle the dimension settings." def __init__( self ): "Set the default settings, execute title & settings fileName." profile.addListsToCraftTypeRepository( 'skeinforge_tools.craft_plugins.dimension.html', self ) self.fileNameInput = settings.FileNameInput().getFromFileName( interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Dimension', self, '' ) self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute( 'http://www.bitsfrombytes.com/wiki/index.php?title=Skeinforge_Dimension' ) self.activateDimension = settings.BooleanSetting().getFromValue( 'Activate Dimension', self, False ) extrusionDistanceFormatLatentStringVar = settings.LatentStringVar() self.extrusionDistanceFormatChoiceLabel = settings.LabelDisplay().getFromName( 'Extrusion Distance Format Choice: ', self ) settings.Radio().getFromRadio( extrusionDistanceFormatLatentStringVar, 'Absolute Extrusion Distance', self, False ) self.relativeExtrusionDistance = settings.Radio().getFromRadio( extrusionDistanceFormatLatentStringVar, 'Relative Extrusion Distance', self, True ) self.executeTitle = 'Dimension' def execute( self ): "Dimension button has been clicked." fileNames = polyfile.getFileOrDirectoryTypesUnmodifiedGcode( self.fileNameInput.value, interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled ) for fileName in fileNames: writeOutput( fileName ) class DimensionSkein: "A class to dimension a skein of extrusions." def __init__( self ): self.distanceFeedRate = gcodec.DistanceFeedRate() self.feedRateMinute = 958.0 self.isExtruderActive = False self.lineIndex = 0 self.oldLocation = None self.operatingFeedRate = None self.operatingFlowRate = None self.totalExtrusionDistance = 0.0 def getCraftedGcode( self, gcodeText, repository ): "Parse gcode text and store the dimension gcode." self.repository = repository self.lines = gcodec.getTextLines( gcodeText ) self.parseInitialization() if self.operatingFlowRate == None: print( 'There is no operatingFlowRate so dimension will do nothing.' ) return gcodeText self.feedOverFlow = self.operatingFeedRate / self.operatingFlowRate for lineIndex in xrange( self.lineIndex, len( self.lines ) ): self.parseLine( lineIndex ) return self.distanceFeedRate.output.getvalue() def getDimensionedArcMovement( self, line, splitLine ): "Get an dimensioned arc movement." if self.oldLocation == None: return line relativeLocation = gcodec.getLocationFromSplitLine( self.oldLocation, splitLine ) location = self.oldLocation + relativeLocation self.oldLocation = location halfPlaneLineDistance = 0.5 * abs( relativeLocation.dropAxis( 2 ) ) radius = gcodec.getDoubleFromCharacterSplitLine( 'R', splitLine ) if radius == None: relativeCenter = complex( gcodec.getDoubleFromCharacterSplitLine( 'I', splitLine ), gcodec.getDoubleFromCharacterSplitLine( 'J', splitLine ) ) radius = abs( relativeCenter ) angle = 0.0 if radius > 0.0: angle = math.pi if halfPlaneLineDistance < radius: angle = 2.0 * math.asin( halfPlaneLineDistance / radius ) else: angle *= halfPlaneLineDistance / radius deltaZ = abs( relativeLocation.z ) arcDistanceZ = complex( abs( angle ) * radius, relativeLocation.z ) distance = abs( arcDistanceZ ) return line + self.getExtrusionDistanceString( distance, splitLine ) def getDimensionedLinearMovement( self, line, splitLine ): "Get an dimensioned linear movement." distance = 0.0 if self.distanceFeedRate.absoluteDistanceMode: location = gcodec.getLocationFromSplitLine( self.oldLocation, splitLine ) if self.oldLocation != None: distance = abs( location - self.oldLocation ) self.oldLocation = location else: if self.oldLocation == None: print( 'Warning: There was no absolute location when the G91 command was parsed, so the absolute location will be set to the origin.' ) self.oldLocation = Vector3() location = gcodec.getLocationFromSplitLine( None, splitLine ) distance = abs( location ) self.oldLocation += location return line + self.getExtrusionDistanceString( distance, splitLine ) def getExtrusionDistanceString( self, distance, splitLine ): "Get the extrusion distance string." self.feedRateMinute = gcodec.getFeedRateMinute( self.feedRateMinute, splitLine ) if not self.isExtruderActive: return '' if distance <= 0.0: return '' extrusionDistance = self.feedOverFlow * self.flowRate / self.feedRateMinute * distance if self.repository.relativeExtrusionDistance.value: return ' E' + self.distanceFeedRate.getRounded( extrusionDistance ) self.totalExtrusionDistance += extrusionDistance return ' E' + self.distanceFeedRate.getRounded( self.totalExtrusionDistance ) def parseInitialization( self ): "Parse gcode initialization and store the parameters." for self.lineIndex in xrange( len( self.lines ) ): line = self.lines[ self.lineIndex ] splitLine = gcodec.getSplitLineBeforeBracketSemicolon( line ) firstWord = gcodec.getFirstWord( splitLine ) self.distanceFeedRate.parseSplitLine( firstWord, splitLine ) if firstWord == '()': self.distanceFeedRate.addLine( '( dimension )' ) return elif firstWord == '(': self.operatingFeedRate = 60.0 * float( splitLine[ 1 ] ) elif firstWord == '(': self.operatingFlowRate = float( splitLine[ 1 ] ) self.flowRate = self.operatingFlowRate self.distanceFeedRate.addLine( line ) def parseLine( self, lineIndex ): "Parse a gcode line and add it to the dimension skein." line = self.lines[ lineIndex ].lstrip() splitLine = line.split() if len( splitLine ) < 1: return firstWord = splitLine[ 0 ] if firstWord == 'G2' or firstWord == 'G3': line = self.getDimensionedArcMovement( line, splitLine ) if firstWord == 'G1': line = self.getDimensionedLinearMovement( line, splitLine ) elif firstWord == 'M101': self.isExtruderActive = True elif firstWord == 'M103': self.isExtruderActive = False elif firstWord == 'M108': self.flowRate = float( splitLine[ 1 ][ 1 : ] ) self.distanceFeedRate.addLine( line ) def main(): "Display the dimension dialog." if len( sys.argv ) > 1: writeOutput( ' '.join( sys.argv[ 1 : ] ) ) else: settings.startMainLoopFromConstructor( getNewRepository() ) if __name__ == "__main__": main()