From: daid Date: Tue, 10 Jan 2012 16:03:14 +0000 (+0100) Subject: Updated README to explain a bit what these files are X-Git-Tag: RC1~182 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=commitdiff_plain;h=5c3c45a51f11a37dcb23b4182ef73e5c1e5bf09e;p=cura.git Updated README to explain a bit what these files are Added patch for SF46 --- diff --git a/README b/README index e9a5014e..9d8cdf73 100644 --- a/README +++ b/README @@ -3,6 +3,12 @@ The code consists out of a few patches and a build script. The final result is a release package for Windows which should be ready to run without any additional requirements. +====What is THIS?==== +READ THIS! PLEASE! + +These source files are just patch files on skeinforge. If you want to use this don't look at the source files. Look at the download section in github at: +https://github.com/daid/Skeinforge_PyPy/downloads + ====What is changed==== * All: Do not show settings when ran from command line * All: Run PyPy to slice a model instead of normal python diff --git a/patches/46 b/patches/46 new file mode 100644 index 00000000..e80b8ede --- /dev/null +++ b/patches/46 @@ -0,0 +1,5201 @@ +--- ori/46/fabmetheus_utilities/archive.py ++++ target/SF46/fabmetheus_utilities/archive.py +@@ -18,7 +18,7 @@ + __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' + + +-globalTemporarySettingsPath = os.path.join(os.path.expanduser('~'), '.skeinforge') ++globalTemporarySettingsPath = os.path.join(os.path.expanduser('~'), '.skeinforge_pypy') + + + def addToNamePathDictionary(directoryPath, namePathDictionary): +--- ori/46/fabmetheus_utilities/settings.py ++++ target/SF46/fabmetheus_utilities/settings.py +@@ -289,7 +289,7 @@ + if repository.baseNameSynonym != None: + text = archive.getFileText(archive.getProfilesPath(getProfileBaseNameSynonym(repository)), False) + if text == '': +- print('The default %s will be written in the .skeinforge folder in the home directory.' % repository.title.lower() ) ++ print('The default %s will be written in the .skeinforge_pypy folder in the home directory.' % repository.title.lower() ) + text = archive.getFileText(getProfilesDirectoryInAboveDirectory(getProfileBaseName(repository)), False) + if text != '': + readSettingsFromText(repository, text) +--- ori/46/skeinforge_application/alterations/end.gcode ++++ target/SF46/skeinforge_application/alterations/end.gcode +@@ -0,0 +1,10 @@ ++(start of end.gcode) ++M104 S0 (extruder heat off) ++M106 (fan on) ++G91 (relative positioning) ++G1 Z+10 E-5 F400 (move Z up a bit and retract filament by 5mm) ++G1 X-20 Y-20 F1500 (move X and Y over a bit) ++M84 (steppers off) ++G90 (absolute positioning) ++(end of end.gcode) ++ +--- ori/46/skeinforge_application/alterations/start.gcode ++++ target/SF46/skeinforge_application/alterations/start.gcode +@@ -0,0 +1,31 @@ ++(start of start.txt) ++M92 E926.5 (the number of extruder steps to take in 1mm of filament) ++G21 (metric values) ++G21 ++G21 (all the extra G21 commands are comments - skeinforge eats lines without a gcode) ++G21 ++G90 (absolute positioning) ++G21 ++G28 X0 Y0 (move X/Y to min endstops) ++G28 Z0 (move Z to min endstops) ++G21 ++G21 ( if your prints start too high, try changing the Z0.0 below ) ++G21 ( to Z1.0 - the number after the Z is the actual, physical ) ++G21 ( height of the nozzle in mm. This can take some messing around ) ++G21 ( with to get just right... ) ++G21 ++G92 X0 Y0 Z0 E0 (reset software position to front/left/z=0.0) ++G21 ++G1 Z15.0 F400 (move the platform down 15mm) ++G92 E0 (zero the extruded length) ++G21 ++G1 F75 E5 (extrude 5mm of feed stock) ++G1 F75 E3.5 (reverse feed stock by 1.5mm) ++G92 E0 (zero the extruded length again) ++G21 ++M1 (Clean the nozzle then press YES to continue...) ++G21 ++G1 X100 Y100 F3500 (go to the middle of the platform) ++G1 Z0.0 F400 (back to Z=0 and start the print!) ++(end of start.txt) ++ +--- ori/46/skeinforge_application/skeinforge_plugins/analyze_plugins/skeinlayer.py ++++ target/SF46/skeinforge_application/skeinforge_plugins/analyze_plugins/skeinlayer.py +@@ -200,7 +200,7 @@ + self.baseNameSynonym = 'skeinview.csv' + self.fileNameInput = settings.FileNameInput().getFromFileName( [ ('Gcode text files', '*.gcode') ], 'Open File for Skeinlayer', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Skeinlayer') +- self.activateSkeinlayer = settings.BooleanSetting().getFromValue('Activate Skeinlayer', self, True ) ++ self.activateSkeinlayer = settings.BooleanSetting().getFromValue('Activate Skeinlayer', self, False ) + self.addAnimation() + self.drawArrows = settings.BooleanSetting().getFromValue('Draw Arrows', self, True ) + self.goAroundExtruderOffTravel = settings.BooleanSetting().getFromValue('Go Around Extruder Off Travel', self, False ) +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/carve.py ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/carve.py +@@ -163,7 +163,7 @@ + self.addLayerTemplateToSVG = settings.BooleanSetting().getFromValue('Add Layer Template to SVG', self, True) + self.extraDecimalPlaces = settings.FloatSpin().getFromValue(0.0, 'Extra Decimal Places (float):', self, 3.0, 2.0) + self.importCoarseness = settings.FloatSpin().getFromValue( 0.5, 'Import Coarseness (ratio):', self, 2.0, 1.0 ) +- self.layerThickness = settings.FloatSpin().getFromValue( 0.1, 'Layer Thickness (mm):', self, 1.0, 0.4 ) ++ self.layerThickness = settings.FloatSpin().getFromValue( 0.1, 'Layer Thickness (mm):', self, 1.0, 0.2 ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Layers -', self ) + self.layersFrom = settings.IntSpin().getFromValue( 0, 'Layers From (index):', self, 20, 0 ) +@@ -173,7 +173,7 @@ + importLatentStringVar = settings.LatentStringVar() + self.correctMesh = settings.Radio().getFromRadio( importLatentStringVar, 'Correct Mesh', self, True ) + self.unprovenMesh = settings.Radio().getFromRadio( importLatentStringVar, 'Unproven Mesh', self, False ) +- self.perimeterWidthOverThickness = settings.FloatSpin().getFromValue( 1.4, 'Perimeter Width over Thickness (ratio):', self, 2.2, 1.8 ) ++ self.perimeterWidth = settings.FloatSpin().getFromValue( 0.1, 'Perimeter Width:', self, 2.2, 0.4 ) + self.svgViewer = settings.StringSetting().getFromValue('SVG Viewer:', self, 'webbrowser') + settings.LabelSeparator().getFromRepository(self) + self.executeTitle = 'Carve' +@@ -190,7 +190,7 @@ + def getCarvedSVG(self, carving, fileName, repository): + "Parse gnu triangulated surface text and store the carved gcode." + layerThickness = repository.layerThickness.value +- perimeterWidth = repository.perimeterWidthOverThickness.value * layerThickness ++ perimeterWidth = repository.perimeterWidth.value + carving.setCarveLayerThickness(layerThickness) + importRadius = 0.5 * repository.importCoarseness.value * abs(perimeterWidth) + carving.setCarveImportRadius(max(importRadius, 0.001 * layerThickness)) +@@ -201,7 +201,7 @@ + return '' + layerThickness = carving.getCarveLayerThickness() + decimalPlacesCarried = euclidean.getDecimalPlacesCarried(repository.extraDecimalPlaces.value, layerThickness) +- perimeterWidth = repository.perimeterWidthOverThickness.value * layerThickness ++ perimeterWidth = repository.perimeterWidth.value + svgWriter = svg_writer.SVGWriter( + repository.addLayerTemplateToSVG.value, + carving.getCarveCornerMaximum(), +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/carve.py.orig ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/carve.py.orig +@@ -0,0 +1,224 @@ ++""" ++This page is in the table of contents. ++Carve is the most important plugin to define for your printer. ++ ++It carves a shape into svg slice layers. It also sets the layer thickness and perimeter width for the rest of the tool chain. ++ ++The carve manual page is at: ++http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Carve ++ ++On the Arcol Blog a method of deriving the layer thickness is posted. That article "Machine Calibrating" is at: ++http://blog.arcol.hu/?p=157 ++ ++==Settings== ++===Add Layer Template to SVG=== ++Default is on. ++ ++When selected, the layer template will be added to the svg output, which adds javascript control boxes. So 'Add Layer Template to SVG' should be selected when the svg will be viewed in a browser. ++ ++When off, no controls will be added, the svg output will only include the fabrication paths. So 'Add Layer Template to SVG' should be deselected when the svg will be used by other software, like Inkscape. ++ ++===Extra Decimal Places=== ++Default is two. ++ ++Defines the number of extra decimal places export will output compared to the number of decimal places in the layer thickness. The higher the 'Extra Decimal Places', the more significant figures the output numbers will have. ++ ++===Import Coarseness=== ++Default is one. ++ ++When a triangle mesh has holes in it, the triangle mesh slicer switches over to a slow algorithm that spans gaps in the mesh. The higher the 'Import Coarseness' setting, the wider the gaps in the mesh it will span. An import coarseness of one means it will span gaps of the perimeter width. ++ ++===Layer Thickness=== ++Default is 0.4 mm. ++ ++Defines the the thickness of the layers skeinforge will cut your object into, in the z direction. This is the most important carve setting, many values in the toolchain are derived from the layer thickness. ++ ++For a 0.5 mm nozzle usable values are 0.3 mm to 0.5 mm. Note; if you are using thinner layers make sure to adjust the extrusion speed as well. ++ ++===Layers=== ++Carve slices from bottom to top. To get a single layer, set the "Layers From" to zero and the "Layers To" to one. The 'Layers From' until 'Layers To' range is a python slice. ++ ++====Layers From==== ++Default is zero. ++ ++Defines the index of the bottom layer that will be carved. If the 'Layers From' is the default zero, the carving will start from the lowest layer. If the 'Layers From' index is negative, then the carving will start from the 'Layers From' index below the top layer. ++ ++For example if your object is 5 mm tall and your layer thicknes is 1 mm if you set layers from to 3 you will ignore the first 3 mm and start from 3 mm. ++ ++====Layers To==== ++Default is a huge number, which will be limited to the highest index layer. ++ ++Defines the index of the top layer that will be carved. If the 'Layers To' index is a huge number like the default, the carving will go to the top of the model. If the 'Layers To' index is negative, then the carving will go to the 'Layers To' index below the top layer. ++ ++This is the same as layers from, only it defines when to end the generation of gcode. ++ ++===Mesh Type=== ++Default is 'Correct Mesh'. ++ ++====Correct Mesh==== ++When selected, the mesh will be accurately carved, and if a hole is found, carve will switch over to the algorithm that spans gaps. ++ ++====Unproven Mesh==== ++When selected, carve will use the gap spanning algorithm from the start. The problem with the gap spanning algothm is that it will span gaps, even if there is not actually a gap in the model. ++ ++===Perimeter Width over Thickness=== ++Default is 1.8. ++ ++Defines the ratio of the extrusion perimeter width to the layer thickness. This parameter tells skeinforge how wide the perimeter wall is expected to be in relation to the layer thickness. Default value of 1.8 for the default layer thickness of 0.4 states that a single filament perimeter wall should be 0.4 mm * 1.8 = 0.72 mm wide. The higher the value the more the perimeter will be inset. A ratio of one means the extrusion is a circle, the default ratio of 1.8 means the extrusion is a wide oval. ++ ++This is an important value because if you are calibrating your machine you need to ensure that the speed of the head and the extrusion rate in combination produce a wall that is 'Layer Thickness' * 'Perimeter Width over Thickness' wide. To start with 'Perimeter Width over Thickness' is probably best left at the default of 1.8 and the extrusion rate adjusted to give the correct calculated wall thickness. ++ ++Adjustment is in the 'Speed' section with 'Feed Rate' controlling speed of the head in X & Y and 'Flow Rate' controlling the extrusion rate. Initially it is probably easier to start adjusting the flow rate only a little at a time until you get a single filament of the correct width. If you change too many parameters at once you can get in a right mess. ++ ++===SVG Viewer=== ++Default is webbrowser. ++ ++If the 'SVG Viewer' is set to the default 'webbrowser', the scalable vector graphics file will be sent to the default browser to be opened. If the 'SVG Viewer' is set to a program name, the scalable vector graphics file will be sent to that program to be opened. ++ ++==Examples== ++The following examples carve the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and carve.py. ++ ++> python carve.py ++This brings up the carve dialog. ++ ++> python carve.py Screw Holder Bottom.stl ++The carve tool is parsing the file: ++Screw Holder Bottom.stl ++.. ++The carve tool has created the file: ++.. Screw Holder Bottom_carve.svg ++ ++""" ++ ++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 fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret ++from fabmetheus_utilities import archive ++from fabmetheus_utilities import euclidean ++from fabmetheus_utilities import gcodec ++from fabmetheus_utilities import settings ++from fabmetheus_utilities import svg_writer ++from skeinforge_application.skeinforge_utilities import skeinforge_polyfile ++from skeinforge_application.skeinforge_utilities import skeinforge_profile ++import math ++import os ++import sys ++import time ++ ++ ++__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' ++__date__ = '$Date: 2008/02/05 $' ++__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' ++ ++ ++def getCraftedText( fileName, gcodeText = '', repository=None): ++ "Get carved text." ++ if fileName.endswith('.svg'): ++ gcodeText = archive.getTextIfEmpty(fileName, gcodeText) ++ if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'carve'): ++ return gcodeText ++ carving = svg_writer.getCarving(fileName) ++ if carving == None: ++ return '' ++ if repository == None: ++ repository = CarveRepository() ++ settings.getReadRepository(repository) ++ return CarveSkein().getCarvedSVG( carving, fileName, repository ) ++ ++def getNewRepository(): ++ 'Get new repository.' ++ return CarveRepository() ++ ++def writeOutput(fileName, shouldAnalyze=True): ++ "Carve a GNU Triangulated Surface file." ++ startTime = time.time() ++ print('File ' + archive.getSummarizedFileName(fileName) + ' is being carved.') ++ repository = CarveRepository() ++ settings.getReadRepository(repository) ++ carveGcode = getCraftedText(fileName, '', repository) ++ if carveGcode == '': ++ return ++ suffixFileName = archive.getFilePathWithUnderscoredBasename(fileName, '_carve.svg') ++ archive.writeFileText(suffixFileName, carveGcode) ++ print('The carved file is saved as ' + archive.getSummarizedFileName(suffixFileName)) ++ print('It took %s to carve the file.' % euclidean.getDurationString(time.time() - startTime)) ++ if shouldAnalyze: ++ settings.openSVGPage(suffixFileName, repository.svgViewer.value) ++ ++ ++class CarveRepository: ++ "A class to handle the carve settings." ++ def __init__(self): ++ "Set the default settings, execute title & settings fileName." ++ skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.carve.html', self ) ++ self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getTranslatorFileTypeTuples(), 'Open File for Carve', self, '') ++ self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Carve') ++ self.addLayerTemplateToSVG = settings.BooleanSetting().getFromValue('Add Layer Template to SVG', self, True) ++ self.extraDecimalPlaces = settings.FloatSpin().getFromValue(0.0, 'Extra Decimal Places (float):', self, 3.0, 2.0) ++ self.importCoarseness = settings.FloatSpin().getFromValue( 0.5, 'Import Coarseness (ratio):', self, 2.0, 1.0 ) ++ self.layerThickness = settings.FloatSpin().getFromValue( 0.1, 'Layer Thickness (mm):', self, 1.0, 0.4 ) ++ settings.LabelSeparator().getFromRepository(self) ++ settings.LabelDisplay().getFromName('- Layers -', self ) ++ self.layersFrom = settings.IntSpin().getFromValue( 0, 'Layers From (index):', self, 20, 0 ) ++ self.layersTo = settings.IntSpin().getSingleIncrementFromValue( 0, 'Layers To (index):', self, 912345678, 912345678 ) ++ settings.LabelSeparator().getFromRepository(self) ++ self.meshTypeLabel = settings.LabelDisplay().getFromName('Mesh Type: ', self ) ++ importLatentStringVar = settings.LatentStringVar() ++ self.correctMesh = settings.Radio().getFromRadio( importLatentStringVar, 'Correct Mesh', self, True ) ++ self.unprovenMesh = settings.Radio().getFromRadio( importLatentStringVar, 'Unproven Mesh', self, False ) ++ self.perimeterWidthOverThickness = settings.FloatSpin().getFromValue( 1.4, 'Perimeter Width over Thickness (ratio):', self, 2.2, 1.8 ) ++ self.svgViewer = settings.StringSetting().getFromValue('SVG Viewer:', self, 'webbrowser') ++ settings.LabelSeparator().getFromRepository(self) ++ self.executeTitle = 'Carve' ++ ++ def execute(self): ++ "Carve button has been clicked." ++ fileNames = skeinforge_polyfile.getFileOrDirectoryTypes(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) ++ for fileName in fileNames: ++ writeOutput(fileName) ++ ++ ++class CarveSkein: ++ "A class to carve a carving." ++ def getCarvedSVG(self, carving, fileName, repository): ++ "Parse gnu triangulated surface text and store the carved gcode." ++ layerThickness = repository.layerThickness.value ++ perimeterWidth = repository.perimeterWidthOverThickness.value * layerThickness ++ carving.setCarveLayerThickness(layerThickness) ++ importRadius = 0.5 * repository.importCoarseness.value * abs(perimeterWidth) ++ carving.setCarveImportRadius(max(importRadius, 0.001 * layerThickness)) ++ carving.setCarveIsCorrectMesh(repository.correctMesh.value) ++ loopLayers = carving.getCarveBoundaryLayers() ++ if len(loopLayers) < 1: ++ print('Warning, there are no slices for the model, this could be because the model is too small for the Layer Thickness.') ++ return '' ++ layerThickness = carving.getCarveLayerThickness() ++ decimalPlacesCarried = euclidean.getDecimalPlacesCarried(repository.extraDecimalPlaces.value, layerThickness) ++ perimeterWidth = repository.perimeterWidthOverThickness.value * layerThickness ++ svgWriter = svg_writer.SVGWriter( ++ repository.addLayerTemplateToSVG.value, ++ carving.getCarveCornerMaximum(), ++ carving.getCarveCornerMinimum(), ++ decimalPlacesCarried, ++ carving.getCarveLayerThickness(), ++ perimeterWidth) ++ truncatedRotatedBoundaryLayers = svg_writer.getTruncatedRotatedBoundaryLayers(loopLayers, repository) ++ return svgWriter.getReplacedSVGTemplate(fileName, truncatedRotatedBoundaryLayers, 'carve', carving.getFabmetheusXML()) ++ ++ ++def main(): ++ "Display the carve dialog." ++ if len(sys.argv) > 1: ++ writeOutput(' '.join(sys.argv[1 :])) ++ else: ++ settings.startMainLoopFromConstructor(getNewRepository()) ++ ++if __name__ == "__main__": ++ main() +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/chamber.py ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/chamber.py +@@ -205,7 +205,7 @@ + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.chamber.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Chamber', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Chamber') +- self.activateChamber = settings.BooleanSetting().getFromValue('Activate Chamber', self, True ) ++ self.activateChamber = settings.BooleanSetting().getFromValue('Activate Chamber', self, False ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Bed -', self ) + self.bedTemperature = settings.FloatSpin().getFromValue(20.0, 'Bed Temperature (Celcius):', self, 90.0, 60.0) +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/chamber.py.orig ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/chamber.py.orig +@@ -0,0 +1,300 @@ ++""" ++This page is in the table of contents. ++Some filaments contract too much and warp the extruded object. To prevent this you have to print the object in a temperature regulated chamber and/or on a temperature regulated bed. The chamber tool allows you to control the bed and chamber temperature and the holding pressure. ++ ++The chamber gcodes are also described at: ++ ++http://reprap.org/wiki/Mendel_User_Manual:_RepRapGCodes ++ ++The chamber manual page is at: ++ ++http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Chamber ++ ++==Operation== ++The default 'Activate Chamber' checkbox is on. When it is on, the functions described below will work, when it is off, nothing will be done. ++ ++==Settings== ++===Bed=== ++The initial bed temperature is defined by 'Bed Temperature'. If the 'Bed Temperature End Change Height' is greater or equal to the 'Bed Temperature Begin Change Height' and the 'Bed Temperature Begin Change Height' is greater or equal to zero, then the temperature will be ramped toward the 'Bed Temperature End'. The ramp will start once the extruder reaches the 'Bed Temperature Begin Change Height', then the bed temperature will approach the 'Bed Temperature End' as the extruder reaches the 'Bed Temperature End Change Height', finally the bed temperature will stay at the 'Bed Temperature End' for the remainder of the build. ++ ++====Bed Temperature==== ++Default: 60C ++ ++Defines the initial print bed temperature in Celcius by adding an M140 command. ++ ++====Bed Temperature Begin Change Height==== ++Default: -1 mm ++ ++Defines the height of the beginning of the temperature ramp. If the 'Bed Temperature End Change Height' is less than zero, the bed temperature will remain at the initial 'Bed Temperature'. ++ ++====Bed Temperature End Change Height==== ++Default: -1 mm ++ ++Defines the height of the end of the temperature ramp. If the 'Bed Temperature End Change Height' is less than zero or less than the 'Bed Temperature Begin Change Height', the bed temperature will remain at the initial 'Bed Temperature'. ++ ++====Bed Temperature End==== ++Default: 20C ++ ++Defines the end bed temperature if there is a temperature ramp. ++ ++===Chamber Temperature=== ++Default: 30C ++ ++Defines the chamber temperature in Celcius by adding an M141 command. ++ ++===Holding Force=== ++Default: 0 ++ ++Defines the holding pressure of a mechanism, like a vacuum table or electromagnet, to hold the bed surface or object, by adding an M142 command. The holding pressure is in bars. For hardware which only has on/off holding, when the holding pressure is zero, turn off holding, when the holding pressure is greater than zero, turn on holding. ++ ++==Heated Beds== ++===Bothacker=== ++A resistor heated aluminum plate by Bothacker: ++ ++http://bothacker.com ++ ++with an article at: ++ ++http://bothacker.com/2009/12/18/heated-build-platform/ ++ ++===Domingo=== ++A heated copper build plate by Domingo: ++ ++http://casainho-emcrepstrap.blogspot.com/ ++ ++with articles at: ++ ++http://casainho-emcrepstrap.blogspot.com/2010/01/first-time-with-pla-testing-it-also-on.html ++ ++http://casainho-emcrepstrap.blogspot.com/2010/01/call-for-helpideas-to-develop-heated.html ++ ++http://casainho-emcrepstrap.blogspot.com/2010/01/new-heated-build-platform.html ++ ++http://casainho-emcrepstrap.blogspot.com/2010/01/no-acrylic-and-instead-kapton-tape-on.html ++ ++http://casainho-emcrepstrap.blogspot.com/2010/01/problems-with-heated-build-platform-and.html ++ ++http://casainho-emcrepstrap.blogspot.com/2010/01/perfect-build-platform.html ++ ++http://casainho-emcrepstrap.blogspot.com/2009/12/almost-no-warp.html ++ ++http://casainho-emcrepstrap.blogspot.com/2009/12/heated-base-plate.html ++ ++===Jmil=== ++A heated build stage by jmil, over at: ++ ++http://www.hive76.org ++ ++with articles at: ++ ++http://www.hive76.org/handling-hot-build-surfaces ++ ++http://www.hive76.org/heated-build-stage-success ++ ++===Metalab=== ++A heated base by the Metalab folks: ++ ++http://reprap.soup.io ++ ++with information at: ++ ++http://reprap.soup.io/?search=heated%20base ++ ++===Nophead=== ++A resistor heated aluminum bed by Nophead: ++ ++http://hydraraptor.blogspot.com ++ ++with articles at: ++ ++http://hydraraptor.blogspot.com/2010/01/will-it-stick.html ++ ++http://hydraraptor.blogspot.com/2010/01/hot-metal-and-serendipity.html ++ ++http://hydraraptor.blogspot.com/2010/01/new-year-new-plastic.html ++ ++http://hydraraptor.blogspot.com/2010/01/hot-bed.html ++ ++===Prusajr=== ++A resistive wire heated plexiglass plate by prusajr: ++ ++http://prusadjs.cz/ ++ ++with articles at: ++ ++http://prusadjs.cz/2010/01/heated-reprap-print-bed-mk2/ ++ ++http://prusadjs.cz/2009/11/look-ma-no-warping-heated-reprap-print-bed/ ++ ++===Zaggo=== ++A resistor heated aluminum plate by Zaggo at Pleasant Software: ++ ++http://pleasantsoftware.com/developer/3d/ ++ ++with articles at: ++ ++http://pleasantsoftware.com/developer/3d/2009/12/05/raftless/ ++ ++http://pleasantsoftware.com/developer/3d/2009/11/15/living-in-times-of-warp-free-printing/ ++ ++http://pleasantsoftware.com/developer/3d/2009/11/12/canned-heat/ ++ ++==Examples== ++The following examples chamber the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and chamber.py. ++ ++> python chamber.py ++This brings up the chamber dialog. ++ ++> python chamber.py Screw Holder Bottom.stl ++The chamber tool is parsing the file: ++Screw Holder Bottom.stl ++.. ++The chamber tool has created the file: ++Screw Holder Bottom_chamber.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 fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret ++from fabmetheus_utilities import archive ++from fabmetheus_utilities import euclidean ++from fabmetheus_utilities import gcodec ++from fabmetheus_utilities import settings ++from skeinforge_application.skeinforge_utilities import skeinforge_craft ++from skeinforge_application.skeinforge_utilities import skeinforge_polyfile ++from skeinforge_application.skeinforge_utilities import skeinforge_profile ++import sys ++ ++ ++__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' ++__date__ = '$Date: 2008/21/04 $' ++__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' ++ ++ ++def getCraftedText(fileName, text='', repository=None): ++ "Chamber the file or text." ++ return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository) ++ ++def getCraftedTextFromText(gcodeText, repository=None): ++ "Chamber a gcode linear move text." ++ if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'chamber'): ++ return gcodeText ++ if repository == None: ++ repository = settings.getReadRepository(ChamberRepository()) ++ if not repository.activateChamber.value: ++ return gcodeText ++ return ChamberSkein().getCraftedGcode(gcodeText, repository) ++ ++def getNewRepository(): ++ 'Get new repository.' ++ return ChamberRepository() ++ ++def writeOutput(fileName, shouldAnalyze=True): ++ "Chamber a gcode linear move file." ++ skeinforge_craft.writeChainTextWithNounMessage(fileName, 'chamber', shouldAnalyze) ++ ++ ++class ChamberRepository: ++ "A class to handle the chamber settings." ++ def __init__(self): ++ "Set the default settings, execute title & settings fileName." ++ skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.chamber.html', self ) ++ self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Chamber', self, '') ++ self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Chamber') ++ self.activateChamber = settings.BooleanSetting().getFromValue('Activate Chamber', self, True ) ++ settings.LabelSeparator().getFromRepository(self) ++ settings.LabelDisplay().getFromName('- Bed -', self ) ++ self.bedTemperature = settings.FloatSpin().getFromValue(20.0, 'Bed Temperature (Celcius):', self, 90.0, 60.0) ++ self.bedTemperatureBeginChangeHeight = settings.FloatSpin().getFromValue(-1.0, 'Bed Temperature Begin Change Height (mm):', self, 20.0, -1.0) ++ self.bedTemperatureEndChangeHeight = settings.FloatSpin().getFromValue(-1.0, 'Bed Temperature End Change Height (mm):', self, 40.0, -1.0) ++ self.bedTemperatureEnd = settings.FloatSpin().getFromValue(20.0, 'Bed Temperature End (Celcius):', self, 90.0, 20.0) ++ settings.LabelSeparator().getFromRepository(self) ++ self.chamberTemperature = settings.FloatSpin().getFromValue( 20.0, 'Chamber Temperature (Celcius):', self, 90.0, 30.0 ) ++ self.holdingForce = settings.FloatSpin().getFromValue( 0.0, 'Holding Force (bar):', self, 100.0, 0.0 ) ++ self.executeTitle = 'Chamber' ++ ++ def execute(self): ++ "Chamber button has been clicked." ++ fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) ++ for fileName in fileNames: ++ writeOutput(fileName) ++ ++ ++ ++class ChamberSkein: ++ "A class to chamber a skein of extrusions." ++ def __init__(self): ++ 'Initialize.' ++ self.changeWidth = None ++ self.distanceFeedRate = gcodec.DistanceFeedRate() ++ self.lineIndex = 0 ++ self.lines = None ++ self.oldBedTemperature = None ++ ++ def addBedTemperature(self, bedTemperature): ++ 'Add bed temperature if it is different from the old.' ++ if bedTemperature != self.oldBedTemperature: ++ self.distanceFeedRate.addParameter('M140', bedTemperature) ++ self.oldBedTemperature = bedTemperature ++ ++ def getCraftedGcode(self, gcodeText, repository): ++ "Parse gcode text and store the chamber gcode." ++ endAtLeastBegin = repository.bedTemperatureEndChangeHeight.value >= repository.bedTemperatureBeginChangeHeight.value ++ if endAtLeastBegin and repository.bedTemperatureBeginChangeHeight.value >= 0.0: ++ self.changeWidth = repository.bedTemperatureEndChangeHeight.value - repository.bedTemperatureBeginChangeHeight.value ++ self.repository = repository ++ self.lines = archive.getTextLines(gcodeText) ++ self.parseInitialization() ++ for line in self.lines[self.lineIndex :]: ++ self.parseLine(line) ++ return self.distanceFeedRate.output.getvalue() ++ ++ 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.addTagBracketedProcedure('chamber') ++ return ++ self.distanceFeedRate.addLine(line) ++ ++ def parseLine(self, line): ++ "Parse a gcode line and add it to the chamber skein." ++ splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) ++ if len(splitLine) < 1: ++ return ++ firstWord = splitLine[0] ++ if firstWord == '()': ++ self.distanceFeedRate.addLine(line) ++ self.addBedTemperature(self.repository.bedTemperature.value) ++ self.distanceFeedRate.addParameter('M141', self.repository.chamberTemperature.value) # Set chamber temperature. ++ self.distanceFeedRate.addParameter('M142', self.repository.holdingForce.value) # Set holding pressure. ++ return ++ self.distanceFeedRate.addLine(line) ++ if firstWord == '(' and self.changeWidth != None: ++ z = float(splitLine[1]) ++ if z >= self.repository.bedTemperatureEndChangeHeight.value: ++ self.addBedTemperature(self.repository.bedTemperatureEnd.value) ++ return ++ if z <= self.repository.bedTemperatureBeginChangeHeight.value: ++ return ++ along = (z - self.repository.bedTemperatureBeginChangeHeight.value) / self.changeWidth ++ self.addBedTemperature(self.repository.bedTemperature.value * (1 - along) + self.repository.bedTemperatureEnd.value * along) ++ ++ ++def main(): ++ "Display the chamber dialog." ++ if len(sys.argv) > 1: ++ writeOutput(' '.join(sys.argv[1 :])) ++ else: ++ settings.startMainLoopFromConstructor(getNewRepository()) ++ ++if __name__ == "__main__": ++ main() +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/clip.py ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/clip.py +@@ -92,7 +92,7 @@ + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.clip.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Clip', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Clip') +- self.activateClip = settings.BooleanSetting().getFromValue('Activate Clip', self, True ) ++ self.activateClip = settings.BooleanSetting().getFromValue('Activate Clip', self, False ) + self.clipOverPerimeterWidth = settings.FloatSpin().getFromValue( 0.1, 'Clip Over Perimeter Width (ratio):', self, 0.8, 0.5 ) + self.maximumConnectionDistanceOverPerimeterWidth = settings.FloatSpin().getFromValue( 1.0, 'Maximum Connection Distance Over Perimeter Width (ratio):', self, 20.0, 10.0 ) + self.executeTitle = 'Clip' +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/comb.py ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/comb.py +@@ -160,7 +160,7 @@ + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.comb.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Comb', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Comb') +- self.activateComb = settings.BooleanSetting().getFromValue('Activate Comb', self, False ) ++ self.activateComb = settings.BooleanSetting().getFromValue('Activate Comb', self, True ) + self.runningJumpSpace = settings.FloatSpin().getFromValue(0.0, 'Running Jump Space (mm):', self, 5.0, 2.0) + self.executeTitle = 'Comb' + +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/comb.py.orig ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/comb.py.orig +@@ -0,0 +1,500 @@ ++""" ++This page is in the table of contents. ++Comb is a craft plugin to bend the extruder travel paths around holes in the slices, to avoid stringers. ++ ++The comb manual page is at: ++http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Comb ++ ++==Operation== ++The default 'Activate Comb' checkbox is off. When it is on, the functions described below will work, when it is off, nothing will be done. ++ ++==Settings== ++===Running Jump Space=== ++Default: 2 mm ++ ++Defines the running jump space that is added before going from one island to another. If the running jump space is greater than zero, the departure from the island will also be brought closer to the arrival point on the next island so that the stringer between islands will be shorter. For an extruder with acceleration code, an extra space before leaving the island means that it will be going at high speed as it exits the island, which means the stringer between islands will be thinner. ++ ++==Examples== ++The following examples comb the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and comb.py. ++ ++> python comb.py ++This brings up the comb dialog. ++ ++> python comb.py Screw Holder Bottom.stl ++The comb tool is parsing the file: ++Screw Holder Bottom.stl ++.. ++The comb tool has created the file: ++.. Screw Holder Bottom_comb.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 fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret ++from fabmetheus_utilities import archive ++from fabmetheus_utilities import euclidean ++from fabmetheus_utilities import gcodec ++from fabmetheus_utilities import intercircle ++from fabmetheus_utilities import settings ++from skeinforge_application.skeinforge_utilities import skeinforge_craft ++from skeinforge_application.skeinforge_utilities import skeinforge_polyfile ++from skeinforge_application.skeinforge_utilities import skeinforge_profile ++import math ++import sys ++ ++ ++__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' ++__date__ = '$Date: 2008/21/04 $' ++__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' ++ ++ ++def getCraftedText(fileName, text, repository=None): ++ "Comb a gcode linear move text." ++ return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository) ++ ++def getCraftedTextFromText(gcodeText, repository=None): ++ "Comb a gcode linear move text." ++ if gcodec.isProcedureDoneOrFileIsEmpty(gcodeText, 'comb'): ++ return gcodeText ++ if repository == None: ++ repository = settings.getReadRepository(CombRepository()) ++ if not repository.activateComb.value: ++ return gcodeText ++ return CombSkein().getCraftedGcode(gcodeText, repository) ++ ++def getJumpPoint(begin, end, loop, runningJumpSpace): ++ 'Get running jump point inside loop.' ++ segment = begin - end ++ segmentLength = abs(segment) ++ if segmentLength == 0.0: ++ return begin ++ segment /= segmentLength ++ distancePoint = DistancePoint(begin, loop, runningJumpSpace, segment) ++ if distancePoint.distance == runningJumpSpace: ++ return distancePoint.point ++ effectiveDistance = distancePoint.distance ++ jumpPoint = distancePoint.point ++ segmentLeft = complex(0.70710678118654757, -0.70710678118654757) ++ distancePoint = DistancePoint(begin, loop, runningJumpSpace, segmentLeft) ++ distancePoint.distance *= 0.5 ++ if distancePoint.distance > effectiveDistance: ++ effectiveDistance = distancePoint.distance ++ jumpPoint = distancePoint.point ++ segmentRight = complex(0.70710678118654757, 0.70710678118654757) ++ distancePoint = DistancePoint(begin, loop, runningJumpSpace, segmentRight) ++ distancePoint.distance *= 0.5 ++ if distancePoint.distance > effectiveDistance: ++ effectiveDistance = distancePoint.distance ++ jumpPoint = distancePoint.point ++ return jumpPoint ++ ++def getJumpPointIfInside(boundary, otherPoint, perimeterWidth, runningJumpSpace): ++ 'Get the jump point if it is inside the boundary, otherwise return None.' ++ insetBoundary = intercircle.getSimplifiedInsetFromClockwiseLoop(boundary, -perimeterWidth) ++ closestJumpDistanceIndex = euclidean.getClosestDistanceIndexToLine(otherPoint, insetBoundary) ++ jumpIndex = (closestJumpDistanceIndex.index + 1) % len(insetBoundary) ++ jumpPoint = euclidean.getClosestPointOnSegment(insetBoundary[closestJumpDistanceIndex.index], insetBoundary[jumpIndex], otherPoint) ++ jumpPoint = getJumpPoint(jumpPoint, otherPoint, boundary, runningJumpSpace) ++ if euclidean.isPointInsideLoop(boundary, jumpPoint): ++ return jumpPoint ++ return None ++ ++def getNewRepository(): ++ 'Get new repository.' ++ return CombRepository() ++ ++def getPathsByIntersectedLoop(begin, end, loop): ++ 'Get both paths along the loop from the point closest to the begin to the point closest to the end.' ++ closestBeginDistanceIndex = euclidean.getClosestDistanceIndexToLine(begin, loop) ++ closestEndDistanceIndex = euclidean.getClosestDistanceIndexToLine(end, loop) ++ beginIndex = (closestBeginDistanceIndex.index + 1) % len(loop) ++ endIndex = (closestEndDistanceIndex.index + 1) % len(loop) ++ closestBegin = euclidean.getClosestPointOnSegment(loop[closestBeginDistanceIndex.index], loop[beginIndex], begin) ++ closestEnd = euclidean.getClosestPointOnSegment(loop[closestEndDistanceIndex.index], loop[endIndex], end) ++ clockwisePath = [closestBegin] ++ widdershinsPath = [closestBegin] ++ if closestBeginDistanceIndex.index != closestEndDistanceIndex.index: ++ widdershinsPath += euclidean.getAroundLoop(beginIndex, endIndex, loop) ++ clockwisePath += euclidean.getAroundLoop(endIndex, beginIndex, loop)[: : -1] ++ clockwisePath.append(closestEnd) ++ widdershinsPath.append(closestEnd) ++ return [clockwisePath, widdershinsPath] ++ ++def writeOutput(fileName, shouldAnalyze=True): ++ "Comb a gcode linear move file." ++ skeinforge_craft.writeChainTextWithNounMessage(fileName, 'comb', shouldAnalyze) ++ ++ ++class BoundarySegment: ++ 'A boundary and segment.' ++ def __init__(self, begin): ++ 'Initialize' ++ self.segment = [begin] ++ ++ def getSegment(self, boundarySegmentIndex, boundarySegments, perimeterWidth, runningJumpSpace): ++ 'Get both paths along the loop from the point closest to the begin to the point closest to the end.' ++ negativePerimeterWidth = -perimeterWidth ++ nextBoundarySegment = boundarySegments[boundarySegmentIndex + 1] ++ nextBegin = nextBoundarySegment.segment[0] ++ end = getJumpPointIfInside(self.boundary, nextBegin, perimeterWidth, runningJumpSpace) ++ if end == None: ++ end = self.boundary.segment[1] ++ nextBegin = getJumpPointIfInside(nextBoundarySegment.boundary, end, perimeterWidth, runningJumpSpace) ++ if nextBegin != None: ++ nextBoundarySegment.segment[0] = nextBegin ++ return (self.segment[0], end) ++ ++ ++class CombRepository: ++ "A class to handle the comb settings." ++ def __init__(self): ++ "Set the default settings, execute title & settings fileName." ++ skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.comb.html', self ) ++ self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Comb', self, '') ++ self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Comb') ++ self.activateComb = settings.BooleanSetting().getFromValue('Activate Comb', self, False ) ++ self.runningJumpSpace = settings.FloatSpin().getFromValue(0.0, 'Running Jump Space (mm):', self, 5.0, 2.0) ++ self.executeTitle = 'Comb' ++ ++ def execute(self): ++ "Comb button has been clicked." ++ fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) ++ for fileName in fileNames: ++ writeOutput(fileName) ++ ++ ++class CombSkein: ++ "A class to comb a skein of extrusions." ++ def __init__(self): ++ 'Initialize' ++ self.betweenTable = {} ++ self.boundaryLoop = None ++ self.distanceFeedRate = gcodec.DistanceFeedRate() ++ self.extruderActive = False ++ self.layer = None ++ self.layerCount = settings.LayerCount() ++ self.layerTable = {} ++ self.layerZ = None ++ self.lineIndex = 0 ++ self.lines = None ++ self.nextLayerZ = None ++ self.oldLocation = None ++ self.oldZ = None ++ self.operatingFeedRatePerMinute = None ++ self.travelFeedRateMinute = None ++ self.widdershinTable = {} ++ ++ def addGcodePathZ( self, feedRateMinute, path, z ): ++ "Add a gcode path, without modifying the extruder, to the output." ++ for point in path: ++ self.distanceFeedRate.addGcodeMovementZWithFeedRate(feedRateMinute, 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.getBoundaries()) > 0: ++ highestZ = max(location.z, self.oldLocation.z) ++ self.addGcodePathZ(self.travelFeedRateMinute, self.getAroundBetweenPath(self.oldLocation.dropAxis(), location.dropAxis()), highestZ) ++ self.oldLocation = location ++ ++ 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 = [] ++ self.layer.append(self.boundaryLoop) ++ self.boundaryLoop.append(location.dropAxis()) ++ ++ def getAroundBetweenLineSegment(self, begin, boundaries, end): ++ 'Get the path around the loops in the way of the original line segment.' ++ aroundBetweenLineSegment = [] ++ boundaries = self.getBoundaries() ++ points = [] ++ boundaryIndexes = self.getBoundaryIndexes(begin, boundaries, end, points) ++ boundaryIndexesIndex = 0 ++ while boundaryIndexesIndex < len(boundaryIndexes) - 1: ++ if boundaryIndexes[boundaryIndexesIndex + 1] == boundaryIndexes[boundaryIndexesIndex]: ++ loopFirst = boundaries[boundaryIndexes[boundaryIndexesIndex]] ++ pathBetween = self.getPathBetween(loopFirst, points[boundaryIndexesIndex : boundaryIndexesIndex + 4]) ++ begin = points[boundaryIndexesIndex] ++ end = points[boundaryIndexesIndex + 3] ++ pathBetween = self.getInsidePointsAlong(begin, pathBetween[0], points) + pathBetween ++ pathBetween += self.getInsidePointsAlong(end, pathBetween[-1], points) ++ aroundBetweenLineSegment += pathBetween ++ boundaryIndexesIndex += 2 ++ else: ++ boundaryIndexesIndex += 1 ++ return aroundBetweenLineSegment ++ ++ def getAroundBetweenPath(self, begin, end): ++ 'Get the path around the loops in the way of the original line segment.' ++ aroundBetweenPath = [] ++ betweens = self.getBetweens() ++ boundaries = self.getBoundaries() ++ boundarySegments = self.getBoundarySegments(begin, boundaries, end) ++ for boundarySegmentIndex, boundarySegment in enumerate(boundarySegments): ++ segment = boundarySegment.segment ++ if boundarySegmentIndex < len(boundarySegments) - 1 and self.runningJumpSpace > 0.0: ++ segment = boundarySegment.getSegment(boundarySegmentIndex, boundarySegments, self.perimeterWidth, self.runningJumpSpace) ++ aroundBetweenPath += self.getAroundBetweenLineSegment(segment[0], boundaries, segment[1]) ++ if boundarySegmentIndex < len(boundarySegments) - 1: ++ aroundBetweenPath.append(segment[1]) ++ aroundBetweenPath.append(boundarySegments[boundarySegmentIndex + 1].segment[0]) ++ for pointIndex in xrange(len(aroundBetweenPath) - 1, -1, -1): ++ pointBefore = begin ++ beforeIndex = pointIndex - 1 ++ if beforeIndex >= 0: ++ pointBefore = aroundBetweenPath[beforeIndex] ++ pointAfter = end ++ afterIndex = pointIndex + 1 ++ if afterIndex < len(aroundBetweenPath): ++ pointAfter = aroundBetweenPath[afterIndex] ++ if not euclidean.isLineIntersectingLoops(betweens, pointBefore, pointAfter): ++ del aroundBetweenPath[pointIndex] ++ return aroundBetweenPath ++ ++ def getBetweens(self): ++ 'Get betweens for the layer.' ++ if not self.layerZ in self.betweenTable: ++ self.betweenTable[self.layerZ] = [] ++ for boundary in self.getBoundaries(): ++ self.betweenTable[self.layerZ] += intercircle.getInsetLoopsFromLoop(boundary, self.betweenInset) ++ return self.betweenTable[self.layerZ] ++ ++ def getBoundaries(self): ++ "Get boundaries for the layer." ++ if self.layerZ in self.layerTable: ++ return self.layerTable[self.layerZ] ++ return [] ++ ++ def getBoundaryIndexes(self, begin, boundaries, end, points): ++ 'Get boundary indexes and set the points in the way of the original line segment.' ++ boundaryIndexes = [] ++ points.append(begin) ++ switchX = [] ++ segment = euclidean.getNormalized(end - begin) ++ segmentYMirror = complex(segment.real, - segment.imag) ++ beginRotated = segmentYMirror * begin ++ endRotated = segmentYMirror * end ++ y = beginRotated.imag ++ for boundaryIndex in xrange(len(boundaries)): ++ boundary = boundaries[boundaryIndex] ++ boundaryRotated = euclidean.getRotatedComplexes(segmentYMirror, boundary) ++ euclidean.addXIntersectionIndexesFromLoopY(boundaryRotated, boundaryIndex, switchX, y) ++ switchX.sort() ++ maximumX = max(beginRotated.real, endRotated.real) ++ minimumX = min(beginRotated.real, endRotated.real) ++ for xIntersection in switchX: ++ if xIntersection.x > minimumX and xIntersection.x < maximumX: ++ point = segment * complex(xIntersection.x, y) ++ points.append(point) ++ boundaryIndexes.append(xIntersection.index) ++ points.append(end) ++ return boundaryIndexes ++ ++ def getBoundarySegments(self, begin, boundaries, end): ++ 'Get the path broken into boundary segments whenever a different boundary is crossed.' ++ boundarySegments = [] ++ boundarySegment = BoundarySegment(begin) ++ boundarySegments.append(boundarySegment) ++ points = [] ++ boundaryIndexes = self.getBoundaryIndexes(begin, boundaries, end, points) ++ boundaryIndexesIndex = 0 ++ while boundaryIndexesIndex < len(boundaryIndexes) - 1: ++ if boundaryIndexes[boundaryIndexesIndex + 1] != boundaryIndexes[boundaryIndexesIndex]: ++ boundarySegment.boundary = boundaries[boundaryIndexes[boundaryIndexesIndex]] ++ nextBoundary = boundaries[boundaryIndexes[boundaryIndexesIndex + 1]] ++ if euclidean.isWiddershins(boundarySegment.boundary) and euclidean.isWiddershins(nextBoundary): ++ boundarySegment.segment.append(points[boundaryIndexesIndex + 1]) ++ boundarySegment = BoundarySegment(points[boundaryIndexesIndex + 2]) ++ boundarySegment.boundary = nextBoundary ++ boundarySegments.append(boundarySegment) ++ boundaryIndexesIndex += 1 ++ boundaryIndexesIndex += 1 ++ boundarySegment.segment.append(points[-1]) ++ return boundarySegments ++ ++ def getCraftedGcode(self, gcodeText, repository): ++ "Parse gcode text and store the comb gcode." ++ self.runningJumpSpace = repository.runningJumpSpace.value ++ self.repository = repository ++ self.lines = archive.getTextLines(gcodeText) ++ self.parseInitialization() ++ for lineIndex in xrange(self.lineIndex, len(self.lines)): ++ line = self.lines[lineIndex] ++ self.parseBoundariesLayers(line) ++ for lineIndex in xrange(self.lineIndex, len(self.lines)): ++ line = self.lines[lineIndex] ++ self.parseLine(line) ++ return self.distanceFeedRate.output.getvalue() ++ ++ def getInsidePointsAlong(self, begin, end, points): ++ 'Get the points along the segment if it is required to keep the path inside the widdershin boundaries.' ++ segment = end - begin ++ segmentLength = abs(segment) ++ if segmentLength < self.quadruplePerimeterWidth: ++ return [] ++ segmentHalfPerimeter = self.halfPerimeterWidth / segmentLength * segment ++ justAfterBegin = begin + segmentHalfPerimeter ++ justBeforeEnd = end - segmentHalfPerimeter ++ widdershins = self.getWiddershins() ++ if not euclidean.isLineIntersectingLoops(widdershins, justAfterBegin, justBeforeEnd): ++ return [] ++ numberOfSteps = 10 ++ stepLength = (segmentLength - self.doublePerimeterWidth) / float(numberOfSteps) ++ for step in xrange(1, numberOfSteps + 1): ++ along = begin + stepLength * step ++ if not euclidean.isLineIntersectingLoops(widdershins, along, justBeforeEnd): ++ return [along] ++ return [] ++ ++ def getPathBetween(self, loop, points): ++ "Add a path between the perimeter and the fill." ++ paths = getPathsByIntersectedLoop(points[1], points[2], loop) ++ shortestPath = paths[int(euclidean.getPathLength(paths[1]) < euclidean.getPathLength(paths[0]))] ++ if len(shortestPath) < 2: ++ return shortestPath ++ if abs(points[1] - shortestPath[0]) > abs(points[1] - shortestPath[-1]): ++ shortestPath.reverse() ++ loopWiddershins = euclidean.isWiddershins(loop) ++ pathBetween = [] ++ for pointIndex in xrange(len(shortestPath)): ++ center = shortestPath[pointIndex] ++ centerPerpendicular = None ++ beginIndex = pointIndex - 1 ++ if beginIndex >= 0: ++ begin = shortestPath[beginIndex] ++ centerPerpendicular = intercircle.getWiddershinsByLength(center, begin, self.perimeterWidth) ++ centerEnd = None ++ endIndex = pointIndex + 1 ++ if endIndex < len(shortestPath): ++ end = shortestPath[endIndex] ++ centerEnd = intercircle.getWiddershinsByLength(end, center, self.perimeterWidth) ++ if centerPerpendicular == None: ++ centerPerpendicular = centerEnd ++ elif centerEnd != None: ++ centerPerpendicular = 0.5 * (centerPerpendicular + centerEnd) ++ between = None ++ if centerPerpendicular == None: ++ between = center ++ if between == None: ++ centerSideWiddershins = center + centerPerpendicular ++ if euclidean.isPointInsideLoop(loop, centerSideWiddershins) == loopWiddershins: ++ between = centerSideWiddershins ++ if between == None: ++ centerSideClockwise = center - centerPerpendicular ++ if euclidean.isPointInsideLoop(loop, centerSideClockwise) == loopWiddershins: ++ between = centerSideClockwise ++ if between == None: ++ between = center ++ pathBetween.append(between) ++ return pathBetween ++ ++ def getWiddershins(self): ++ 'Get widdershins for the layer.' ++ if self.layerZ in self.widdershinTable: ++ return self.widdershinTable[self.layerZ] ++ self.widdershinTable[self.layerZ] = [] ++ for boundary in self.getBoundaries(): ++ if euclidean.isWiddershins(boundary): ++ self.widdershinTable[self.layerZ].append(boundary) ++ return self.widdershinTable[self.layerZ] ++ ++ def parseBoundariesLayers(self, line): ++ "Parse a gcode line." ++ splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) ++ if len(splitLine) < 1: ++ return ++ firstWord = splitLine[0] ++ if firstWord == 'M103': ++ self.boundaryLoop = None ++ elif firstWord == '(': ++ location = gcodec.getLocationFromSplitLine(None, splitLine) ++ self.addToLoop(location) ++ elif firstWord == '(': ++ self.boundaryLoop = None ++ self.layer = None ++ self.oldZ = float(splitLine[1]) ++ ++ 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.addTagBracketedProcedure('comb') ++ return ++ elif firstWord == '(': ++ self.perimeterWidth = float(splitLine[1]) ++ self.betweenInset = 0.7 * self.perimeterWidth ++ self.doublePerimeterWidth = self.perimeterWidth + self.perimeterWidth ++ self.halfPerimeterWidth = 0.5 * self.perimeterWidth ++ self.quadruplePerimeterWidth = self.doublePerimeterWidth + self.doublePerimeterWidth ++ elif firstWord == '(': ++ self.travelFeedRateMinute = 60.0 * float(splitLine[1]) ++ self.distanceFeedRate.addLine(line) ++ ++ def parseLine(self, line): ++ "Parse a gcode line and add it to the comb skein." ++ splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) ++ if len(splitLine) < 1: ++ return ++ firstWord = splitLine[0] ++ if self.distanceFeedRate.getIsAlteration(line): ++ return ++ if firstWord == 'G1': ++ self.addIfTravel(splitLine) ++ self.layerZ = self.nextLayerZ ++ elif firstWord == 'M101': ++ self.extruderActive = True ++ elif firstWord == 'M103': ++ self.extruderActive = False ++ elif firstWord == '(': ++ self.layerCount.printProgressIncrement('comb') ++ self.nextLayerZ = float(splitLine[1]) ++ if self.layerZ == None: ++ self.layerZ = self.nextLayerZ ++ self.distanceFeedRate.addLineCheckAlteration(line) ++ ++ ++class DistancePoint: ++ 'A class to get the distance of the point along a segment inside a loop.' ++ def __init__(self, begin, loop, runningJumpSpace, segment): ++ 'Initialize' ++ self.distance = 0.0 ++ self.point = begin ++ steps = 10 ++ spaceOverSteps = runningJumpSpace / float(steps) ++ for numerator in xrange(1, steps + 1): ++ distance = float(numerator) * spaceOverSteps ++ point = begin + segment * distance ++ if euclidean.isPointInsideLoop(loop, point): ++ self.distance = distance ++ self.point = point ++ else: ++ return ++ ++ ++def main(): ++ "Display the comb dialog." ++ if len(sys.argv) > 1: ++ writeOutput(' '.join(sys.argv[1 :])) ++ else: ++ settings.startMainLoopFromConstructor(getNewRepository()) ++ ++if __name__ == "__main__": ++ main() +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/cool.py ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/cool.py +@@ -149,7 +149,7 @@ + self.orbit = settings.MenuRadio().getFromMenuButtonDisplay(self.coolType, 'Orbit', self, False) + self.slowDown = settings.MenuRadio().getFromMenuButtonDisplay(self.coolType, 'Slow Down', self, True) + self.maximumCool = settings.FloatSpin().getFromValue(0.0, 'Maximum Cool (Celcius):', self, 10.0, 2.0) +- self.minimumLayerTime = settings.FloatSpin().getFromValue(0.0, 'Minimum Layer Time (seconds):', self, 120.0, 60.0) ++ self.minimumLayerTime = settings.FloatSpin().getFromValue(0.0, 'Minimum Layer Time (seconds):', self, 120.0, 10.0) + self.minimumOrbitalRadius = settings.FloatSpin().getFromValue( + 0.0, 'Minimum Orbital Radius (millimeters):', self, 20.0, 10.0) + settings.LabelSeparator().getFromRepository(self) +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/dimension.py ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/dimension.py +@@ -148,7 +148,7 @@ + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.dimension.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Dimension', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Dimension') +- self.activateDimension = settings.BooleanSetting().getFromValue('Activate Dimension', self, False ) ++ self.activateDimension = settings.BooleanSetting().getFromValue('Activate Dimension', self, True ) + extrusionDistanceFormatLatentStringVar = settings.LatentStringVar() + self.extrusionDistanceFormatChoiceLabel = settings.LabelDisplay().getFromName('Extrusion Distance Format Choice: ', self ) + settings.Radio().getFromRadio( extrusionDistanceFormatLatentStringVar, 'Absolute Extrusion Distance', self, True ) +@@ -156,7 +156,7 @@ + self.extruderRetractionSpeed = settings.FloatSpin().getFromValue( 4.0, 'Extruder Retraction Speed (mm/s):', self, 34.0, 13.3 ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Filament -', self ) +- self.filamentDiameter = settings.FloatSpin().getFromValue(1.0, 'Filament Diameter (mm):', self, 6.0, 2.8) ++ self.filamentDiameter = settings.FloatSpin().getFromValue(1.0, 'Filament Diameter (mm):', self, 6.0, 2.89) + self.filamentPackingDensity = settings.FloatSpin().getFromValue(0.7, 'Filament Packing Density (ratio):', self, 1.0, 0.85) + settings.LabelSeparator().getFromRepository(self) + self.maximumEValueBeforeReset = settings.FloatSpin().getFromValue(0.0, 'Maximum E Value before Reset (float):', self, 999999.9, 91234.0) +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/export.py ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/export.py +@@ -339,17 +339,20 @@ + self.exportLabel = settings.LabelDisplay().getFromName('Export Operations: ', self) + self.exportPlugins = [] + exportLatentStringVar = settings.LatentStringVar() +- self.doNotChangeOutput = settings.RadioCapitalized().getFromRadio(exportLatentStringVar, 'Do Not Change Output', self, True) ++ self.doNotChangeOutput = settings.RadioCapitalized().getFromRadio(exportLatentStringVar, 'Do Not Change Output', self, False) + self.doNotChangeOutput.directoryPath = None + allExportPluginFileNames = exportPluginFileNames + exportStaticPluginFileNames + for exportPluginFileName in allExportPluginFileNames: + exportPlugin = None ++ default = False ++ if exportPluginFileName == "gcode_small": ++ default = True + if exportPluginFileName in exportPluginFileNames: + path = os.path.join(exportPluginsFolderPath, exportPluginFileName) +- exportPlugin = settings.RadioCapitalizedButton().getFromPath(exportLatentStringVar, exportPluginFileName, path, self, False) ++ exportPlugin = settings.RadioCapitalizedButton().getFromPath(exportLatentStringVar, exportPluginFileName, path, self, default) + exportPlugin.directoryPath = exportPluginsFolderPath + else: +- exportPlugin = settings.RadioCapitalized().getFromRadio(exportLatentStringVar, exportPluginFileName, self, False) ++ exportPlugin = settings.RadioCapitalized().getFromRadio(exportLatentStringVar, exportPluginFileName, self, default) + exportPlugin.directoryPath = exportStaticDirectoryPath + self.exportPlugins.append(exportPlugin) + self.fileExtension = settings.StringSetting().getFromValue('File Extension:', self, 'gcode') +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/fill.py ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/fill.py +@@ -802,7 +802,7 @@ + self.infillPatternLine = settings.Radio().getFromRadio( infillLatentStringVar, 'Line', self, True ) + self.infillPerimeterOverlap = settings.FloatSpin().getFromValue( 0.0, 'Infill Perimeter Overlap (ratio):', self, 0.4, 0.15 ) + self.infillSolidity = settings.FloatSpin().getFromValue( 0.04, 'Infill Solidity (ratio):', self, 0.3, 0.2 ) +- self.infillWidthOverThickness = settings.FloatSpin().getFromValue(1.3, 'Infill Width over Thickness (ratio):', self, 1.7, 1.5) ++ self.infillWidth = settings.FloatSpin().getFromValue( 0.1, 'Infill Width:', self, 1.7, 0.4 ) + settings.LabelSeparator().getFromRepository(self) + self.solidSurfaceThickness = settings.IntSpin().getFromValue(0, 'Solid Surface Thickness (layers):', self, 5, 3) + self.startFromChoice = settings.MenuButtonDisplay().getFromName('Start From Choice:', self) +@@ -1250,7 +1250,7 @@ + return + elif firstWord == '(': + self.layerThickness = float(splitLine[1]) +- self.infillWidth = self.repository.infillWidthOverThickness.value * self.layerThickness ++ self.infillWidth = self.repository.infillWidth.value + self.surroundingSlope = math.tan(math.radians(min(self.repository.surroundingAngle.value, 80.0))) + self.distanceFeedRate.addTagRoundedLine('infillPerimeterOverlap', self.repository.infillPerimeterOverlap.value) + self.distanceFeedRate.addTagRoundedLine('infillWidth', self.infillWidth) +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/fill.py.orig ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/fill.py.orig +@@ -0,0 +1,1374 @@ ++#! /usr/bin/env python ++""" ++This page is in the table of contents. ++Fill is a script to fill the perimeters of a gcode file. ++ ++The fill manual page is at: ++http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Fill ++ ++Allan Ecker aka The Masked Retriever has written the "Skeinforge Quicktip: Fill" at: ++http://blog.thingiverse.com/2009/07/21/mysteries-of-skeinforge-fill/ ++ ++==Operation== ++The default 'Activate Fill' checkbox is off. When it is on, the functions described below will work, when it is off, the functions will not be called. ++ ++==Settings== ++===Diaphragm=== ++The diaphragm is a solid group of layers, at regular intervals. It can be used with a sparse infill to give the object watertight, horizontal compartments and/or a higher shear strength. ++ ++====Diaphragm Period==== ++Default is one hundred. ++ ++Defines the number of layers between diaphrams. ++ ++====Diaphragm Thickness==== ++Default is zero, because the diaphragm feature is rarely used. ++ ++Defines the number of layers the diaphram is composed of. ++ ++===Extra Shells=== ++The shells interior perimeter loops. Adding extra shells makes the object stronger & heavier. ++ ++====Extra Shells on Alternating Solid Layers==== ++Default is two. ++ ++Defines the number of extra shells, on the alternating solid layers. ++ ++====Extra Shells on Base==== ++Default is one. ++ ++Defines the number of extra shells on the bottom, base layer and every even solid layer after that. Setting this to a different value than the "Extra Shells on Alternating Solid Layers" means the infill pattern will alternate, creating a strong interleaved bond even if the perimeter loop shrinks. ++ ++====Extra Shells on Sparse Layer==== ++Default is one. ++ ++Defines the number of extra shells on the sparse layers. The solid layers are those at the top & bottom, and wherever the object has a plateau or overhang, the sparse layers are the layers in between. ++ ++===Grid=== ++====Grid Circle Separation over Perimeter Width==== ++Default is 0.2. ++ ++Defines the ratio of the amount the grid circle is inset over the perimeter width, the default is zero. With a value of zero the circles will touch, with a value of one two threads could be fitted between the circles. ++ ++====Grid Extra Overlap==== ++Default is 0.1. ++ ++Defines the amount of extra overlap added when extruding the grid to compensate for the fact that when the first thread going through a grid point is extruded, since there is nothing there yet for it to connect to it will shrink extra. ++ ++====Grid Junction Separation over Octogon Radius At End==== ++Default is zero. ++ ++Defines the ratio of the amount the grid square is increased in each direction over the extrusion width at the end. With a value of one or so the grid pattern will have large squares to go with the octogons. ++ ++====Grid Junction Separation over Octogon Radius At Middle==== ++Default is zero. ++ ++Defines the increase at the middle. If this value is different than the value at the end, the grid would have an accordion pattern, which would give it a higher shear strength. ++ ++====Grid Junction Separation Band Height==== ++Default is ten. ++ ++Defines the height of the bands of the accordion pattern. ++ ++===Infill=== ++====Infill Pattern==== ++Default is 'Line', since it is quicker to generate and does not add extra movements for the extruder. The grid pattern has extra diagonal lines, so when choosing a grid option, set the infill solidity to 0.2 or less so that there is not too much plastic and the grid generation time, which increases with the third power of solidity, will be reasonable. ++ ++=====Grid Circular===== ++When selected, the infill will be a grid of separated circles. Because the circles are separated, the pattern is weak, it only provides support for the top layer threads and some strength in the z direction. The flip side is that this infill does not warp the object, the object will get warped only by the walls. ++ ++Because this pattern turns the extruder on and off often, it is best to use a stepper motor extruder. ++ ++=====Grid Hexagonal===== ++When selected, the infill will be a hexagonal grid. Because the grid is made with threads rather than with molding or milling, only a partial hexagon is possible, so the rectangular grid pattern is stronger. ++ ++=====Grid Rectangular===== ++When selected, the infill will be a funky octogon square honeycomb like pattern which gives the object extra strength. ++ ++=====Line===== ++When selected, the infill will be made up of lines. ++ ++====Infill Begin Rotation==== ++Default is forty five degrees, giving a diagonal infill. ++ ++Defines the amount the infill direction of the base and every second layer thereafter is rotated. ++ ++====Infill Odd Layer Extra Rotation==== ++Default is ninety degrees, making the odd layer infill perpendicular to the base layer. ++ ++Defines the extra amount the infill direction of the odd layers is rotated compared to the base layer. ++ ++====Infill Begin Rotation Repeat==== ++Default is one, giving alternating cross hatching. ++ ++Defines the number of layers that the infill begin rotation will repeat. With a value higher than one, the infill will go in one direction more often, giving the object more strength in one direction and less in the other, this is useful for beams and cantilevers. ++ ++====Infill Perimeter Overlap==== ++Default is 0.15. ++ ++Defines the amount the infill overlaps the perimeter over the average of the perimeter and infill width. The higher the value the more the infill will overlap the perimeter, and the thicker join between the infill and the perimeter. If the value is too high, the join will be so thick that the nozzle will run plow through the join below making a mess, also when it is above 0.45 fill may not be able to create infill correctly. If you want to stretch the infill a lot, set 'Path Stretch over Perimeter Width' in stretch to a high value. ++ ++====Infill Solidity==== ++Default is 0.2. ++ ++Defines the solidity of the infill, this is the most important setting in fill. A value of one means the infill lines will be right beside each other, resulting in a solid, strong, heavy shape which takes a long time to extrude. A low value means the infill will be sparse, the interior will be mosty empty space, the object will be weak, light and quick to build. ++ ++====Infill Width over Thickness==== ++Default is 1.5. ++ ++Defines the ratio of the infill width over the layer thickness. The higher the value the wider apart the infill will be and therefore the sparser the infill will be. ++ ++===Solid Surface Thickness=== ++Default is three. ++ ++Defines the number of solid layers that are at the bottom, top, plateaus and overhang. With a value of zero, the entire object will be composed of a sparse infill, and water could flow right through it. With a value of one, water will leak slowly through the surface and with a value of three, the object could be watertight. The higher the solid surface thickness, the stronger and heavier the object will be. ++ ++===Start From Choice=== ++Default is 'Lower Left'. ++ ++Defines where each layer starts from. ++ ++====Lower Left==== ++When selected the layer will start from the lower left corner. This is to extrude in round robin fashion so that the first extrusion will be deposited on the coolest part of the last layer. The reason for this is described at: ++http://hydraraptor.blogspot.com/2010/12/round-robin.html ++ ++====Nearest==== ++When selected the layer will start from the closest point to the end of the last layer. This leads to less stringing, but the first extrusion will be deposited on the hottest part of the last layer which leads to melting problems. So this option is deprecated, eventually this option will be removed and the layers will always start from the lower left. ++ ++===Surrounding Angle=== ++Default: 60 degrees ++ ++Defines the angle that the surrounding layers around the infill are expanded. ++ ++To decide whether or not the infill should be sparse or solid, fill looks at the 'Solid Surface Thickness' surrounding layers above and below the infill. If any of the expanded layers above or below the infill do not cover the infill, then the infill will be solid in that region. The layers are expanded by the height difference times the tangent of the surrounding angle, which is from the vertical. For example, if the model is a wedge with a wall angle less than the surrounding angle, the interior layers (those which are not on the bottom or top) will be sparse. If the wall angle is greater than the surrounding angle, the interior layers will be solid. ++ ++The time required to examine the surrounding layers increases with the surrounding angle, so the surrounding angle is limited to eighty degrees, regardless of the input value. ++ ++If you have an organic shape with gently sloping surfaces; if the surrounding angle is set too high, then too many layers will be sparse. If the surrounding angle is too low, then too many layers will be solid and the extruder may end up plowing through previous layers: ++http://hydraraptor.blogspot.com/2008/08/bearing-fruit.html ++ ++===Thread Sequence Choice=== ++The 'Thread Sequence Choice' is the sequence in which the threads will be extruded on the second and higher layers. There are three kinds of thread, the perimeter threads on the outside of the object, the loop threads aka inner shell threads, and the interior infill threads. The first layer thread sequence is 'Perimeter > Loops > Infill'. ++ ++The default choice is 'Perimeter > Loops > Infill', which the default stretch parameters are based on. If you change from the default sequence choice setting of perimeter, then loops, then infill, the optimal stretch thread parameters would also be different. In general, if the infill is extruded first, the infill would have to be stretched more so that even after the filament shrinkage, it would still be long enough to connect to the loop or perimeter. The six sequence combinations follow below. ++ ++====Infill > Loops > Perimeter==== ++====Infill > Perimeter > Loops==== ++====Loops > Infill > Perimeter==== ++====Loops > Perimeter > Infill==== ++====Perimeter > Infill > Loops==== ++====Perimeter > Loops > Infill==== ++ ++==Examples== ++The following examples fill the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and fill.py. ++ ++> python fill.py ++This brings up the fill dialog. ++ ++> python fill.py Screw Holder Bottom.stl ++The fill tool is parsing the file: ++Screw Holder Bottom.stl ++.. ++The fill tool has created the file: ++.. Screw Holder Bottom_fill.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 fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret ++from fabmetheus_utilities.geometry.solids import triangle_mesh ++from fabmetheus_utilities.vector3 import Vector3 ++from fabmetheus_utilities import archive ++from fabmetheus_utilities import euclidean ++from fabmetheus_utilities import gcodec ++from fabmetheus_utilities import intercircle ++from fabmetheus_utilities import settings ++from skeinforge_application.skeinforge_utilities import skeinforge_craft ++from skeinforge_application.skeinforge_utilities import skeinforge_polyfile ++from skeinforge_application.skeinforge_utilities import skeinforge_profile ++import math ++import sys ++ ++ ++__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' ++__date__ = '$Date: 2008/28/04 $' ++__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' ++ ++ ++ ++def addAroundGridPoint( arounds, gridPoint, gridPointInsetX, gridPointInsetY, gridPoints, gridSearchRadius, isBothOrNone, isDoubleJunction, isJunctionWide, paths, pixelTable, width ): ++ 'Add the path around the grid point.' ++ closestPathIndex = None ++ aroundIntersectionPaths = [] ++ for aroundIndex in xrange( len(arounds) ): ++ loop = arounds[ aroundIndex ] ++ for pointIndex in xrange(len(loop)): ++ pointFirst = loop[pointIndex] ++ pointSecond = loop[(pointIndex + 1) % len(loop)] ++ yIntersection = euclidean.getYIntersectionIfExists( pointFirst, pointSecond, gridPoint.real ) ++ addYIntersectionPathToList( aroundIndex, pointIndex, gridPoint.imag, yIntersection, aroundIntersectionPaths ) ++ if len( aroundIntersectionPaths ) < 2: ++ print('This should never happen, aroundIntersectionPaths is less than 2 in fill.') ++ print( aroundIntersectionPaths ) ++ print( gridPoint ) ++ return ++ yCloseToCenterArounds = getClosestOppositeIntersectionPaths( aroundIntersectionPaths ) ++ if len( yCloseToCenterArounds ) < 2: ++# This used to be worth the warning below. ++# print('This should never happen, yCloseToCenterArounds is less than 2 in fill.') ++# print( gridPoint ) ++# print( len( yCloseToCenterArounds ) ) ++ return ++ segmentFirstY = min( yCloseToCenterArounds[0].y, yCloseToCenterArounds[1].y ) ++ segmentSecondY = max( yCloseToCenterArounds[0].y, yCloseToCenterArounds[1].y ) ++ yIntersectionPaths = [] ++ gridPixel = euclidean.getStepKeyFromPoint( gridPoint / width ) ++ segmentFirstPixel = euclidean.getStepKeyFromPoint( complex( gridPoint.real, segmentFirstY ) / width ) ++ segmentSecondPixel = euclidean.getStepKeyFromPoint( complex( gridPoint.real, segmentSecondY ) / width ) ++ pathIndexTable = {} ++ addPathIndexFirstSegment( gridPixel, pathIndexTable, pixelTable, segmentFirstPixel ) ++ addPathIndexSecondSegment( gridPixel, pathIndexTable, pixelTable, segmentSecondPixel ) ++ for pathIndex in pathIndexTable.keys(): ++ path = paths[ pathIndex ] ++ for pointIndex in xrange( len(path) - 1 ): ++ pointFirst = path[pointIndex] ++ pointSecond = path[pointIndex + 1] ++ yIntersection = getYIntersectionInsideYSegment( segmentFirstY, segmentSecondY, pointFirst, pointSecond, gridPoint.real ) ++ addYIntersectionPathToList( pathIndex, pointIndex, gridPoint.imag, yIntersection, yIntersectionPaths ) ++ if len( yIntersectionPaths ) < 1: ++ return ++ yCloseToCenterPaths = [] ++ if isDoubleJunction: ++ yCloseToCenterPaths = getClosestOppositeIntersectionPaths( yIntersectionPaths ) ++ else: ++ yIntersectionPaths.sort( compareDistanceFromCenter ) ++ yCloseToCenterPaths = [ yIntersectionPaths[0] ] ++ for yCloseToCenterPath in yCloseToCenterPaths: ++ setIsOutside( yCloseToCenterPath, aroundIntersectionPaths ) ++ if len( yCloseToCenterPaths ) < 2: ++ yCloseToCenterPaths[0].gridPoint = gridPoint ++ insertGridPointPair( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, paths, pixelTable, yCloseToCenterPaths[0], width ) ++ return ++ plusMinusSign = getPlusMinusSign( yCloseToCenterPaths[1].y - yCloseToCenterPaths[0].y ) ++ yCloseToCenterPaths[0].gridPoint = complex( gridPoint.real, gridPoint.imag - plusMinusSign * gridPointInsetY ) ++ yCloseToCenterPaths[1].gridPoint = complex( gridPoint.real, gridPoint.imag + plusMinusSign * gridPointInsetY ) ++ yCloseToCenterPaths.sort( comparePointIndexDescending ) ++ insertGridPointPairs( gridPoint, gridPointInsetX, gridPoints, yCloseToCenterPaths[0], yCloseToCenterPaths[1], isBothOrNone, isJunctionWide, paths, pixelTable, width ) ++ ++def addInfillBoundary(infillBoundary, nestedRings): ++ 'Add infill boundary to the nested ring that contains it.' ++ infillPoint = infillBoundary[0] ++ for nestedRing in nestedRings: ++ if euclidean.isPointInsideLoop(nestedRing.boundary, infillPoint): ++ nestedRing.infillBoundaries.append(infillBoundary) ++ return ++ ++def addLoop(infillWidth, infillPaths, loop, rotationPlaneAngle): ++ 'Add simplified path to fill.' ++ simplifiedLoop = euclidean.getSimplifiedLoop(loop, infillWidth) ++ if len(simplifiedLoop) < 2: ++ return ++ simplifiedLoop.append(simplifiedLoop[0]) ++ planeRotated = euclidean.getRotatedComplexes(rotationPlaneAngle, simplifiedLoop) ++ infillPaths.append(planeRotated) ++ ++def addPath(infillWidth, infillPaths, path, rotationPlaneAngle): ++ 'Add simplified path to fill.' ++ simplifiedPath = euclidean.getSimplifiedPath(path, infillWidth) ++ if len(simplifiedPath) < 2: ++ return ++ planeRotated = euclidean.getRotatedComplexes(rotationPlaneAngle, simplifiedPath) ++ infillPaths.append(planeRotated) ++ ++def addPathIndexFirstSegment( gridPixel, pathIndexTable, pixelTable, segmentFirstPixel ): ++ 'Add the path index of the closest segment found toward the second segment.' ++ for yStep in xrange( gridPixel[1], segmentFirstPixel[1] - 1, - 1 ): ++ if getKeyIsInPixelTableAddValue( ( gridPixel[0], yStep ), pathIndexTable, pixelTable ): ++ return ++ ++def addPathIndexSecondSegment( gridPixel, pathIndexTable, pixelTable, segmentSecondPixel ): ++ 'Add the path index of the closest segment found toward the second segment.' ++ for yStep in xrange( gridPixel[1], segmentSecondPixel[1] + 1 ): ++ if getKeyIsInPixelTableAddValue( ( gridPixel[0], yStep ), pathIndexTable, pixelTable ): ++ return ++ ++def addPointOnPath( path, pathIndex, pixelTable, point, pointIndex, width ): ++ 'Add a point to a path and the pixel table.' ++ pointIndexMinusOne = pointIndex - 1 ++ if pointIndex < len(path) and pointIndexMinusOne >= 0: ++ segmentTable = {} ++ begin = path[ pointIndexMinusOne ] ++ end = path[pointIndex] ++ euclidean.addValueSegmentToPixelTable( begin, end, segmentTable, pathIndex, width ) ++ euclidean.removePixelTableFromPixelTable( segmentTable, pixelTable ) ++ if pointIndexMinusOne >= 0: ++ begin = path[ pointIndexMinusOne ] ++ euclidean.addValueSegmentToPixelTable( begin, point, pixelTable, pathIndex, width ) ++ if pointIndex < len(path): ++ end = path[pointIndex] ++ euclidean.addValueSegmentToPixelTable( point, end, pixelTable, pathIndex, width ) ++ path.insert( pointIndex, point ) ++ ++def addPointOnPathIfFree( path, pathIndex, pixelTable, point, pointIndex, width ): ++ 'Add the closest point to a path, if the point added to a path is free.' ++ if isAddedPointOnPathFree( path, pixelTable, point, pointIndex, width ): ++ addPointOnPath( path, pathIndex, pixelTable, point, pointIndex, width ) ++ ++def addSparseEndpoints(doubleInfillWidth, endpoints, horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey, infillSolidity, removedEndpoints, solidSurfaceThickness, surroundingXIntersections): ++ 'Add sparse endpoints.' ++ segments = horizontalSegmentsDictionary[horizontalSegmentsDictionaryKey] ++ for segment in segments: ++ addSparseEndpointsFromSegment(doubleInfillWidth, endpoints, horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey, infillSolidity, removedEndpoints, segment, solidSurfaceThickness, surroundingXIntersections) ++ ++def addSparseEndpointsFromSegment(doubleInfillWidth, endpoints, horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey, infillSolidity, removedEndpoints, segment, solidSurfaceThickness, surroundingXIntersections): ++ 'Add sparse endpoints from a segment.' ++ if infillSolidity > 0.0: ++ if int(round(round(float(horizontalSegmentsDictionaryKey) * infillSolidity) / infillSolidity)) == horizontalSegmentsDictionaryKey: ++ endpoints += segment ++ return ++ if abs(segment[0].point - segment[1].point) < doubleInfillWidth: ++ endpoints += segment ++ return ++ if not isSegmentAround(horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey - 1, segment): ++ endpoints += segment ++ return ++ if not isSegmentAround(horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey + 1, segment): ++ endpoints += segment ++ return ++ if solidSurfaceThickness == 0: ++ removedEndpoints += segment ++ return ++ if isSegmentCompletelyInAnIntersection(segment, surroundingXIntersections): ++ removedEndpoints += segment ++ return ++ endpoints += segment ++ ++def addYIntersectionPathToList( pathIndex, pointIndex, y, yIntersection, yIntersectionPaths ): ++ 'Add the y intersection path to the y intersection paths.' ++ if yIntersection == None: ++ return ++ yIntersectionPath = YIntersectionPath( pathIndex, pointIndex, yIntersection ) ++ yIntersectionPath.yMinusCenter = yIntersection - y ++ yIntersectionPaths.append( yIntersectionPath ) ++ ++def compareDistanceFromCenter(self, other): ++ 'Get comparison in order to sort y intersections in ascending order of distance from the center.' ++ distanceFromCenter = abs( self.yMinusCenter ) ++ distanceFromCenterOther = abs( other.yMinusCenter ) ++ if distanceFromCenter > distanceFromCenterOther: ++ return 1 ++ if distanceFromCenter < distanceFromCenterOther: ++ return - 1 ++ return 0 ++ ++def comparePointIndexDescending(self, other): ++ 'Get comparison in order to sort y intersections in descending order of point index.' ++ if self.pointIndex > other.pointIndex: ++ return - 1 ++ if self.pointIndex < other.pointIndex: ++ return 1 ++ return 0 ++ ++def createExtraFillLoops(nestedRing, radius, radiusAround, shouldExtraLoopsBeAdded): ++ 'Create extra fill loops.' ++ for innerNestedRing in nestedRing.innerNestedRings: ++ createFillForSurroundings(innerNestedRing.innerNestedRings, radius, radiusAround, shouldExtraLoopsBeAdded) ++ allFillLoops = intercircle.getInsetSeparateLoopsFromAroundLoops(nestedRing.getLoopsToBeFilled(), radius, max(1.4 * radius, radiusAround)) ++ if len(allFillLoops) < 1: ++ return ++ if shouldExtraLoopsBeAdded: ++ nestedRing.extraLoops += allFillLoops ++ nestedRing.penultimateFillLoops = nestedRing.lastFillLoops ++ nestedRing.lastFillLoops = allFillLoops ++ ++def createFillForSurroundings(nestedRings, radius, radiusAround, shouldExtraLoopsBeAdded): ++ 'Create extra fill loops for nested rings.' ++ for nestedRing in nestedRings: ++ createExtraFillLoops(nestedRing, radius, radiusAround, shouldExtraLoopsBeAdded) ++ ++def getAdditionalLength( path, point, pointIndex ): ++ 'Get the additional length added by inserting a point into a path.' ++ if pointIndex == 0: ++ return abs( point - path[0] ) ++ if pointIndex == len(path): ++ return abs( point - path[-1] ) ++ return abs( point - path[pointIndex - 1] ) + abs( point - path[pointIndex] ) - abs( path[pointIndex] - path[pointIndex - 1] ) ++ ++def getClosestOppositeIntersectionPaths( yIntersectionPaths ): ++ 'Get the close to center paths, starting with the first and an additional opposite if it exists.' ++ yIntersectionPaths.sort( compareDistanceFromCenter ) ++ beforeFirst = yIntersectionPaths[0].yMinusCenter < 0.0 ++ yCloseToCenterPaths = [ yIntersectionPaths[0] ] ++ for yIntersectionPath in yIntersectionPaths[1 :]: ++ beforeSecond = yIntersectionPath.yMinusCenter < 0.0 ++ if beforeFirst != beforeSecond: ++ yCloseToCenterPaths.append( yIntersectionPath ) ++ return yCloseToCenterPaths ++ return yCloseToCenterPaths ++ ++def getCraftedText( fileName, gcodeText = '', repository=None): ++ 'Fill the inset file or gcode text.' ++ return getCraftedTextFromText( archive.getTextIfEmpty(fileName, gcodeText), repository ) ++ ++def getCraftedTextFromText(gcodeText, repository=None): ++ 'Fill the inset gcode text.' ++ if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'fill'): ++ return gcodeText ++ if repository == None: ++ repository = settings.getReadRepository( FillRepository() ) ++ if not repository.activateFill.value: ++ return gcodeText ++ return FillSkein().getCraftedGcode( repository, gcodeText ) ++ ++def getKeyIsInPixelTableAddValue( key, pathIndexTable, pixelTable ): ++ 'Determine if the key is in the pixel table, and if it is and if the value is not None add it to the path index table.' ++ if key in pixelTable: ++ value = pixelTable[key] ++ if value != None: ++ pathIndexTable[value] = None ++ return True ++ return False ++ ++def getLowerLeftCorner(nestedRings): ++ 'Get the lower left corner from the nestedRings.' ++ lowerLeftCorner = Vector3() ++ lowestRealPlusImaginary = 987654321.0 ++ for nestedRing in nestedRings: ++ for point in nestedRing.boundary: ++ realPlusImaginary = point.real + point.imag ++ if realPlusImaginary < lowestRealPlusImaginary: ++ lowestRealPlusImaginary = realPlusImaginary ++ lowerLeftCorner.setToXYZ(point.real, point.imag, nestedRing.z) ++ return lowerLeftCorner ++ ++def getNewRepository(): ++ 'Get new repository.' ++ return FillRepository() ++ ++def getNonIntersectingGridPointLine( gridPointInsetX, isJunctionWide, paths, pixelTable, yIntersectionPath, width ): ++ 'Get the points around the grid point that is junction wide that do not intersect.' ++ pointIndexPlusOne = yIntersectionPath.getPointIndexPlusOne() ++ path = yIntersectionPath.getPath(paths) ++ begin = path[ yIntersectionPath.pointIndex ] ++ end = path[ pointIndexPlusOne ] ++ plusMinusSign = getPlusMinusSign( end.real - begin.real ) ++ if isJunctionWide: ++ gridPointXFirst = complex( yIntersectionPath.gridPoint.real - plusMinusSign * gridPointInsetX, yIntersectionPath.gridPoint.imag ) ++ gridPointXSecond = complex( yIntersectionPath.gridPoint.real + plusMinusSign * gridPointInsetX, yIntersectionPath.gridPoint.imag ) ++ if isAddedPointOnPathFree( path, pixelTable, gridPointXSecond, pointIndexPlusOne, width ): ++ if isAddedPointOnPathFree( path, pixelTable, gridPointXFirst, pointIndexPlusOne, width ): ++ return [ gridPointXSecond, gridPointXFirst ] ++ if isAddedPointOnPathFree( path, pixelTable, yIntersectionPath.gridPoint, pointIndexPlusOne, width ): ++ return [ gridPointXSecond, yIntersectionPath.gridPoint ] ++ return [ gridPointXSecond ] ++ if isAddedPointOnPathFree( path, pixelTable, yIntersectionPath.gridPoint, pointIndexPlusOne, width ): ++ return [ yIntersectionPath.gridPoint ] ++ return [] ++ ++def getPlusMinusSign(number): ++ 'Get one if the number is zero or positive else negative one.' ++ if number >= 0.0: ++ return 1.0 ++ return - 1.0 ++ ++def getWithLeastLength( path, point ): ++ 'Insert a point into a path, at the index at which the path would be shortest.' ++ if len(path) < 1: ++ return 0 ++ shortestPointIndex = None ++ shortestAdditionalLength = 999999999987654321.0 ++ for pointIndex in xrange( len(path) + 1 ): ++ additionalLength = getAdditionalLength( path, point, pointIndex ) ++ if additionalLength < shortestAdditionalLength: ++ shortestAdditionalLength = additionalLength ++ shortestPointIndex = pointIndex ++ return shortestPointIndex ++ ++def getYIntersectionInsideYSegment( segmentFirstY, segmentSecondY, beginComplex, endComplex, x ): ++ 'Get the y intersection inside the y segment if it does, else none.' ++ yIntersection = euclidean.getYIntersectionIfExists( beginComplex, endComplex, x ) ++ if yIntersection == None: ++ return None ++ if yIntersection < min( segmentFirstY, segmentSecondY ): ++ return None ++ if yIntersection <= max( segmentFirstY, segmentSecondY ): ++ return yIntersection ++ return None ++ ++def insertGridPointPair( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, paths, pixelTable, yIntersectionPath, width ): ++ 'Insert a pair of points around the grid point is is junction wide, otherwise inset one point.' ++ linePath = getNonIntersectingGridPointLine( gridPointInsetX, isJunctionWide, paths, pixelTable, yIntersectionPath, width ) ++ insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, linePath, paths, pixelTable, yIntersectionPath, width ) ++ ++def insertGridPointPairs( gridPoint, gridPointInsetX, gridPoints, intersectionPathFirst, intersectionPathSecond, isBothOrNone, isJunctionWide, paths, pixelTable, width ): ++ 'Insert a pair of points around a pair of grid points.' ++ gridPointLineFirst = getNonIntersectingGridPointLine( gridPointInsetX, isJunctionWide, paths, pixelTable, intersectionPathFirst, width ) ++ if len( gridPointLineFirst ) < 1: ++ if isBothOrNone: ++ return ++ intersectionPathSecond.gridPoint = gridPoint ++ insertGridPointPair( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, paths, pixelTable, intersectionPathSecond, width ) ++ return ++ gridPointLineSecond = getNonIntersectingGridPointLine( gridPointInsetX, isJunctionWide, paths, pixelTable, intersectionPathSecond, width ) ++ if len( gridPointLineSecond ) > 0: ++ insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, gridPointLineFirst, paths, pixelTable, intersectionPathFirst, width ) ++ insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, gridPointLineSecond, paths, pixelTable, intersectionPathSecond, width ) ++ return ++ if isBothOrNone: ++ return ++ originalGridPointFirst = intersectionPathFirst.gridPoint ++ intersectionPathFirst.gridPoint = gridPoint ++ gridPointLineFirstCenter = getNonIntersectingGridPointLine( gridPointInsetX, isJunctionWide, paths, pixelTable, intersectionPathFirst, width ) ++ if len( gridPointLineFirstCenter ) > 0: ++ insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, gridPointLineFirstCenter, paths, pixelTable, intersectionPathFirst, width ) ++ return ++ intersectionPathFirst.gridPoint = originalGridPointFirst ++ insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, gridPointLineFirst, paths, pixelTable, intersectionPathFirst, width ) ++ ++def insertGridPointPairWithLinePath( gridPoint, gridPointInsetX, gridPoints, isJunctionWide, linePath, paths, pixelTable, yIntersectionPath, width ): ++ 'Insert a pair of points around the grid point is is junction wide, otherwise inset one point.' ++ if len( linePath ) < 1: ++ return ++ if gridPoint in gridPoints: ++ gridPoints.remove( gridPoint ) ++ intersectionBeginPoint = None ++ moreThanInset = 2.1 * gridPointInsetX ++ path = yIntersectionPath.getPath(paths) ++ begin = path[ yIntersectionPath.pointIndex ] ++ end = path[ yIntersectionPath.getPointIndexPlusOne() ] ++ if yIntersectionPath.isOutside: ++ distanceX = end.real - begin.real ++ if abs( distanceX ) > 2.1 * moreThanInset: ++ intersectionBeginXDistance = yIntersectionPath.gridPoint.real - begin.real ++ endIntersectionXDistance = end.real - yIntersectionPath.gridPoint.real ++ intersectionPoint = begin * endIntersectionXDistance / distanceX + end * intersectionBeginXDistance / distanceX ++ distanceYAbsoluteInset = max( abs( yIntersectionPath.gridPoint.imag - intersectionPoint.imag ), moreThanInset ) ++ intersectionEndSegment = end - intersectionPoint ++ intersectionEndSegmentLength = abs( intersectionEndSegment ) ++ if intersectionEndSegmentLength > 1.1 * distanceYAbsoluteInset: ++ intersectionEndPoint = intersectionPoint + intersectionEndSegment * distanceYAbsoluteInset / intersectionEndSegmentLength ++ path.insert( yIntersectionPath.getPointIndexPlusOne(), intersectionEndPoint ) ++ intersectionBeginSegment = begin - intersectionPoint ++ intersectionBeginSegmentLength = abs( intersectionBeginSegment ) ++ if intersectionBeginSegmentLength > 1.1 * distanceYAbsoluteInset: ++ intersectionBeginPoint = intersectionPoint + intersectionBeginSegment * distanceYAbsoluteInset / intersectionBeginSegmentLength ++ for point in linePath: ++ addPointOnPath( path, yIntersectionPath.pathIndex, pixelTable, point, yIntersectionPath.getPointIndexPlusOne(), width ) ++ if intersectionBeginPoint != None: ++ addPointOnPath( path, yIntersectionPath.pathIndex, pixelTable, intersectionBeginPoint, yIntersectionPath.getPointIndexPlusOne(), width ) ++ ++def isAddedPointOnPathFree( path, pixelTable, point, pointIndex, width ): ++ 'Determine if the point added to a path is intersecting the pixel table or the path.' ++ if pointIndex > 0 and pointIndex < len(path): ++ if isSharpCorner( ( path[pointIndex - 1] ), point, ( path[pointIndex] ) ): ++ return False ++ pointIndexMinusOne = pointIndex - 1 ++ if pointIndexMinusOne >= 0: ++ maskTable = {} ++ begin = path[ pointIndexMinusOne ] ++ if pointIndex < len(path): ++ end = path[pointIndex] ++ euclidean.addValueSegmentToPixelTable( begin, end, maskTable, None, width ) ++ segmentTable = {} ++ euclidean.addSegmentToPixelTable( point, begin, segmentTable, 0.0, 2.0, width ) ++ if euclidean.isPixelTableIntersecting( pixelTable, segmentTable, maskTable ): ++ return False ++ if isAddedPointOnPathIntersectingPath( begin, path, point, pointIndexMinusOne ): ++ return False ++ if pointIndex < len(path): ++ maskTable = {} ++ begin = path[pointIndex] ++ if pointIndexMinusOne >= 0: ++ end = path[ pointIndexMinusOne ] ++ euclidean.addValueSegmentToPixelTable( begin, end, maskTable, None, width ) ++ segmentTable = {} ++ euclidean.addSegmentToPixelTable( point, begin, segmentTable, 0.0, 2.0, width ) ++ if euclidean.isPixelTableIntersecting( pixelTable, segmentTable, maskTable ): ++ return False ++ if isAddedPointOnPathIntersectingPath( begin, path, point, pointIndex ): ++ return False ++ return True ++ ++def isAddedPointOnPathIntersectingPath( begin, path, point, pointIndex ): ++ 'Determine if the point added to a path is intersecting the path by checking line intersection.' ++ segment = point - begin ++ segmentLength = abs(segment) ++ if segmentLength <= 0.0: ++ return False ++ normalizedSegment = segment / segmentLength ++ segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag) ++ pointRotated = segmentYMirror * point ++ beginRotated = segmentYMirror * begin ++ if euclidean.isXSegmentIntersectingPath( path[ max( 0, pointIndex - 20 ) : pointIndex ], pointRotated.real, beginRotated.real, segmentYMirror, pointRotated.imag ): ++ return True ++ return euclidean.isXSegmentIntersectingPath( path[ pointIndex + 1 : pointIndex + 21 ], pointRotated.real, beginRotated.real, segmentYMirror, pointRotated.imag ) ++ ++def isIntersectingLoopsPaths( loops, paths, pointBegin, pointEnd ): ++ 'Determine if the segment between the first and second point is intersecting the loop list.' ++ normalizedSegment = pointEnd.dropAxis() - pointBegin.dropAxis() ++ normalizedSegmentLength = abs( normalizedSegment ) ++ if normalizedSegmentLength == 0.0: ++ return False ++ normalizedSegment /= normalizedSegmentLength ++ segmentYMirror = complex(normalizedSegment.real, -normalizedSegment.imag) ++ pointBeginRotated = euclidean.getRoundZAxisByPlaneAngle( segmentYMirror, pointBegin ) ++ pointEndRotated = euclidean.getRoundZAxisByPlaneAngle( segmentYMirror, pointEnd ) ++ if euclidean.isLoopListIntersectingInsideXSegment( loops, pointBeginRotated.real, pointEndRotated.real, segmentYMirror, pointBeginRotated.imag ): ++ return True ++ return euclidean.isXSegmentIntersectingPaths( paths, pointBeginRotated.real, pointEndRotated.real, segmentYMirror, pointBeginRotated.imag ) ++ ++def isPointAddedAroundClosest(layerInfillWidth, paths, pixelTable, removedEndpointPoint, width): ++ 'Add the closest removed endpoint to the path, with minimal twisting.' ++ closestDistanceSquared = 999999999987654321.0 ++ closestPathIndex = None ++ for pathIndex in xrange(len(paths)): ++ path = paths[ pathIndex ] ++ for pointIndex in xrange(len(path)): ++ point = path[pointIndex] ++ distanceSquared = abs(point - removedEndpointPoint) ++ if distanceSquared < closestDistanceSquared: ++ closestDistanceSquared = distanceSquared ++ closestPathIndex = pathIndex ++ if closestPathIndex == None: ++ return ++ if closestDistanceSquared < 0.8 * layerInfillWidth * layerInfillWidth: ++ return ++ closestPath = paths[closestPathIndex] ++ closestPointIndex = getWithLeastLength(closestPath, removedEndpointPoint) ++ if isAddedPointOnPathFree(closestPath, pixelTable, removedEndpointPoint, closestPointIndex, width): ++ addPointOnPath(closestPath, closestPathIndex, pixelTable, removedEndpointPoint, closestPointIndex, width) ++ return True ++ return isSidePointAdded(pixelTable, closestPath, closestPathIndex, closestPointIndex, layerInfillWidth, removedEndpointPoint, width) ++ ++def isSegmentAround(aroundSegmentsDictionary, aroundSegmentsDictionaryKey, segment): ++ 'Determine if there is another segment around.' ++ if aroundSegmentsDictionaryKey not in aroundSegmentsDictionary: ++ return False ++ for aroundSegment in aroundSegmentsDictionary[aroundSegmentsDictionaryKey]: ++ endpoint = aroundSegment[0] ++ if isSegmentInX(segment, endpoint.point.real, endpoint.otherEndpoint.point.real): ++ return True ++ return False ++ ++def isSegmentCompletelyInAnIntersection( segment, xIntersections ): ++ 'Add sparse endpoints from a segment.' ++ for xIntersectionIndex in xrange( 0, len( xIntersections ), 2 ): ++ surroundingXFirst = xIntersections[ xIntersectionIndex ] ++ surroundingXSecond = xIntersections[ xIntersectionIndex + 1 ] ++ if euclidean.isSegmentCompletelyInX( segment, surroundingXFirst, surroundingXSecond ): ++ return True ++ return False ++ ++def isSegmentInX( segment, xFirst, xSecond ): ++ 'Determine if the segment overlaps within x.' ++ segmentFirstX = segment[0].point.real ++ segmentSecondX = segment[1].point.real ++ if min( segmentFirstX, segmentSecondX ) > max( xFirst, xSecond ): ++ return False ++ return max( segmentFirstX, segmentSecondX ) > min( xFirst, xSecond ) ++ ++def isSharpCorner( beginComplex, centerComplex, endComplex ): ++ 'Determine if the three complex points form a sharp corner.' ++ centerBeginComplex = beginComplex - centerComplex ++ centerEndComplex = endComplex - centerComplex ++ centerBeginLength = abs( centerBeginComplex ) ++ centerEndLength = abs( centerEndComplex ) ++ if centerBeginLength <= 0.0 or centerEndLength <= 0.0: ++ return False ++ centerBeginComplex /= centerBeginLength ++ centerEndComplex /= centerEndLength ++ return euclidean.getDotProduct( centerBeginComplex, centerEndComplex ) > 0.9 ++ ++def isSidePointAdded( pixelTable, closestPath, closestPathIndex, closestPointIndex, layerInfillWidth, removedEndpointPoint, width ): ++ 'Add side point along with the closest removed endpoint to the path, with minimal twisting.' ++ if closestPointIndex <= 0 or closestPointIndex >= len( closestPath ): ++ return False ++ pointBegin = closestPath[ closestPointIndex - 1 ] ++ pointEnd = closestPath[ closestPointIndex ] ++ removedEndpointPoint = removedEndpointPoint ++ closest = pointBegin ++ farthest = pointEnd ++ removedMinusClosest = removedEndpointPoint - pointBegin ++ removedMinusClosestLength = abs( removedMinusClosest ) ++ if removedMinusClosestLength <= 0.0: ++ return False ++ removedMinusOther = removedEndpointPoint - pointEnd ++ removedMinusOtherLength = abs( removedMinusOther ) ++ if removedMinusOtherLength <= 0.0: ++ return False ++ insertPointAfter = None ++ insertPointBefore = None ++ if removedMinusOtherLength < removedMinusClosestLength: ++ closest = pointEnd ++ farthest = pointBegin ++ removedMinusClosest = removedMinusOther ++ removedMinusClosestLength = removedMinusOtherLength ++ insertPointBefore = removedEndpointPoint ++ else: ++ insertPointAfter = removedEndpointPoint ++ removedMinusClosestNormalized = removedMinusClosest / removedMinusClosestLength ++ perpendicular = removedMinusClosestNormalized * complex( 0.0, layerInfillWidth ) ++ sidePoint = removedEndpointPoint + perpendicular ++ #extra check in case the line to the side point somehow slips by the line to the perpendicular ++ sidePointOther = removedEndpointPoint - perpendicular ++ if abs( sidePoint - farthest ) > abs( sidePointOther - farthest ): ++ perpendicular = - perpendicular ++ sidePoint = sidePointOther ++ maskTable = {} ++ closestSegmentTable = {} ++ toPerpendicularTable = {} ++ euclidean.addValueSegmentToPixelTable( pointBegin, pointEnd, maskTable, None, width ) ++ euclidean.addValueSegmentToPixelTable( closest, removedEndpointPoint, closestSegmentTable, None, width ) ++ euclidean.addValueSegmentToPixelTable( sidePoint, farthest, toPerpendicularTable, None, width ) ++ if euclidean.isPixelTableIntersecting( pixelTable, toPerpendicularTable, maskTable ) or euclidean.isPixelTableIntersecting( closestSegmentTable, toPerpendicularTable, maskTable ): ++ sidePoint = removedEndpointPoint - perpendicular ++ toPerpendicularTable = {} ++ euclidean.addValueSegmentToPixelTable( sidePoint, farthest, toPerpendicularTable, None, width ) ++ if euclidean.isPixelTableIntersecting( pixelTable, toPerpendicularTable, maskTable ) or euclidean.isPixelTableIntersecting( closestSegmentTable, toPerpendicularTable, maskTable ): ++ return False ++ if insertPointBefore != None: ++ addPointOnPathIfFree( closestPath, closestPathIndex, pixelTable, insertPointBefore, closestPointIndex, width ) ++ addPointOnPathIfFree( closestPath, closestPathIndex, pixelTable, sidePoint, closestPointIndex, width ) ++ if insertPointAfter != None: ++ addPointOnPathIfFree( closestPath, closestPathIndex, pixelTable, insertPointAfter, closestPointIndex, width ) ++ return True ++ ++def removeEndpoints(layerInfillWidth, paths, pixelTable, removedEndpoints, aroundWidth): ++ 'Remove endpoints which are added to the path.' ++ for removedEndpointIndex in xrange(len(removedEndpoints) -1, -1, -1): ++ removedEndpoint = removedEndpoints[removedEndpointIndex] ++ removedEndpointPoint = removedEndpoint.point ++ if isPointAddedAroundClosest(layerInfillWidth, paths, pixelTable, removedEndpointPoint, aroundWidth): ++ removedEndpoints.remove(removedEndpoint ) ++ ++def setIsOutside( yCloseToCenterPath, yIntersectionPaths ): ++ 'Determine if the yCloseToCenterPath is outside.' ++ beforeClose = yCloseToCenterPath.yMinusCenter < 0.0 ++ for yIntersectionPath in yIntersectionPaths: ++ if yIntersectionPath != yCloseToCenterPath: ++ beforePath = yIntersectionPath.yMinusCenter < 0.0 ++ if beforeClose == beforePath: ++ yCloseToCenterPath.isOutside = False ++ return ++ yCloseToCenterPath.isOutside = True ++ ++def writeOutput(fileName, shouldAnalyze=True): ++ 'Fill an inset gcode file.' ++ skeinforge_craft.writeChainTextWithNounMessage(fileName, 'fill', shouldAnalyze) ++ ++ ++class FillRepository: ++ 'A class to handle the fill settings.' ++ def __init__(self): ++ 'Set the default settings, execute title & settings fileName.' ++ skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.fill.html', self ) ++ self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Fill', self, '') ++ self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Fill') ++ self.activateFill = settings.BooleanSetting().getFromValue('Activate Fill', self, True) ++ settings.LabelSeparator().getFromRepository(self) ++ settings.LabelDisplay().getFromName('- Diaphragm -', self ) ++ self.diaphragmPeriod = settings.IntSpin().getFromValue( 20, 'Diaphragm Period (layers):', self, 200, 100 ) ++ self.diaphragmThickness = settings.IntSpin().getFromValue( 0, 'Diaphragm Thickness (layers):', self, 5, 0 ) ++ settings.LabelSeparator().getFromRepository(self) ++ settings.LabelDisplay().getFromName('- Extra Shells -', self ) ++ self.extraShellsAlternatingSolidLayer = settings.IntSpin().getFromValue( 0, 'Extra Shells on Alternating Solid Layer (layers):', self, 3, 2 ) ++ self.extraShellsBase = settings.IntSpin().getFromValue( 0, 'Extra Shells on Base (layers):', self, 3, 1 ) ++ self.extraShellsSparseLayer = settings.IntSpin().getFromValue( 0, 'Extra Shells on Sparse Layer (layers):', self, 3, 1 ) ++ settings.LabelSeparator().getFromRepository(self) ++ settings.LabelDisplay().getFromName('- Grid -', self ) ++ self.gridCircleSeparationOverPerimeterWidth = settings.FloatSpin().getFromValue(0.0, 'Grid Circle Separation over Perimeter Width (ratio):', self, 1.0, 0.2) ++ self.gridExtraOverlap = settings.FloatSpin().getFromValue( 0.0, 'Grid Extra Overlap (ratio):', self, 0.5, 0.1 ) ++ self.gridJunctionSeparationBandHeight = settings.IntSpin().getFromValue( 0, 'Grid Junction Separation Band Height (layers):', self, 20, 10 ) ++ self.gridJunctionSeparationOverOctogonRadiusAtEnd = settings.FloatSpin().getFromValue( 0.0, 'Grid Junction Separation over Octogon Radius At End (ratio):', self, 0.8, 0.0 ) ++ self.gridJunctionSeparationOverOctogonRadiusAtMiddle = settings.FloatSpin().getFromValue( 0.0, 'Grid Junction Separation over Octogon Radius At Middle (ratio):', self, 0.8, 0.0 ) ++ settings.LabelSeparator().getFromRepository(self) ++ settings.LabelDisplay().getFromName('- Infill -', self ) ++ self.infillBeginRotation = settings.FloatSpin().getFromValue( 0.0, 'Infill Begin Rotation (degrees):', self, 90.0, 45.0 ) ++ self.infillBeginRotationRepeat = settings.IntSpin().getFromValue( 0, 'Infill Begin Rotation Repeat (layers):', self, 3, 1 ) ++ self.infillOddLayerExtraRotation = settings.FloatSpin().getFromValue( 30.0, 'Infill Odd Layer Extra Rotation (degrees):', self, 90.0, 90.0 ) ++ self.infillPatternLabel = settings.LabelDisplay().getFromName('Infill Pattern:', self ) ++ infillLatentStringVar = settings.LatentStringVar() ++ self.infillPatternGridCircular = settings.Radio().getFromRadio( infillLatentStringVar, 'Grid Circular', self, False ) ++ self.infillPatternGridHexagonal = settings.Radio().getFromRadio( infillLatentStringVar, 'Grid Hexagonal', self, False ) ++ self.infillPatternGridRectangular = settings.Radio().getFromRadio( infillLatentStringVar, 'Grid Rectangular', self, False ) ++ self.infillPatternLine = settings.Radio().getFromRadio( infillLatentStringVar, 'Line', self, True ) ++ self.infillPerimeterOverlap = settings.FloatSpin().getFromValue( 0.0, 'Infill Perimeter Overlap (ratio):', self, 0.4, 0.15 ) ++ self.infillSolidity = settings.FloatSpin().getFromValue( 0.04, 'Infill Solidity (ratio):', self, 0.3, 0.2 ) ++ self.infillWidthOverThickness = settings.FloatSpin().getFromValue(1.3, 'Infill Width over Thickness (ratio):', self, 1.7, 1.5) ++ settings.LabelSeparator().getFromRepository(self) ++ self.solidSurfaceThickness = settings.IntSpin().getFromValue(0, 'Solid Surface Thickness (layers):', self, 5, 3) ++ self.startFromChoice = settings.MenuButtonDisplay().getFromName('Start From Choice:', self) ++ self.startFromLowerLeft = settings.MenuRadio().getFromMenuButtonDisplay(self.startFromChoice, 'Lower Left', self, True) ++ self.startFromNearest = settings.MenuRadio().getFromMenuButtonDisplay(self.startFromChoice, 'Nearest', self, False) ++ self.surroundingAngle = settings.FloatSpin().getFromValue(30.0, 'Surrounding Angle (degrees):', self, 80.0, 60.0) ++ self.threadSequenceChoice = settings.MenuButtonDisplay().getFromName('Thread Sequence Choice:', self) ++ self.threadSequenceInfillLoops = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Infill > Loops > Perimeter', self, False) ++ self.threadSequenceInfillPerimeter = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Infill > Perimeter > Loops', self, False) ++ self.threadSequenceLoopsInfill = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Loops > Infill > Perimeter', self, False) ++ self.threadSequenceLoopsPerimeter = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Loops > Perimeter > Infill', self, True) ++ self.threadSequencePerimeterInfill = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Perimeter > Infill > Loops', self, False) ++ self.threadSequencePerimeterLoops = settings.MenuRadio().getFromMenuButtonDisplay(self.threadSequenceChoice, 'Perimeter > Loops > Infill', self, False) ++ self.executeTitle = 'Fill' ++ ++ def execute(self): ++ 'Fill button has been clicked.' ++ fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) ++ for fileName in fileNames: ++ writeOutput(fileName) ++ ++ ++class FillSkein: ++ 'A class to fill a skein of extrusions.' ++ def __init__(self): ++ 'Initialize.' ++ self.distanceFeedRate = gcodec.DistanceFeedRate() ++ self.extruderActive = False ++ self.fillInset = 0.18 ++ self.isPerimeter = False ++ self.lastExtraShells = - 1 ++ self.lineIndex = 0 ++ self.oldLocation = None ++ self.oldOrderedLocation = None ++ self.perimeterWidth = None ++ self.rotatedLayer = None ++ self.rotatedLayers = [] ++ self.shutdownLineIndex = sys.maxint ++ self.nestedRing = None ++ self.thread = None ++ ++ def addFill(self, layerIndex): ++ 'Add fill to the carve layer.' ++# if layerIndex > 2: ++# return ++ settings.printProgressByNumber(layerIndex, len(self.rotatedLayers), 'fill') ++ arounds = [] ++ endpoints = [] ++ extraShells = self.repository.extraShellsSparseLayer.value ++ infillPaths = [] ++ layerFillInset = self.fillInset ++ layerInfillSolidity = self.infillSolidity ++ layerRemainder = layerIndex % int(round(self.repository.diaphragmPeriod.value)) ++ layerRotation = self.getLayerRotation(layerIndex) ++ pixelTable = {} ++ reverseRotation = complex(layerRotation.real, - layerRotation.imag) ++ rotatedLayer = self.rotatedLayers[layerIndex] ++ self.isDoubleJunction = True ++ self.isJunctionWide = True ++ surroundingCarves = [] ++ self.distanceFeedRate.addLine('( %s )' % rotatedLayer.z) ++ if layerRemainder >= int(round(self.repository.diaphragmThickness.value)): ++ for surroundingIndex in xrange(1, self.solidSurfaceThickness + 1): ++ self.addRotatedCarve(layerIndex, -surroundingIndex, reverseRotation, surroundingCarves) ++ self.addRotatedCarve(layerIndex, surroundingIndex, reverseRotation, surroundingCarves) ++ if len(surroundingCarves) < self.doubleSolidSurfaceThickness: ++ extraShells = self.repository.extraShellsAlternatingSolidLayer.value ++ if self.lastExtraShells != self.repository.extraShellsBase.value: ++ extraShells = self.repository.extraShellsBase.value ++ if rotatedLayer.rotation != None: ++ extraShells = 0 ++ self.distanceFeedRate.addLine('( %s )' % layerRotation) ++ aroundWidth = 0.34321 * self.infillWidth ++ doubleInfillWidth = 2.0 * self.infillWidth ++ gridPointInsetX = 0.5 * self.fillInset ++ self.lastExtraShells = extraShells ++ if self.repository.infillPatternGridHexagonal.value: ++ infillBeginRotationPolar = euclidean.getWiddershinsUnitPolar(self.infillBeginRotation) ++ if abs(euclidean.getDotProduct(layerRotation, infillBeginRotationPolar)) < math.sqrt( 0.5): ++ layerInfillSolidity *= 0.5 ++ self.isDoubleJunction = False ++ else: ++ self.isJunctionWide = False ++ nestedRings = euclidean.getOrderedNestedRings(rotatedLayer.nestedRings) ++ radiusAround = 0.5 * min(self.infillWidth, self.perimeterWidth) ++ createFillForSurroundings(nestedRings, self.perimeterMinusHalfInfillWidth, radiusAround, False) ++ for extraShellIndex in xrange(extraShells): ++ createFillForSurroundings(nestedRings, self.infillWidth, radiusAround, True) ++ fillLoops = euclidean.getFillOfSurroundings(nestedRings, None) ++ rotatedLoops = euclidean.getRotatedComplexLists(reverseRotation, fillLoops) ++ infillDictionary = triangle_mesh.getInfillDictionary(arounds, aroundWidth, self.fillInset, self.infillWidth, pixelTable, rotatedLoops) ++ if len(arounds) < 1: ++ self.addThreadsBridgeLayer(layerIndex, nestedRings, rotatedLayer) ++ return ++ self.horizontalSegmentsDictionary = {} ++ for infillDictionaryKey in infillDictionary.keys(): ++ xIntersections = infillDictionary[infillDictionaryKey] ++ xIntersections.sort() ++ y = infillDictionaryKey * self.infillWidth ++ self.horizontalSegmentsDictionary[infillDictionaryKey] = euclidean.getSegmentsFromXIntersections(xIntersections, y) ++ self.surroundingXIntersectionsDictionary = {} ++ gridCircular = False ++ removedEndpoints = [] ++ if len(surroundingCarves) >= self.doubleSolidSurfaceThickness: ++ if self.repository.infillPatternGridCircular.value and self.repository.infillSolidity.value > 0.0: ++ gridCircular = True ++ layerInfillSolidity = 0.0 ++ xSurroundingIntersectionsDictionaries = [infillDictionary] ++ for surroundingCarve in surroundingCarves: ++ xSurroundingIntersectionsDictionary = {} ++ euclidean.addXIntersectionsFromLoopsForTable(surroundingCarve, xSurroundingIntersectionsDictionary, self.infillWidth) ++ xSurroundingIntersectionsDictionaries.append(xSurroundingIntersectionsDictionary) ++ self.surroundingXIntersectionsDictionary = euclidean.getIntersectionOfXIntersectionsTables(xSurroundingIntersectionsDictionaries) ++ for horizontalSegmentsDictionaryKey in self.horizontalSegmentsDictionary.keys(): ++ if horizontalSegmentsDictionaryKey in self.surroundingXIntersectionsDictionary: ++ surroundingXIntersections = self.surroundingXIntersectionsDictionary[horizontalSegmentsDictionaryKey] ++ else: ++ surroundingXIntersections = [] ++ addSparseEndpoints(doubleInfillWidth, endpoints, self.horizontalSegmentsDictionary, horizontalSegmentsDictionaryKey, layerInfillSolidity, removedEndpoints, self.solidSurfaceThickness, surroundingXIntersections) ++ else: ++ for segments in self.horizontalSegmentsDictionary.values(): ++ for segment in segments: ++ endpoints += segment ++ paths = euclidean.getPathsFromEndpoints(endpoints, 5.0 * self.infillWidth, pixelTable, aroundWidth) ++ if gridCircular: ++ startAngle = euclidean.globalGoldenAngle * float(layerIndex) ++ for gridPoint in self.getGridPoints(fillLoops, reverseRotation): ++ self.addGridCircle(gridPoint, infillPaths, layerRotation, pixelTable, rotatedLoops, layerRotation, aroundWidth) ++ else: ++ if self.isGridToBeExtruded(): ++ self.addGrid( ++ arounds, fillLoops, gridPointInsetX, layerIndex, paths, pixelTable, reverseRotation, surroundingCarves, aroundWidth) ++ oldRemovedEndpointLength = len(removedEndpoints) + 1 ++ while oldRemovedEndpointLength - len(removedEndpoints) > 0: ++ oldRemovedEndpointLength = len(removedEndpoints) ++ removeEndpoints(self.infillWidth, paths, pixelTable, removedEndpoints, aroundWidth) ++ paths = euclidean.getConnectedPaths(paths, pixelTable, aroundWidth) ++ for path in paths: ++ addPath(self.infillWidth, infillPaths, path, layerRotation) ++ euclidean.transferPathsToNestedRings(nestedRings, infillPaths) ++ for fillLoop in fillLoops: ++ addInfillBoundary(fillLoop, nestedRings) ++ self.addThreadsBridgeLayer(layerIndex, nestedRings, rotatedLayer) ++ ++ def addGcodeFromThreadZ( self, thread, z ): ++ 'Add a gcode thread to the output.' ++ self.distanceFeedRate.addGcodeFromThreadZ( thread, z ) ++ ++ def addGrid(self, arounds, fillLoops, gridPointInsetX, layerIndex, paths, pixelTable, reverseRotation, surroundingCarves, width): ++ 'Add the grid to the infill layer.' ++ if len(surroundingCarves) < self.doubleSolidSurfaceThickness: ++ return ++ explodedPaths = [] ++ pathGroups = [] ++ for path in paths: ++ pathIndexBegin = len( explodedPaths ) ++ for pointIndex in xrange( len(path) - 1 ): ++ pathSegment = [ path[pointIndex], path[pointIndex + 1] ] ++ explodedPaths.append( pathSegment ) ++ pathGroups.append( ( pathIndexBegin, len( explodedPaths ) ) ) ++ for pathIndex in xrange( len( explodedPaths ) ): ++ explodedPath = explodedPaths[ pathIndex ] ++ euclidean.addPathToPixelTable( explodedPath, pixelTable, pathIndex, width ) ++ gridPoints = self.getGridPoints(fillLoops, reverseRotation) ++ gridPointInsetY = gridPointInsetX * ( 1.0 - self.repository.gridExtraOverlap.value ) ++ if self.repository.infillPatternGridRectangular.value: ++ gridBandHeight = self.repository.gridJunctionSeparationBandHeight.value ++ gridLayerRemainder = ( layerIndex - self.solidSurfaceThickness ) % gridBandHeight ++ halfBandHeight = 0.5 * float( gridBandHeight ) ++ halfBandHeightFloor = math.floor( halfBandHeight ) ++ fromMiddle = math.floor( abs( gridLayerRemainder - halfBandHeight ) ) ++ fromEnd = halfBandHeightFloor - fromMiddle ++ gridJunctionSeparation = self.gridJunctionEnd * fromMiddle + self.gridJunctionMiddle * fromEnd ++ gridJunctionSeparation /= halfBandHeightFloor ++ gridPointInsetX += gridJunctionSeparation ++ gridPointInsetY += gridJunctionSeparation ++ oldGridPointLength = len( gridPoints ) + 1 ++ while oldGridPointLength - len( gridPoints ) > 0: ++ oldGridPointLength = len( gridPoints ) ++ self.addRemainingGridPoints( arounds, gridPointInsetX, gridPointInsetY, gridPoints, True, explodedPaths, pixelTable, width ) ++ oldGridPointLength = len( gridPoints ) + 1 ++ while oldGridPointLength - len( gridPoints ) > 0: ++ oldGridPointLength = len( gridPoints ) ++ self.addRemainingGridPoints( arounds, gridPointInsetX, gridPointInsetY, gridPoints, False, explodedPaths, pixelTable, width ) ++ for pathGroupIndex in xrange( len( pathGroups ) ): ++ pathGroup = pathGroups[ pathGroupIndex ] ++ paths[ pathGroupIndex ] = [] ++ for explodedPathIndex in xrange( pathGroup[0], pathGroup[1] ): ++ explodedPath = explodedPaths[ explodedPathIndex ] ++ if len( paths[ pathGroupIndex ] ) == 0: ++ paths[ pathGroupIndex ] = explodedPath ++ else: ++ paths[ pathGroupIndex ] += explodedPath[1 :] ++ ++ def addGridCircle(self, center, infillPaths, layerRotation, pixelTable, rotatedLoops, startRotation, width): ++ 'Add circle to the grid.' ++ startAngle = -math.atan2(startRotation.imag, startRotation.real) ++ loop = euclidean.getComplexPolygon(center, self.gridCircleRadius, 17, startAngle) ++ loopPixelDictionary = {} ++ euclidean.addLoopToPixelTable(loop, loopPixelDictionary, width) ++ if not euclidean.isPixelTableIntersecting(pixelTable, loopPixelDictionary): ++ if euclidean.getIsInFilledRegion(rotatedLoops, euclidean.getLeftPoint(loop)): ++ addLoop(self.infillWidth, infillPaths, loop, layerRotation) ++ return ++ insideIndexPaths = [] ++ insideIndexPath = None ++ for pointIndex, point in enumerate(loop): ++ nextPoint = loop[(pointIndex + 1) % len(loop)] ++ segmentDictionary = {} ++ euclidean.addValueSegmentToPixelTable(point, nextPoint, segmentDictionary, None, width) ++ euclidean.addSquareTwoToPixelDictionary(segmentDictionary, point, None, width) ++ euclidean.addSquareTwoToPixelDictionary(segmentDictionary, nextPoint, None, width) ++ shouldAddLoop = not euclidean.isPixelTableIntersecting(pixelTable, segmentDictionary) ++ if shouldAddLoop: ++ shouldAddLoop = euclidean.getIsInFilledRegion(rotatedLoops, point) ++ if shouldAddLoop: ++ if insideIndexPath == None: ++ insideIndexPath = [pointIndex] ++ insideIndexPaths.append(insideIndexPath) ++ else: ++ insideIndexPath.append(pointIndex) ++ else: ++ insideIndexPath = None ++ if len(insideIndexPaths) > 1: ++ insideIndexPathFirst = insideIndexPaths[0] ++ insideIndexPathLast = insideIndexPaths[-1] ++ if insideIndexPathFirst[0] == 0 and insideIndexPathLast[-1] == len(loop) - 1: ++ insideIndexPaths[0] = insideIndexPathLast + insideIndexPathFirst ++ del insideIndexPaths[-1] ++ for insideIndexPath in insideIndexPaths: ++ path = [] ++ for insideIndex in insideIndexPath: ++ if len(path) == 0: ++ path.append(loop[insideIndex]) ++ path.append(loop[(insideIndex + 1) % len(loop)]) ++ addPath(self.infillWidth, infillPaths, path, layerRotation) ++ ++ def addGridLinePoints( self, begin, end, gridPoints, gridRotationAngle, offset, y ): ++ 'Add the segments of one line of a grid to the infill.' ++ if self.gridRadius == 0.0: ++ return ++ gridXStep = int(math.floor((begin) / self.gridXStepSize)) - 3 ++ gridXOffset = offset + self.gridXStepSize * float(gridXStep) ++ while gridXOffset < end: ++ if gridXOffset >= begin: ++ gridPointComplex = complex(gridXOffset, y) * gridRotationAngle ++ if self.repository.infillPatternGridCircular.value or self.isPointInsideLineSegments(gridPointComplex): ++ gridPoints.append(gridPointComplex) ++ gridXStep = self.getNextGripXStep(gridXStep) ++ gridXOffset = offset + self.gridXStepSize * float(gridXStep) ++ ++ def addRemainingGridPoints( ++ self, arounds, gridPointInsetX, gridPointInsetY, gridPoints, isBothOrNone, paths, pixelTable, width): ++ 'Add the remaining grid points to the grid point list.' ++ for gridPointIndex in xrange( len( gridPoints ) - 1, - 1, - 1 ): ++ gridPoint = gridPoints[ gridPointIndex ] ++ addAroundGridPoint( arounds, gridPoint, gridPointInsetX, gridPointInsetY, gridPoints, self.gridRadius, isBothOrNone, self.isDoubleJunction, self.isJunctionWide, paths, pixelTable, width ) ++ ++ def addRotatedCarve(self, currentLayer, layerDelta, reverseRotation, surroundingCarves): ++ 'Add a rotated carve to the surrounding carves.rotatedCarveDictionary' ++ layerIndex = currentLayer + layerDelta ++ if layerIndex < 0 or layerIndex >= len(self.rotatedLayers): ++ return ++ layerDifference = abs(layerDelta) ++ rotatedLayer = self.rotatedLayers[layerIndex] ++ if layerDifference in rotatedLayer.rotatedCarveDictionary: ++ surroundingCarves.append(rotatedLayer.rotatedCarveDictionary[layerDifference]) ++ return ++ nestedRings = rotatedLayer.nestedRings ++ rotatedCarve = [] ++ for nestedRing in nestedRings: ++ planeRotatedLoop = euclidean.getRotatedComplexes(reverseRotation, nestedRing.boundary) ++ rotatedCarve.append(planeRotatedLoop) ++ outsetRadius = float(layerDifference) * self.layerThickness * self.surroundingSlope - self.perimeterWidth ++ if outsetRadius > 0.0: ++ rotatedCarve = intercircle.getInsetSeparateLoopsFromAroundLoops(rotatedCarve, -outsetRadius, self.layerThickness) ++ surroundingCarves.append(rotatedCarve) ++ rotatedLayer.rotatedCarveDictionary[layerDifference] = rotatedCarve ++ ++ def addThreadsBridgeLayer(self, layerIndex, nestedRings, rotatedLayer, testLoops=None): ++ 'Add the threads, add the bridge end & the layer end tag.' ++ if self.oldOrderedLocation == None or self.repository.startFromLowerLeft.value: ++ self.oldOrderedLocation = getLowerLeftCorner(nestedRings) ++ extrusionHalfWidth = 0.5 * self.infillWidth ++ threadSequence = self.threadSequence ++ if layerIndex < 1: ++ threadSequence = ['perimeter', 'loops', 'infill'] ++ euclidean.addToThreadsRemove(extrusionHalfWidth, nestedRings, self.oldOrderedLocation, self, threadSequence) ++ if testLoops != None: ++ for testLoop in testLoops: ++ self.addGcodeFromThreadZ(testLoop, self.oldOrderedLocation.z) ++ self.distanceFeedRate.addLine('()') ++ if rotatedLayer.rotation != None: ++ self.distanceFeedRate.addLine('()') ++ self.distanceFeedRate.addLine('()') ++ ++ def addToThread(self, location): ++ 'Add a location to thread.' ++ if self.oldLocation == None: ++ return ++ if self.isPerimeter: ++ self.nestedRing.addToLoop( location ) ++ return ++ if self.thread == None: ++ self.thread = [ self.oldLocation.dropAxis() ] ++ self.nestedRing.perimeterPaths.append(self.thread) ++ self.thread.append(location.dropAxis()) ++ ++ def getCraftedGcode( self, repository, gcodeText ): ++ 'Parse gcode text and store the bevel gcode.' ++ self.repository = repository ++ self.lines = archive.getTextLines(gcodeText) ++ self.threadSequence = None ++ if repository.threadSequenceInfillLoops.value: ++ self.threadSequence = ['infill', 'loops', 'perimeter'] ++ if repository.threadSequenceInfillPerimeter.value: ++ self.threadSequence = ['infill', 'perimeter', 'loops'] ++ if repository.threadSequenceLoopsInfill.value: ++ self.threadSequence = ['loops', 'infill', 'perimeter'] ++ if repository.threadSequenceLoopsPerimeter.value: ++ self.threadSequence = ['loops', 'perimeter', 'infill'] ++ if repository.threadSequencePerimeterInfill.value: ++ self.threadSequence = ['perimeter', 'infill', 'loops'] ++ if repository.threadSequencePerimeterLoops.value: ++ self.threadSequence = ['perimeter', 'loops', 'infill'] ++ if self.repository.infillPerimeterOverlap.value > 0.45: ++ print('') ++ print('!!! WARNING !!!') ++ print('"Infill Perimeter Overlap" is greater than 0.45, which may create problems with the infill, like threads going through empty space and/or the extruder switching on and off a lot.') ++ print('If you want to stretch the infill a lot, set "Path Stretch over Perimeter Width" in stretch to a high value instead of setting "Infill Perimeter Overlap" to a high value.') ++ print('') ++ self.parseInitialization() ++ if self.perimeterWidth == None: ++ print('Warning, nothing will be done because self.perimeterWidth in getCraftedGcode in FillSkein was None.') ++ return '' ++ self.fillInset = self.infillWidth - self.infillWidth * self.repository.infillPerimeterOverlap.value ++ self.infillSolidity = repository.infillSolidity.value ++ self.perimeterMinusHalfInfillWidth = self.perimeterWidth - 0.5 * self.infillWidth ++ if self.isGridToBeExtruded(): ++ self.setGridVariables(repository) ++ self.infillBeginRotation = math.radians( repository.infillBeginRotation.value ) ++ self.infillOddLayerExtraRotation = math.radians( repository.infillOddLayerExtraRotation.value ) ++ self.solidSurfaceThickness = int( round( self.repository.solidSurfaceThickness.value ) ) ++ self.doubleSolidSurfaceThickness = self.solidSurfaceThickness + self.solidSurfaceThickness ++ for lineIndex in xrange(self.lineIndex, len(self.lines)): ++ self.parseLine( lineIndex ) ++ for layerIndex in xrange(len(self.rotatedLayers)): ++ self.addFill(layerIndex) ++ self.distanceFeedRate.addLines( self.lines[ self.shutdownLineIndex : ] ) ++ return self.distanceFeedRate.output.getvalue() ++ ++ def getGridPoints(self, fillLoops, reverseRotation): ++ 'Get the grid points.' ++ if self.infillSolidity > 0.8: ++ return [] ++ rotationBaseAngle = euclidean.getWiddershinsUnitPolar(self.infillBeginRotation) ++ reverseRotationBaseAngle = complex(rotationBaseAngle.real, - rotationBaseAngle.imag) ++ gridRotationAngle = reverseRotation * rotationBaseAngle ++ slightlyGreaterThanFillInset = intercircle.globalIntercircleMultiplier * self.gridInset ++ triangle_mesh.sortLoopsInOrderOfArea(True, fillLoops) ++ rotatedLoops = euclidean.getRotatedComplexLists(reverseRotationBaseAngle, fillLoops) ++ if self.repository.infillPatternGridCircular.value: ++ return self.getGridPointsByLoops( ++ gridRotationAngle, intercircle.getInsetSeparateLoopsFromLoops(rotatedLoops, -self.gridCircleRadius)) ++ return self.getGridPointsByLoops(gridRotationAngle, intercircle.getInsetSeparateLoopsFromLoops(rotatedLoops, self.gridInset)) ++ ++ def getGridPointsByLoops(self, gridRotationAngle, loops): ++ 'Get the grid points by loops.' ++ gridIntersectionsDictionary = {} ++ gridPoints = [] ++ euclidean.addXIntersectionsFromLoopsForTable(loops, gridIntersectionsDictionary, self.gridRadius) ++ for gridIntersectionsKey in gridIntersectionsDictionary: ++ y = gridIntersectionsKey * self.gridRadius + self.gridRadius * 0.5 ++ gridIntersections = gridIntersectionsDictionary[gridIntersectionsKey] ++ gridIntersections.sort() ++ gridIntersectionsLength = len(gridIntersections) ++ if gridIntersectionsLength % 2 == 1: ++ gridIntersectionsLength -= 1 ++ for gridIntersectionIndex in xrange(0, gridIntersectionsLength, 2): ++ begin = gridIntersections[gridIntersectionIndex] ++ end = gridIntersections[gridIntersectionIndex + 1] ++ offset = self.offsetMultiplier * (gridIntersectionsKey % 2) + self.offsetBaseX ++ self.addGridLinePoints(begin, end, gridPoints, gridRotationAngle, offset, y) ++ return gridPoints ++ ++ def getLayerRotation(self, layerIndex): ++ 'Get the layer rotation.' ++ rotation = self.rotatedLayers[layerIndex].rotation ++ if rotation != None: ++ return rotation ++ infillBeginRotationRepeat = self.repository.infillBeginRotationRepeat.value ++ infillOddLayerRotationMultiplier = float( layerIndex % ( infillBeginRotationRepeat + 1 ) == infillBeginRotationRepeat ) ++ layerAngle = self.infillBeginRotation + infillOddLayerRotationMultiplier * self.infillOddLayerExtraRotation ++ return euclidean.getWiddershinsUnitPolar(layerAngle) ++ ++ def getNextGripXStep( self, gridXStep ): ++ 'Get the next grid x step, increment by an extra one every three if hexagonal grid is chosen.' ++ gridXStep += 1 ++ if self.repository.infillPatternGridHexagonal.value: ++ if gridXStep % 3 == 0: ++ gridXStep += 1 ++ return gridXStep ++ ++ def isGridToBeExtruded(self): ++ 'Determine if the grid is to be extruded.' ++ if self.repository.infillPatternLine.value: ++ return False ++ return self.repository.infillSolidity.value > 0.0 ++ ++ def isPointInsideLineSegments( self, gridPoint ): ++ 'Is the point inside the line segments of the loops.' ++ if self.solidSurfaceThickness <= 0: ++ return True ++ fillLine = int(round(gridPoint.imag / self.infillWidth)) ++ if fillLine not in self.horizontalSegmentsDictionary: ++ return False ++ if fillLine not in self.surroundingXIntersectionsDictionary: ++ return False ++ lineSegments = self.horizontalSegmentsDictionary[fillLine] ++ surroundingXIntersections = self.surroundingXIntersectionsDictionary[fillLine] ++ for lineSegment in lineSegments: ++ if isSegmentCompletelyInAnIntersection(lineSegment, surroundingXIntersections ): ++ xFirst = lineSegment[0].point.real ++ xSecond = lineSegment[1].point.real ++ if gridPoint.real > min(xFirst, xSecond) and gridPoint.real < max(xFirst, xSecond): ++ return True ++ return False ++ ++ def linearMove( self, splitLine ): ++ 'Add a linear move to the thread.' ++ location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) ++ if self.extruderActive: ++ self.addToThread( location ) ++ self.oldLocation = location ++ ++ 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(line) ++ return ++ elif firstWord == '(': ++ self.layerThickness = float(splitLine[1]) ++ self.infillWidth = self.repository.infillWidthOverThickness.value * self.layerThickness ++ self.surroundingSlope = math.tan(math.radians(min(self.repository.surroundingAngle.value, 80.0))) ++ self.distanceFeedRate.addTagRoundedLine('infillPerimeterOverlap', self.repository.infillPerimeterOverlap.value) ++ self.distanceFeedRate.addTagRoundedLine('infillWidth', self.infillWidth) ++ elif firstWord == '(': ++ self.perimeterWidth = float(splitLine[1]) ++ threadSequenceString = ' '.join( self.threadSequence ) ++ self.distanceFeedRate.addTagBracketedLine('threadSequenceString', threadSequenceString ) ++ elif firstWord == '()': ++ self.distanceFeedRate.addTagBracketedProcedure('fill') ++ self.distanceFeedRate.addLine(line) ++ ++ def parseLine( self, lineIndex ): ++ 'Parse a gcode line and add it to the fill skein.' ++ line = self.lines[lineIndex] ++ splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) ++ if len(splitLine) < 1: ++ return ++ firstWord = splitLine[0] ++ if firstWord == 'G1': ++ self.linearMove(splitLine) ++ elif firstWord == 'M101': ++ self.extruderActive = True ++ elif firstWord == 'M103': ++ self.extruderActive = False ++ self.thread = None ++ self.isPerimeter = False ++ elif firstWord == '()': ++ self.nestedRing = euclidean.NestedBand() ++ self.rotatedLayer.nestedRings.append( self.nestedRing ) ++ elif firstWord == '()': ++ self.nestedRing = None ++ elif firstWord == '(': ++ location = gcodec.getLocationFromSplitLine(None, splitLine) ++ self.nestedRing.addToBoundary( location ) ++ elif firstWord == '(': ++ self.rotatedLayer.rotation = gcodec.getRotationBySplitLine(splitLine) ++ elif firstWord == '()': ++ self.shutdownLineIndex = lineIndex ++ elif firstWord == '(': ++ self.rotatedLayer = RotatedLayer(float(splitLine[1])) ++ self.rotatedLayers.append( self.rotatedLayer ) ++ self.thread = None ++ elif firstWord == '(': ++ self.isPerimeter = True ++ ++ def setGridVariables( self, repository ): ++ 'Set the grid variables.' ++ self.gridInset = 1.2 * self.infillWidth ++ self.gridRadius = self.infillWidth / self.infillSolidity ++ self.gridXStepSize = 2.0 * self.gridRadius ++ self.offsetMultiplier = self.gridRadius ++ if self.repository.infillPatternGridHexagonal.value: ++ self.gridXStepSize = 4.0 / 3.0 * self.gridRadius ++ self.offsetMultiplier = 1.5 * self.gridXStepSize ++ if self.repository.infillPatternGridCircular.value: ++ self.gridRadius += self.gridRadius ++ self.gridXStepSize = self.gridRadius / math.sqrt(.75) ++ self.offsetMultiplier = 0.5 * self.gridXStepSize ++ circleInsetOverPerimeterWidth = repository.gridCircleSeparationOverPerimeterWidth.value + 0.5 ++ self.gridMinimumCircleRadius = self.perimeterWidth ++ self.gridInset = self.gridMinimumCircleRadius ++ self.gridCircleRadius = self.offsetMultiplier - circleInsetOverPerimeterWidth * self.perimeterWidth ++ if self.gridCircleRadius < self.gridMinimumCircleRadius: ++ print('') ++ print('!!! WARNING !!!') ++ print('Grid Circle Separation over Perimeter Width is too high, which makes the grid circles too small.') ++ print('You should reduce Grid Circle Separation over Perimeter Width to a reasonable value, like the default of 0.5.') ++ print('The grid circle radius will be set to the minimum grid circle radius.') ++ print('') ++ self.gridCircleRadius = self.gridMinimumCircleRadius ++ self.offsetBaseX = 0.25 * self.gridXStepSize ++ if self.repository.infillPatternGridRectangular.value: ++ halfGridMinusWidth = 0.5 * ( self.gridRadius - self.infillWidth ) ++ self.gridJunctionEnd = halfGridMinusWidth * repository.gridJunctionSeparationOverOctogonRadiusAtEnd.value ++ self.gridJunctionMiddle = halfGridMinusWidth * repository.gridJunctionSeparationOverOctogonRadiusAtMiddle.value ++ ++ ++class RotatedLayer: ++ 'A rotated layer.' ++ def __init__( self, z ): ++ 'Initialize.' ++ self.rotatedCarveDictionary = {} ++ self.rotation = None ++ self.nestedRings = [] ++ self.z = z ++ ++ def __repr__(self): ++ 'Get the string representation of this RotatedLayer.' ++ return '%s, %s, %s' % ( self.z, self.rotation, self.nestedRings ) ++ ++ ++class YIntersectionPath: ++ 'A class to hold the y intersection position, the loop which it intersected and the point index of the loop which it intersected.' ++ def __init__( self, pathIndex, pointIndex, y ): ++ 'Initialize from the path, point index, and y.' ++ self.pathIndex = pathIndex ++ self.pointIndex = pointIndex ++ self.y = y ++ ++ def __repr__(self): ++ 'Get the string representation of this y intersection.' ++ return '%s, %s, %s' % ( self.pathIndex, self.pointIndex, self.y ) ++ ++ def getPath( self, paths ): ++ 'Get the path from the paths and path index.' ++ return paths[ self.pathIndex ] ++ ++ def getPointIndexPlusOne(self): ++ 'Get the point index plus one.' ++ return self.pointIndex + 1 ++ ++ ++def main(): ++ 'Display the fill dialog.' ++ if len(sys.argv) > 1: ++ writeOutput(' '.join(sys.argv[1 :])) ++ else: ++ settings.startMainLoopFromConstructor(getNewRepository()) ++ ++if __name__ == '__main__': ++ main() +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/home.py ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/home.py +@@ -82,10 +82,10 @@ + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.home.html', self) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Home', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Home') +- self.activateHome = settings.BooleanSetting().getFromValue('Activate Home', self, True ) ++ self.activateHome = settings.BooleanSetting().getFromValue('Activate Home', self, False ) + self.nameOfHomeFile = settings.StringSetting().getFromValue('Name of Home File:', self, 'home.gcode') + self.executeTitle = 'Home' +- ++ + def execute(self): + "Home button has been clicked." + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/home.py.orig ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/home.py.orig +@@ -0,0 +1,198 @@ ++""" ++This page is in the table of contents. ++Plugin to home the tool at beginning of each layer. ++ ++The home manual page is at: ++http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Home ++ ++==Operation== ++The default 'Activate Home' checkbox is on. When it is on, the functions described below will work, when it is off, nothing will be done. ++ ++==Settings== ++===Name of Home File=== ++Default: home.gcode ++ ++At the beginning of a each layer, home will add the commands of a gcode script with the name of the "Name of Home File" setting, if one exists. Home 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. Home looks for those files in the alterations folder in the .skeinforge folder in the home directory. If it doesn't find the file it then looks in the alterations folder in the skeinforge_plugins folder. ++ ++==Examples== ++The following examples home the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and home.py. ++ ++> python home.py ++This brings up the home dialog. ++ ++> python home.py Screw Holder Bottom.stl ++The home tool is parsing the file: ++Screw Holder Bottom.stl ++.. ++The home tool has created the file: ++.. Screw Holder Bottom_home.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 fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret ++from fabmetheus_utilities.vector3 import Vector3 ++from fabmetheus_utilities import archive ++from fabmetheus_utilities import euclidean ++from fabmetheus_utilities import gcodec ++from fabmetheus_utilities import settings ++from skeinforge_application.skeinforge_utilities import skeinforge_craft ++from skeinforge_application.skeinforge_utilities import skeinforge_polyfile ++from skeinforge_application.skeinforge_utilities import skeinforge_profile ++import math ++import os ++import sys ++ ++ ++__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' ++__date__ = '$Date: 2008/21/04 $' ++__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' ++ ++ ++def getCraftedText( fileName, text, repository = None ): ++ "Home a gcode linear move file or text." ++ return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository) ++ ++def getCraftedTextFromText( gcodeText, repository = None ): ++ "Home a gcode linear move text." ++ if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'home'): ++ return gcodeText ++ if repository == None: ++ repository = settings.getReadRepository( HomeRepository() ) ++ if not repository.activateHome.value: ++ return gcodeText ++ return HomeSkein().getCraftedGcode(gcodeText, repository) ++ ++def getNewRepository(): ++ 'Get new repository.' ++ return HomeRepository() ++ ++def writeOutput(fileName, shouldAnalyze=True): ++ "Home a gcode linear move file. Chain home the gcode if it is not already homed." ++ skeinforge_craft.writeChainTextWithNounMessage(fileName, 'home', shouldAnalyze) ++ ++ ++class HomeRepository: ++ "A class to handle the home settings." ++ def __init__(self): ++ "Set the default settings, execute title & settings fileName." ++ skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.home.html', self) ++ self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Home', self, '') ++ self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Home') ++ self.activateHome = settings.BooleanSetting().getFromValue('Activate Home', self, True ) ++ self.nameOfHomeFile = settings.StringSetting().getFromValue('Name of Home File:', self, 'home.gcode') ++ self.executeTitle = 'Home' ++ ++ def execute(self): ++ "Home button has been clicked." ++ fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) ++ for fileName in fileNames: ++ writeOutput(fileName) ++ ++ ++class HomeSkein: ++ "A class to home a skein of extrusions." ++ def __init__(self): ++ self.distanceFeedRate = gcodec.DistanceFeedRate() ++ self.extruderActive = False ++ self.highestZ = None ++ self.homeLines = [] ++ self.layerCount = settings.LayerCount() ++ self.lineIndex = 0 ++ self.lines = None ++ self.oldLocation = None ++ self.shouldHome = False ++ self.travelFeedRateMinute = 957.0 ++ ++ def addFloat( self, begin, end ): ++ "Add dive to the original height." ++ beginEndDistance = begin.distance(end) ++ alongWay = self.absolutePerimeterWidth / beginEndDistance ++ closeToEnd = euclidean.getIntermediateLocation( alongWay, end, begin ) ++ closeToEnd.z = self.highestZ ++ self.distanceFeedRate.addLine( self.distanceFeedRate.getLinearGcodeMovementWithFeedRate( self.travelFeedRateMinute, closeToEnd.dropAxis(), closeToEnd.z ) ) ++ ++ def addHomeTravel( self, splitLine ): ++ "Add the home travel gcode." ++ location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) ++ self.highestZ = max( self.highestZ, location.z ) ++ if not self.shouldHome: ++ return ++ self.shouldHome = False ++ if self.oldLocation == None: ++ return ++ if self.extruderActive: ++ self.distanceFeedRate.addLine('M103') ++ self.addHopUp( self.oldLocation ) ++ self.distanceFeedRate.addLinesSetAbsoluteDistanceMode(self.homeLines) ++ self.addHopUp( self.oldLocation ) ++ self.addFloat( self.oldLocation, location ) ++ if self.extruderActive: ++ self.distanceFeedRate.addLine('M101') ++ ++ def addHopUp(self, location): ++ "Add hop to highest point." ++ locationUp = Vector3( location.x, location.y, self.highestZ ) ++ self.distanceFeedRate.addLine( self.distanceFeedRate.getLinearGcodeMovementWithFeedRate( self.travelFeedRateMinute, locationUp.dropAxis(), locationUp.z ) ) ++ ++ def getCraftedGcode( self, gcodeText, repository ): ++ "Parse gcode text and store the home gcode." ++ self.repository = repository ++ self.homeLines = settings.getAlterationFileLines(repository.nameOfHomeFile.value) ++ if len(self.homeLines) < 1: ++ return gcodeText ++ self.lines = archive.getTextLines(gcodeText) ++ self.parseInitialization( repository ) ++ for self.lineIndex in xrange(self.lineIndex, len(self.lines)): ++ line = self.lines[self.lineIndex] ++ self.parseLine(line) ++ return self.distanceFeedRate.output.getvalue() ++ ++ def parseInitialization( self, repository ): ++ '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.addTagBracketedProcedure('home') ++ return ++ elif firstWord == '(': ++ self.absolutePerimeterWidth = abs(float(splitLine[1])) ++ elif firstWord == '(': ++ self.travelFeedRateMinute = 60.0 * float(splitLine[1]) ++ self.distanceFeedRate.addLine(line) ++ ++ def parseLine(self, line): ++ "Parse a gcode line and add it to the bevel gcode." ++ splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) ++ if len(splitLine) < 1: ++ return ++ firstWord = splitLine[0] ++ if firstWord == 'G1': ++ self.addHomeTravel(splitLine) ++ self.oldLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) ++ elif firstWord == '(': ++ self.layerCount.printProgressIncrement('home') ++ if len(self.homeLines) > 0: ++ self.shouldHome = True ++ elif firstWord == 'M101': ++ self.extruderActive = True ++ elif firstWord == 'M103': ++ self.extruderActive = False ++ self.distanceFeedRate.addLine(line) ++ ++ ++def main(): ++ "Display the home dialog." ++ if len(sys.argv) > 1: ++ writeOutput(' '.join(sys.argv[1 :])) ++ else: ++ settings.startMainLoopFromConstructor(getNewRepository()) ++ ++if __name__ == "__main__": ++ main() +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/jitter.py ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/jitter.py +@@ -116,7 +116,7 @@ + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.jitter.html', self) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Jitter', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Jitter') +- self.activateJitter = settings.BooleanSetting().getFromValue('Activate Jitter', self, True) ++ self.activateJitter = settings.BooleanSetting().getFromValue('Activate Jitter', self, False) + self.jitterOverPerimeterWidth = settings.FloatSpin().getFromValue(1.0, 'Jitter Over Perimeter Width (ratio):', self, 3.0, 2.0) + self.executeTitle = 'Jitter' + +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/limit.py ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/limit.py +@@ -86,7 +86,7 @@ + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.limit.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Limit', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Limit') +- self.activateLimit = settings.BooleanSetting().getFromValue('Activate Limit', self, True) ++ self.activateLimit = settings.BooleanSetting().getFromValue('Activate Limit', self, False) + self.maximumInitialFeedRate = settings.FloatSpin().getFromValue(0.5, 'Maximum Initial Feed Rate (mm/s):', self, 10.0, 1.0) + self.executeTitle = 'Limit' + +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/limit.py.orig ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/limit.py.orig +@@ -0,0 +1,201 @@ ++#! /usr/bin/env python ++""" ++This page is in the table of contents. ++This plugin limits the feed rate of the tool head, so that the stepper motors are not driven too fast and skip steps. ++ ++The limit manual page is at: ++http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Limit ++ ++The maximum z feed rate is defined in speed. ++ ++==Operation== ++The default 'Activate Limit' checkbox is on. When it is on, the functions described below will work, when it is off, nothing will be done. ++ ++==Settings== ++===Maximum Initial Feed Rate=== ++Default is one millimeter per second. ++ ++Defines the maximum speed of the inital tool head move. ++ ++==Examples== ++The following examples limit the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and limit.py. ++ ++> python limit.py ++This brings up the limit dialog. ++ ++> python limit.py Screw Holder Bottom.stl ++The limit tool is parsing the file: ++Screw Holder Bottom.stl ++.. ++The limit tool has created the file: ++.. Screw Holder Bottom_limit.gcode ++ ++""" ++ ++#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 fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret ++from fabmetheus_utilities.vector3 import Vector3 ++from fabmetheus_utilities import archive ++from fabmetheus_utilities import euclidean ++from fabmetheus_utilities import gcodec ++from fabmetheus_utilities import intercircle ++from fabmetheus_utilities import settings ++from skeinforge_application.skeinforge_utilities import skeinforge_craft ++from skeinforge_application.skeinforge_utilities import skeinforge_polyfile ++from skeinforge_application.skeinforge_utilities import skeinforge_profile ++import math ++import os ++import sys ++ ++ ++__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' ++__date__ = '$Date: 2008/28/04 $' ++__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' ++ ++ ++def getCraftedText(fileName, gcodeText='', repository=None): ++ 'Limit a gcode file or text.' ++ return getCraftedTextFromText( archive.getTextIfEmpty(fileName, gcodeText), repository ) ++ ++def getCraftedTextFromText(gcodeText, repository=None): ++ 'Limit a gcode text.' ++ if gcodec.isProcedureDoneOrFileIsEmpty(gcodeText, 'limit'): ++ return gcodeText ++ if repository == None: ++ repository = settings.getReadRepository(LimitRepository()) ++ if not repository.activateLimit.value: ++ return gcodeText ++ return LimitSkein().getCraftedGcode(gcodeText, repository) ++ ++def getNewRepository(): ++ 'Get new repository.' ++ return LimitRepository() ++ ++def writeOutput(fileName, shouldAnalyze=True): ++ 'Limit a gcode file.' ++ skeinforge_craft.writeChainTextWithNounMessage(fileName, 'limit', shouldAnalyze) ++ ++ ++class LimitRepository: ++ 'A class to handle the limit settings.' ++ def __init__(self): ++ 'Set the default settings, execute title & settings fileName.' ++ skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.limit.html', self ) ++ self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Limit', self, '') ++ self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Limit') ++ self.activateLimit = settings.BooleanSetting().getFromValue('Activate Limit', self, True) ++ self.maximumInitialFeedRate = settings.FloatSpin().getFromValue(0.5, 'Maximum Initial Feed Rate (mm/s):', self, 10.0, 1.0) ++ self.executeTitle = 'Limit' ++ ++ def execute(self): ++ 'Limit button has been clicked.' ++ fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) ++ for fileName in fileNames: ++ writeOutput(fileName) ++ ++ ++class LimitSkein: ++ 'A class to limit a skein of extrusions.' ++ def __init__(self): ++ self.distanceFeedRate = gcodec.DistanceFeedRate() ++ self.feedRateMinute = None ++ self.lineIndex = 0 ++ self.maximumZDrillFeedRatePerSecond = 987654321.0 ++ self.oldLocation = None ++ ++ def getCraftedGcode(self, gcodeText, repository): ++ 'Parse gcode text and store the limit gcode.' ++ self.repository = repository ++ self.lines = archive.getTextLines(gcodeText) ++ self.parseInitialization() ++ self.maximumZDrillFeedRatePerSecond = min(self.maximumZDrillFeedRatePerSecond, self.maximumZTravelFeedRatePerSecond) ++ self.maximumZFeedRatePerSecond = self.maximumZTravelFeedRatePerSecond ++ for lineIndex in xrange(self.lineIndex, len(self.lines)): ++ self.parseLine( lineIndex ) ++ return self.distanceFeedRate.output.getvalue() ++ ++ def getLimitedInitialMovement(self, line, splitLine): ++ 'Get a limited linear movement.' ++ if self.oldLocation == None: ++ line = self.distanceFeedRate.getLineWithFeedRate(60.0 * self.repository.maximumInitialFeedRate.value, line, splitLine) ++ return line ++ ++ def getZLimitedLine(self, deltaZ, distance, line, splitLine): ++ 'Get a replaced z limited gcode movement line.' ++ zFeedRateSecond = self.feedRateMinute * deltaZ / distance / 60.0 ++ if zFeedRateSecond <= self.maximumZFeedRatePerSecond: ++ return line ++ limitedFeedRateMinute = self.feedRateMinute * self.maximumZFeedRatePerSecond / zFeedRateSecond ++ return self.distanceFeedRate.getLineWithFeedRate(limitedFeedRateMinute, line, splitLine) ++ ++ def getZLimitedLineArc(self, line, splitLine): ++ 'Get a replaced z limited gcode arc movement line.' ++ self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine) ++ if self.feedRateMinute == None or self.oldLocation == None: ++ return line ++ relativeLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) ++ self.oldLocation += relativeLocation ++ deltaZ = abs(relativeLocation.z) ++ distance = gcodec.getArcDistance(relativeLocation, splitLine) ++ return self.getZLimitedLine(deltaZ, distance, line, splitLine) ++ ++ def getZLimitedLineLinear(self, line, location, splitLine): ++ 'Get a replaced z limited gcode linear movement line.' ++ self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine) ++ if location == self.oldLocation: ++ return '' ++ if self.feedRateMinute == None or self.oldLocation == None: ++ return line ++ deltaZ = abs(location.z - self.oldLocation.z) ++ distance = abs(location - self.oldLocation) ++ return self.getZLimitedLine(deltaZ, distance, line, splitLine) ++ ++ 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.addTagBracketedProcedure('limit') ++ return ++ elif firstWord == '(': ++ self.maximumZDrillFeedRatePerSecond = float(splitLine[1]) ++ elif firstWord == '(': ++ self.maximumZTravelFeedRatePerSecond = float(splitLine[1]) ++ self.distanceFeedRate.addLine(line) ++ ++ def parseLine( self, lineIndex ): ++ 'Parse a gcode line and add it to the limit skein.' ++ line = self.lines[lineIndex].lstrip() ++ splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) ++ if len(splitLine) < 1: ++ return ++ firstWord = gcodec.getFirstWord(splitLine) ++ if firstWord == 'G1': ++ location = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) ++ line = self.getLimitedInitialMovement(line, splitLine) ++ line = self.getZLimitedLineLinear(line, location, splitLine) ++ self.oldLocation = location ++ elif firstWord == 'G2' or firstWord == 'G3': ++ line = self.getZLimitedLineArc(line, splitLine) ++ elif firstWord == 'M101': ++ self.maximumZFeedRatePerSecond = self.maximumZDrillFeedRatePerSecond ++ elif firstWord == 'M103': ++ self.maximumZFeedRatePerSecond = self.maximumZTravelFeedRatePerSecond ++ self.distanceFeedRate.addLine(line) ++ ++ ++def main(): ++ 'Display the limit dialog.' ++ if len(sys.argv) > 1: ++ writeOutput(' '.join(sys.argv[1 :])) ++ else: ++ settings.startMainLoopFromConstructor(getNewRepository()) ++ ++if __name__ == '__main__': ++ main() +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/multiply.py ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/multiply.py +@@ -112,11 +112,11 @@ + self.fileNameInput = settings.FileNameInput().getFromFileName( + fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Multiply', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Multiply') +- self.activateMultiply = settings.BooleanSetting().getFromValue('Activate Multiply', self, False) ++ self.activateMultiply = settings.BooleanSetting().getFromValue('Activate Multiply', self, True) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Center -', self ) +- self.centerX = settings.FloatSpin().getFromValue(-100.0, 'Center X (mm):', self, 100.0, 0.0) +- self.centerY = settings.FloatSpin().getFromValue(-100.0, 'Center Y (mm):', self, 100.0, 0.0) ++ self.centerX = settings.FloatSpin().getFromValue(-100.0, 'Center X (mm):', self, 100.0, 105.0) ++ self.centerY = settings.FloatSpin().getFromValue(-100.0, 'Center Y (mm):', self, 100.0, 105.0) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Number of Cells -', self) + self.numberOfColumns = settings.IntSpin().getFromValue(1, 'Number of Columns (integer):', self, 10, 1) +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/raft.py ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/raft.py +@@ -351,7 +351,7 @@ + self.baseInfillDensity = settings.FloatSpin().getFromValue(0.3, 'Base Infill Density (ratio):', self, 0.9, 0.5) + self.baseLayerThicknessOverLayerThickness = settings.FloatSpin().getFromValue( + 1.0, 'Base Layer Thickness over Layer Thickness:', self, 3.0, 2.0) +- self.baseLayers = settings.IntSpin().getFromValue(0, 'Base Layers (integer):', self, 3, 1) ++ self.baseLayers = settings.IntSpin().getFromValue(0, 'Base Layers (integer):', self, 3, 0) + self.baseNozzleLiftOverBaseLayerThickness = settings.FloatSpin().getFromValue( + 0.2, 'Base Nozzle Lift over Base Layer Thickness (ratio):', self, 0.8, 0.4) + settings.LabelSeparator().getFromRepository(self) +@@ -369,7 +369,7 @@ + self.interfaceLayerThicknessOverLayerThickness = settings.FloatSpin().getFromValue( + 1.0, 'Interface Layer Thickness over Layer Thickness:', self, 3.0, 1.0) + self.interfaceLayers = settings.IntSpin().getFromValue( +- 0, 'Interface Layers (integer):', self, 3, 2) ++ 0, 'Interface Layers (integer):', self, 3, 0) + self.interfaceNozzleLiftOverInterfaceLayerThickness = settings.FloatSpin().getFromValue( + 0.25, 'Interface Nozzle Lift over Interface Layer Thickness (ratio):', self, 0.85, 0.45) + settings.LabelSeparator().getFromRepository(self) +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/raft.py.orig ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/raft.py.orig +@@ -0,0 +1,1104 @@ ++""" ++This page is in the table of contents. ++Raft is a plugin to create a raft, elevate the nozzle and set the temperature. A raft is a flat base structure on top of which your object is being build and has a few different purposes. It fills irregularities like scratches and pits in your printbed and gives you a nice base parallel to the printheads movement. It also glues your object to the bed so to prevent warping in bigger object. The rafts base layer performs these tricks while the sparser interface layer(s) help you removing the object from the raft after printing. It is based on the Nophead's reusable raft, which has a base layer running one way, and a couple of perpendicular layers above. Each set of layers can be set to a different temperature. There is the option of having the extruder orbit the raft for a while, so the heater barrel has time to reach a different temperature, without ooze accumulating around the nozzle. ++ ++The raft manual page is at: ++http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Raft ++ ++The important values for the raft settings are the temperatures of the raft, the first layer and the next layers. These will be different for each material. The default settings for ABS, HDPE, PCL & PLA are extrapolated from Nophead's experiments. ++ ++You don't necessarily need a raft and especially small object will print fine on a flat bed without one, sometimes its even better when you need a water tight base to print directly on the bed. If you want to only set the temperature or only create support material or only elevate the nozzle without creating a raft, set the Base Layers and Interface Layers to zero. ++ ++ ++Image:Raft.jpg|Raft ++ ++ ++Example of a raft on the left with the interface layers partially removed exposing the base layer. Notice that the first line of the base is rarely printed well because of the startup time of the extruder. On the right you see an object with its raft still attached. ++ ++The Raft panel has some extra settings, it probably made sense to have them there but they have not that much to do with the actual Raft. First are the Support material settings. Since close to all RepRap style printers have no second extruder for support material Skeinforge offers the option to print support structures with the same material set at a different speed and temperature. The idea is that the support sticks less to the actual object when it is extruded around the minimum possible working temperature. This results in a temperature change EVERY layer so build time will increase seriously. ++ ++Allan Ecker aka The Masked Retriever's has written two quicktips for raft which follow below. ++"Skeinforge Quicktip: The Raft, Part 1" at: ++http://blog.thingiverse.com/2009/07/14/skeinforge-quicktip-the-raft-part-1/ ++"Skeinforge Quicktip: The Raft, Part II" at: ++http://blog.thingiverse.com/2009/08/04/skeinforge-quicktip-the-raft-part-ii/ ++ ++Nophead has written about rafts on his blog: ++http://hydraraptor.blogspot.com/2009/07/thoughts-on-rafts.html ++ ++More pictures of rafting in action are available from the Metalab blog at: ++http://reprap.soup.io/?search=rafting ++ ++==Operation== ++Default: On ++ ++When it is on, the functions described below will work, when it is off, nothing will be done, so no temperatures will be set, nozzle will not be lifted.. ++ ++==Settings== ++===Add Raft, Elevate Nozzle, Orbit=== ++Default: On ++ ++When selected, the script will also create a raft, elevate the nozzle, orbit and set the altitude of the bottom of the raft. It also turns on support generation. ++ ++===Base=== ++Base layer is the part of the raft that touches the bed. ++ ++====Base Feed Rate Multiplier==== ++Default is one. ++ ++Defines the base feed rate multiplier. The greater the 'Base Feed Rate Multiplier', the thinner the base, the lower the 'Base Feed Rate Multiplier', the thicker the base. ++ ++====Base Flow Rate Multiplier==== ++Default is one. ++ ++Defines the base flow rate multiplier. The greater the 'Base Flow Rate Multiplier', the thicker the base, the lower the 'Base Flow Rate Multiplier', the thinner the base. ++ ++====Base Infill Density==== ++Default is 0.5. ++ ++Defines the infill density ratio of the base of the raft. ++ ++====Base Layer Height over Layer Thickness==== ++Default is two. ++ ++Defines the ratio of the height & width of the base layer compared to the height and width of the object infill. The feed rate will be slower for raft layers which have thicker extrusions than the object infill. ++ ++====Base Layers==== ++Default is one. ++ ++Defines the number of base layers. ++ ++====Base Nozzle Lift over Base Layer Thickness==== ++Default is 0.4. ++ ++Defines the amount the nozzle is above the center of the base extrusion divided by the base layer thickness. ++ ++===Initial Circling=== ++Default is off. ++ ++When selected, the extruder will initially circle around until it reaches operating temperature. ++ ++===Infill Overhang over Extrusion Width=== ++Default is 0.05. ++ ++Defines the ratio of the infill overhang over the the extrusion width of the raft. ++ ++===Interface=== ++====Interface Feed Rate Multiplier==== ++Default is one. ++ ++Defines the interface feed rate multiplier. The greater the 'Interface Feed Rate Multiplier', the thinner the interface, the lower the 'Interface Feed Rate Multiplier', the thicker the interface. ++ ++====Interface Flow Rate Multiplier==== ++Default is one. ++ ++Defines the interface flow rate multiplier. The greater the 'Interface Flow Rate Multiplier', the thicker the interface, the lower the 'Interface Flow Rate Multiplier', the thinner the interface. ++ ++====Interface Infill Density==== ++Default is 0.5. ++ ++Defines the infill density ratio of the interface of the raft. ++ ++====Interface Layer Thickness over Extrusion Height==== ++Default is one. ++ ++Defines the ratio of the height & width of the interface layer compared to the height and width of the object infill. The feed rate will be slower for raft layers which have thicker extrusions than the object infill. ++ ++====Interface Layers==== ++Default is two. ++ ++Defines the number of interface layers to print. ++ ++====Interface Nozzle Lift over Interface Layer Thickness==== ++Default is 0.45. ++ ++Defines the amount the nozzle is above the center of the interface extrusion divided by the interface layer thickness. ++ ++===Name of Alteration Files=== ++If support material is generated, raft looks for alteration files in the alterations folder in the .skeinforge folder in the home directory. Raft 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. ++ ++====Name of Support End File==== ++Default is support_end.gcode. ++ ++If support material is generated and if there is a file with the name of the "Name of Support End File" setting, it will be added to the end of the support gcode. ++ ++====Name of Support Start File==== ++If support material is generated and if there is a file with the name of the "Name of Support Start File" setting, it will be added to the start of the support gcode. ++ ++===Operating Nozzle Lift over Layer Thickness=== ++Default is 0.5. ++ ++Defines the amount the nozzle is above the center of the operating extrusion divided by the layer thickness. ++ ++===Raft Size=== ++The raft fills a rectangle whose base size is the rectangle around the bottom layer of the object expanded on each side by the 'Raft Margin' plus the 'Raft Additional Margin over Length (%)' percentage times the length of the side. ++ ++====Raft Additional Margin over Length==== ++Default is 1 percent. ++ ++====Raft Margin==== ++Default is three millimeters. ++ ++===Support=== ++Good articles on support material are at: ++http://davedurant.wordpress.com/2010/07/31/skeinforge-support-part-1/ ++http://davedurant.wordpress.com/2010/07/31/skeinforge-support-part-2/ ++ ++====Support Cross Hatch==== ++Default is off. ++ ++When selected, the support material will cross hatched. Cross hatching the support makes it stronger and harder to remove, which is why the default is off. ++ ++====Support Flow Rate over Operating Flow Rate==== ++Default: 0.9. ++ ++Defines the ratio of the flow rate when the support is extruded over the operating flow rate. With a number less than one, the support flow rate will be smaller so the support will be thinner and easier to remove. ++ ++====Support Gap over Perimeter Extrusion Width==== ++Default: 0.5. ++ ++Defines the gap between the support material and the object over the perimeter extrusion width. ++ ++====Support Material Choice==== ++Default is 'None' because the raft takes time to generate. ++ ++=====Empty Layers Only===== ++When selected, support material will be only on the empty layers. This is useful when making identical objects in a stack. ++ ++=====Everywhere===== ++When selected, support material will be added wherever there are overhangs, even inside the object. Because support material inside objects is hard or impossible to remove, this option should only be chosen if the object has a cavity that needs support and there is some way to extract the support material. ++ ++=====Exterior Only===== ++When selected, support material will be added only the exterior of the object. This is the best option for most objects which require support material. ++ ++=====None===== ++When selected, raft will not add support material. ++ ++====Support Minimum Angle==== ++Default is sixty degrees. ++ ++Defines the minimum angle that a surface overhangs before support material is added. If angle is lower then this value the support will be generated. This angle is defined from the vertical, so zero is a vertical wall, ten is a wall with a bit of overhang, thirty is the typical safe angle for filament extrusion, sixty is a really high angle for extrusion and ninety is an unsupported horizontal ceiling. ++ ++==Examples== ++The following examples raft the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and raft.py. ++ ++> python raft.py ++This brings up the raft dialog. ++ ++> python raft.py Screw Holder Bottom.stl ++The raft tool is parsing the file: ++Screw Holder Bottom.stl ++.. ++The raft tool has created the file: ++Screw Holder Bottom_raft.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 fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret ++from fabmetheus_utilities.geometry.solids import triangle_mesh ++from fabmetheus_utilities.vector3 import Vector3 ++from fabmetheus_utilities import archive ++from fabmetheus_utilities import euclidean ++from fabmetheus_utilities import gcodec ++from fabmetheus_utilities import intercircle ++from fabmetheus_utilities import settings ++from skeinforge_application.skeinforge_utilities import skeinforge_craft ++from skeinforge_application.skeinforge_utilities import skeinforge_polyfile ++from skeinforge_application.skeinforge_utilities import skeinforge_profile ++import math ++import os ++import sys ++ ++ ++__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' ++__date__ = '$Date: 2008/21/04 $' ++__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' ++ ++ ++#maybe later wide support ++#raft outline temperature http://hydraraptor.blogspot.com/2008/09/screw-top-pot.html ++def getCraftedText( fileName, text='', repository=None): ++ 'Raft the file or text.' ++ return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository) ++ ++def getCraftedTextFromText(gcodeText, repository=None): ++ 'Raft a gcode linear move text.' ++ if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'raft'): ++ return gcodeText ++ if repository == None: ++ repository = settings.getReadRepository( RaftRepository() ) ++ if not repository.activateRaft.value: ++ return gcodeText ++ return RaftSkein().getCraftedGcode(gcodeText, repository) ++ ++def getCrossHatchPointLine( crossHatchPointLineTable, y ): ++ 'Get the cross hatch point line.' ++ if not crossHatchPointLineTable.has_key(y): ++ crossHatchPointLineTable[ y ] = {} ++ return crossHatchPointLineTable[ y ] ++ ++def getEndpointsFromYIntersections( x, yIntersections ): ++ 'Get endpoints from the y intersections.' ++ endpoints = [] ++ for yIntersectionIndex in xrange( 0, len( yIntersections ), 2 ): ++ firstY = yIntersections[ yIntersectionIndex ] ++ secondY = yIntersections[ yIntersectionIndex + 1 ] ++ if firstY != secondY: ++ firstComplex = complex( x, firstY ) ++ secondComplex = complex( x, secondY ) ++ endpointFirst = euclidean.Endpoint() ++ endpointSecond = euclidean.Endpoint().getFromOtherPoint( endpointFirst, secondComplex ) ++ endpointFirst.getFromOtherPoint( endpointSecond, firstComplex ) ++ endpoints.append( endpointFirst ) ++ endpoints.append( endpointSecond ) ++ return endpoints ++ ++def getExtendedLineSegment(extensionDistance, lineSegment, loopXIntersections): ++ 'Get extended line segment.' ++ pointBegin = lineSegment[0].point ++ pointEnd = lineSegment[1].point ++ segment = pointEnd - pointBegin ++ segmentLength = abs(segment) ++ if segmentLength <= 0.0: ++ print('This should never happen in getExtendedLineSegment in raft, the segment should have a length greater than zero.') ++ print(lineSegment) ++ return None ++ segmentExtend = segment * extensionDistance / segmentLength ++ lineSegment[0].point -= segmentExtend ++ lineSegment[1].point += segmentExtend ++ for loopXIntersection in loopXIntersections: ++ setExtendedPoint(lineSegment[0], pointBegin, loopXIntersection) ++ setExtendedPoint(lineSegment[1], pointEnd, loopXIntersection) ++ return lineSegment ++ ++def getLoopsBySegmentsDictionary(segmentsDictionary, width): ++ 'Get loops from a horizontal segments dictionary.' ++ points = [] ++ for endpoint in getVerticalEndpoints(segmentsDictionary, width, 0.1 * width, width): ++ points.append(endpoint.point) ++ for endpoint in euclidean.getEndpointsFromSegmentTable(segmentsDictionary): ++ points.append(endpoint.point) ++ return triangle_mesh.getDescendingAreaOrientedLoops(points, points, width + width) ++ ++def getNewRepository(): ++ 'Get new repository.' ++ return RaftRepository() ++ ++def getVerticalEndpoints(horizontalSegmentsTable, horizontalStep, verticalOverhang, verticalStep): ++ 'Get vertical endpoints.' ++ interfaceSegmentsTableKeys = horizontalSegmentsTable.keys() ++ interfaceSegmentsTableKeys.sort() ++ verticalTableTable = {} ++ for interfaceSegmentsTableKey in interfaceSegmentsTableKeys: ++ interfaceSegments = horizontalSegmentsTable[interfaceSegmentsTableKey] ++ for interfaceSegment in interfaceSegments: ++ begin = int(round(interfaceSegment[0].point.real / verticalStep)) ++ end = int(round(interfaceSegment[1].point.real / verticalStep)) ++ for stepIndex in xrange(begin, end + 1): ++ if stepIndex not in verticalTableTable: ++ verticalTableTable[stepIndex] = {} ++ verticalTableTable[stepIndex][interfaceSegmentsTableKey] = None ++ verticalTableTableKeys = verticalTableTable.keys() ++ verticalTableTableKeys.sort() ++ verticalEndpoints = [] ++ for verticalTableTableKey in verticalTableTableKeys: ++ verticalTable = verticalTableTable[verticalTableTableKey] ++ verticalTableKeys = verticalTable.keys() ++ verticalTableKeys.sort() ++ xIntersections = [] ++ for verticalTableKey in verticalTableKeys: ++ y = verticalTableKey * horizontalStep ++ if verticalTableKey - 1 not in verticalTableKeys: ++ xIntersections.append(y - verticalOverhang) ++ if verticalTableKey + 1 not in verticalTableKeys: ++ xIntersections.append(y + verticalOverhang) ++ for segment in euclidean.getSegmentsFromXIntersections(xIntersections, verticalTableTableKey * verticalStep): ++ for endpoint in segment: ++ endpoint.point = complex(endpoint.point.imag, endpoint.point.real) ++ verticalEndpoints.append(endpoint) ++ return verticalEndpoints ++ ++def setExtendedPoint( lineSegmentEnd, pointOriginal, x ): ++ 'Set the point in the extended line segment.' ++ if x > min( lineSegmentEnd.point.real, pointOriginal.real ) and x < max( lineSegmentEnd.point.real, pointOriginal.real ): ++ lineSegmentEnd.point = complex( x, pointOriginal.imag ) ++ ++def writeOutput(fileName, shouldAnalyze=True): ++ 'Raft a gcode linear move file.' ++ skeinforge_craft.writeChainTextWithNounMessage(fileName, 'raft', shouldAnalyze) ++ ++ ++class RaftRepository: ++ 'A class to handle the raft settings.' ++ def __init__(self): ++ 'Set the default settings, execute title & settings fileName.' ++ skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.raft.html', self) ++ self.fileNameInput = settings.FileNameInput().getFromFileName( ++ fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Raft', self, '') ++ self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute( ++ 'http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Raft') ++ self.activateRaft = settings.BooleanSetting().getFromValue('Activate Raft', self, True) ++ self.addRaftElevateNozzleOrbitSetAltitude = settings.BooleanSetting().getFromValue( ++ 'Add Raft, Elevate Nozzle, Orbit:', self, True) ++ settings.LabelSeparator().getFromRepository(self) ++ settings.LabelDisplay().getFromName('- Base -', self) ++ self.baseFeedRateMultiplier = settings.FloatSpin().getFromValue(0.7, 'Base Feed Rate Multiplier (ratio):', self, 1.1, 1.0) ++ self.baseFlowRateMultiplier = settings.FloatSpin().getFromValue(0.7, 'Base Flow Rate Multiplier (ratio):', self, 1.1, 1.0) ++ self.baseInfillDensity = settings.FloatSpin().getFromValue(0.3, 'Base Infill Density (ratio):', self, 0.9, 0.5) ++ self.baseLayerThicknessOverLayerThickness = settings.FloatSpin().getFromValue( ++ 1.0, 'Base Layer Thickness over Layer Thickness:', self, 3.0, 2.0) ++ self.baseLayers = settings.IntSpin().getFromValue(0, 'Base Layers (integer):', self, 3, 1) ++ self.baseNozzleLiftOverBaseLayerThickness = settings.FloatSpin().getFromValue( ++ 0.2, 'Base Nozzle Lift over Base Layer Thickness (ratio):', self, 0.8, 0.4) ++ settings.LabelSeparator().getFromRepository(self) ++ self.initialCircling = settings.BooleanSetting().getFromValue('Initial Circling:', self, False) ++ self.infillOverhangOverExtrusionWidth = settings.FloatSpin().getFromValue( ++ 0.0, 'Infill Overhang over Extrusion Width (ratio):', self, 0.5, 0.05) ++ settings.LabelSeparator().getFromRepository(self) ++ settings.LabelDisplay().getFromName('- Interface -', self) ++ self.interfaceFeedRateMultiplier = settings.FloatSpin().getFromValue( ++ 0.7, 'Interface Feed Rate Multiplier (ratio):', self, 1.1, 1.0) ++ self.interfaceFlowRateMultiplier = settings.FloatSpin().getFromValue( ++ 0.7, 'Interface Flow Rate Multiplier (ratio):', self, 1.1, 1.0) ++ self.interfaceInfillDensity = settings.FloatSpin().getFromValue( ++ 0.3, 'Interface Infill Density (ratio):', self, 0.9, 0.5) ++ self.interfaceLayerThicknessOverLayerThickness = settings.FloatSpin().getFromValue( ++ 1.0, 'Interface Layer Thickness over Layer Thickness:', self, 3.0, 1.0) ++ self.interfaceLayers = settings.IntSpin().getFromValue( ++ 0, 'Interface Layers (integer):', self, 3, 2) ++ self.interfaceNozzleLiftOverInterfaceLayerThickness = settings.FloatSpin().getFromValue( ++ 0.25, 'Interface Nozzle Lift over Interface Layer Thickness (ratio):', self, 0.85, 0.45) ++ settings.LabelSeparator().getFromRepository(self) ++ settings.LabelDisplay().getFromName('- Name of Alteration Files -', self) ++ self.nameOfSupportEndFile = settings.StringSetting().getFromValue('Name of Support End File:', self, 'support_end.gcode') ++ self.nameOfSupportStartFile = settings.StringSetting().getFromValue( ++ 'Name of Support Start File:', self, 'support_start.gcode') ++ settings.LabelSeparator().getFromRepository(self) ++ self.operatingNozzleLiftOverLayerThickness = settings.FloatSpin().getFromValue( ++ 0.3, 'Operating Nozzle Lift over Layer Thickness (ratio):', self, 0.7, 0.5) ++ settings.LabelSeparator().getFromRepository(self) ++ settings.LabelDisplay().getFromName('- Raft Size -', self) ++ self.raftAdditionalMarginOverLengthPercent = settings.FloatSpin().getFromValue( ++ 0.5, 'Raft Additional Margin over Length (%):', self, 1.5, 1.0) ++ self.raftMargin = settings.FloatSpin().getFromValue( ++ 1.0, 'Raft Margin (mm):', self, 5.0, 3.0) ++ settings.LabelSeparator().getFromRepository(self) ++ settings.LabelDisplay().getFromName('- Support -', self) ++ self.supportCrossHatch = settings.BooleanSetting().getFromValue('Support Cross Hatch', self, False) ++ self.supportFlowRateOverOperatingFlowRate = settings.FloatSpin().getFromValue( ++ 0.7, 'Support Flow Rate over Operating Flow Rate (ratio):', self, 1.1, 1.0) ++ self.supportGapOverPerimeterExtrusionWidth = settings.FloatSpin().getFromValue( ++ 0.5, 'Support Gap over Perimeter Extrusion Width (ratio):', self, 1.5, 1.0) ++ self.supportMaterialChoice = settings.MenuButtonDisplay().getFromName('Support Material Choice: ', self) ++ self.supportChoiceNone = settings.MenuRadio().getFromMenuButtonDisplay(self.supportMaterialChoice, 'None', self, True) ++ self.supportChoiceEmptyLayersOnly = settings.MenuRadio().getFromMenuButtonDisplay(self.supportMaterialChoice, 'Empty Layers Only', self, False) ++ self.supportChoiceEverywhere = settings.MenuRadio().getFromMenuButtonDisplay(self.supportMaterialChoice, 'Everywhere', self, False) ++ self.supportChoiceExteriorOnly = settings.MenuRadio().getFromMenuButtonDisplay(self.supportMaterialChoice, 'Exterior Only', self, False) ++ self.supportMinimumAngle = settings.FloatSpin().getFromValue(40.0, 'Support Minimum Angle (degrees):', self, 80.0, 60.0) ++ self.executeTitle = 'Raft' ++ ++ def execute(self): ++ 'Raft button has been clicked.' ++ fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) ++ for fileName in fileNames: ++ writeOutput(fileName) ++ ++ ++class RaftSkein: ++ 'A class to raft a skein of extrusions.' ++ def __init__(self): ++ self.addLineLayerStart = True ++ self.baseTemperature = None ++ self.beginLoop = None ++ self.boundaryLayers = [] ++ self.coolingRate = None ++ self.distanceFeedRate = gcodec.DistanceFeedRate() ++ self.extrusionStart = True ++ self.extrusionTop = 0.0 ++ self.feedRateMinute = 961.0 ++ self.heatingRate = None ++ self.insetTable = {} ++ self.interfaceTemperature = None ++ self.isNestedRing = True ++ self.isPerimeterPath = False ++ self.isStartupEarly = False ++ self.layerIndex = - 1 ++ self.layerStarted = False ++ self.layerThickness = 0.4 ++ self.lineIndex = 0 ++ self.lines = None ++ self.objectFirstLayerInfillTemperature = None ++ self.objectFirstLayerPerimeterTemperature = None ++ self.objectNextLayersTemperature = None ++ self.oldFlowRate = None ++ self.oldLocation = None ++ self.oldTemperatureOutputString = None ++ self.operatingFeedRateMinute = None ++ self.operatingLayerEndLine = '( )' ++ self.operatingJump = None ++ self.orbitalFeedRatePerSecond = 2.01 ++ self.perimeterWidth = 0.6 ++ self.supportFlowRate = None ++ self.supportLayers = [] ++ self.supportLayersTemperature = None ++ self.supportedLayersTemperature = None ++ self.travelFeedRateMinute = None ++ ++ def addBaseLayer(self): ++ 'Add a base layer.' ++ baseLayerThickness = self.layerThickness * self.baseLayerThicknessOverLayerThickness ++ zCenter = self.extrusionTop + 0.5 * baseLayerThickness ++ z = zCenter + baseLayerThickness * self.repository.baseNozzleLiftOverBaseLayerThickness.value ++ if len(self.baseEndpoints) < 1: ++ print('This should never happen, the base layer has a size of zero.') ++ return ++ self.addLayerFromEndpoints( ++ self.baseEndpoints, ++ self.repository.baseFeedRateMultiplier.value, ++ self.repository.baseFlowRateMultiplier.value, ++ baseLayerThickness, ++ self.baseLayerThicknessOverLayerThickness, ++ self.baseStep, ++ z) ++ ++ def addBaseSegments(self, baseExtrusionWidth): ++ 'Add the base segments.' ++ baseOverhang = self.repository.infillOverhangOverExtrusionWidth.value * baseExtrusionWidth ++ self.baseEndpoints = getVerticalEndpoints(self.interfaceSegmentsTable, self.interfaceStep, baseOverhang, self.baseStep) ++ ++ def addEmptyLayerSupport( self, boundaryLayerIndex ): ++ 'Add support material to a layer if it is empty.' ++ supportLayer = SupportLayer([]) ++ self.supportLayers.append(supportLayer) ++ if len( self.boundaryLayers[ boundaryLayerIndex ].loops ) > 0: ++ return ++ aboveXIntersectionsTable = {} ++ euclidean.addXIntersectionsFromLoopsForTable( self.getInsetLoopsAbove(boundaryLayerIndex), aboveXIntersectionsTable, self.interfaceStep ) ++ belowXIntersectionsTable = {} ++ euclidean.addXIntersectionsFromLoopsForTable( self.getInsetLoopsBelow(boundaryLayerIndex), belowXIntersectionsTable, self.interfaceStep ) ++ supportLayer.xIntersectionsTable = euclidean.getIntersectionOfXIntersectionsTables( [ aboveXIntersectionsTable, belowXIntersectionsTable ] ) ++ ++ def addFlowRate(self, flowRate): ++ 'Add a flow rate value if different.' ++ if flowRate != None: ++ self.distanceFeedRate.addLine('M108 S' + euclidean.getFourSignificantFigures(flowRate)) ++ ++ def addInterfaceLayer(self): ++ 'Add an interface layer.' ++ interfaceLayerThickness = self.layerThickness * self.interfaceLayerThicknessOverLayerThickness ++ zCenter = self.extrusionTop + 0.5 * interfaceLayerThickness ++ z = zCenter + interfaceLayerThickness * self.repository.interfaceNozzleLiftOverInterfaceLayerThickness.value ++ if len(self.interfaceEndpoints) < 1: ++ print('This should never happen, the interface layer has a size of zero.') ++ return ++ self.addLayerFromEndpoints( ++ self.interfaceEndpoints, ++ self.repository.interfaceFeedRateMultiplier.value, ++ self.repository.interfaceFlowRateMultiplier.value, ++ interfaceLayerThickness, ++ self.interfaceLayerThicknessOverLayerThickness, ++ self.interfaceStep, ++ z) ++ ++ def addInterfaceTables(self, interfaceExtrusionWidth): ++ 'Add interface tables.' ++ overhang = self.repository.infillOverhangOverExtrusionWidth.value * interfaceExtrusionWidth ++ self.interfaceEndpoints = [] ++ self.interfaceIntersectionsTableKeys = self.interfaceIntersectionsTable.keys() ++ self.interfaceSegmentsTable = {} ++ for yKey in self.interfaceIntersectionsTableKeys: ++ self.interfaceIntersectionsTable[yKey].sort() ++ y = yKey * self.interfaceStep ++ lineSegments = euclidean.getSegmentsFromXIntersections(self.interfaceIntersectionsTable[yKey], y) ++ xIntersectionIndexList = [] ++ for lineSegmentIndex in xrange(len(lineSegments)): ++ lineSegment = lineSegments[lineSegmentIndex] ++ endpointBegin = lineSegment[0] ++ endpointEnd = lineSegment[1] ++ endpointBegin.point = complex(self.baseStep * math.floor(endpointBegin.point.real / self.baseStep) - overhang, y) ++ endpointEnd.point = complex(self.baseStep * math.ceil(endpointEnd.point.real / self.baseStep) + overhang, y) ++ if endpointEnd.point.real > endpointBegin.point.real: ++ euclidean.addXIntersectionIndexesFromSegment(lineSegmentIndex, lineSegment, xIntersectionIndexList) ++ xIntersections = euclidean.getJoinOfXIntersectionIndexes(xIntersectionIndexList) ++ joinedSegments = euclidean.getSegmentsFromXIntersections(xIntersections, y) ++ if len(joinedSegments) > 0: ++ self.interfaceSegmentsTable[yKey] = joinedSegments ++ for joinedSegment in joinedSegments: ++ self.interfaceEndpoints += joinedSegment ++ ++ def addLayerFromEndpoints( ++ self, ++ endpoints, ++ feedRateMultiplier, ++ flowRateMultiplier, ++ layerLayerThickness, ++ layerThicknessRatio, ++ step, ++ z): ++ 'Add a layer from endpoints and raise the extrusion top.' ++ layerThicknessRatioSquared = layerThicknessRatio * layerThicknessRatio ++ feedRateMinute = self.feedRateMinute * feedRateMultiplier / layerThicknessRatioSquared ++ if len(endpoints) < 1: ++ return ++ aroundPixelTable = {} ++ aroundWidth = 0.34321 * step ++ paths = euclidean.getPathsFromEndpoints(endpoints, 1.5 * step, aroundPixelTable, aroundWidth) ++ self.addLayerLine(z) ++ if self.oldFlowRate != None: ++ self.addFlowRate(flowRateMultiplier * self.oldFlowRate) ++ for path in paths: ++ simplifiedPath = euclidean.getSimplifiedPath(path, step) ++ self.distanceFeedRate.addGcodeFromFeedRateThreadZ(feedRateMinute, simplifiedPath, self.travelFeedRateMinute, z) ++ self.extrusionTop += layerLayerThickness ++ self.addFlowRate(self.oldFlowRate) ++ ++ def addLayerLine(self, z): ++ 'Add the layer gcode line and close the last layer gcode block.' ++ if self.layerStarted: ++ self.distanceFeedRate.addLine('()') ++ self.distanceFeedRate.addLine('( %s )' % self.distanceFeedRate.getRounded(z)) # Indicate that a new layer is starting. ++ if self.beginLoop != None: ++ zBegin = self.extrusionTop + self.layerThickness ++ intercircle.addOrbitsIfLarge(self.distanceFeedRate, self.beginLoop, self.orbitalFeedRatePerSecond, self.temperatureChangeTimeBeforeRaft, zBegin) ++ self.beginLoop = None ++ self.layerStarted = True ++ ++ def addOperatingOrbits(self, boundaryLoops, pointComplex, temperatureChangeTime, z): ++ 'Add the orbits before the operating layers.' ++ if len(boundaryLoops) < 1: ++ return ++ insetBoundaryLoops = intercircle.getInsetLoopsFromLoops(boundaryLoops, self.perimeterWidth) ++ if len(insetBoundaryLoops) < 1: ++ insetBoundaryLoops = boundaryLoops ++ largestLoop = euclidean.getLargestLoop(insetBoundaryLoops) ++ if pointComplex != None: ++ largestLoop = euclidean.getLoopStartingClosest(self.perimeterWidth, pointComplex, largestLoop) ++ intercircle.addOrbitsIfLarge(self.distanceFeedRate, largestLoop, self.orbitalFeedRatePerSecond, temperatureChangeTime, z) ++ ++ def addRaft(self): ++ 'Add the raft.' ++ if len(self.boundaryLayers) < 0: ++ print('this should never happen, there are no boundary layers in addRaft') ++ return ++ self.baseLayerThicknessOverLayerThickness = self.repository.baseLayerThicknessOverLayerThickness.value ++ baseExtrusionWidth = self.perimeterWidth * self.baseLayerThicknessOverLayerThickness ++ self.baseStep = baseExtrusionWidth / self.repository.baseInfillDensity.value ++ self.interfaceLayerThicknessOverLayerThickness = self.repository.interfaceLayerThicknessOverLayerThickness.value ++ interfaceExtrusionWidth = self.perimeterWidth * self.interfaceLayerThicknessOverLayerThickness ++ self.interfaceStep = interfaceExtrusionWidth / self.repository.interfaceInfillDensity.value ++ self.setCornersZ() ++ self.cornerMinimumComplex = self.cornerMinimum.dropAxis() ++ originalExtent = self.cornerMaximumComplex - self.cornerMinimumComplex ++ self.raftOutsetRadius = self.repository.raftMargin.value + self.repository.raftAdditionalMarginOverLengthPercent.value * 0.01 * max(originalExtent.real, originalExtent.imag) ++ self.setBoundaryLayers() ++ outsetSeparateLoops = intercircle.getInsetSeparateLoopsFromLoops(self.boundaryLayers[0].loops, -self.raftOutsetRadius, 0.8) ++ self.interfaceIntersectionsTable = {} ++ euclidean.addXIntersectionsFromLoopsForTable(outsetSeparateLoops, self.interfaceIntersectionsTable, self.interfaceStep) ++ if len(self.supportLayers) > 0: ++ supportIntersectionsTable = self.supportLayers[0].xIntersectionsTable ++ euclidean.joinXIntersectionsTables(supportIntersectionsTable, self.interfaceIntersectionsTable) ++ self.addInterfaceTables(interfaceExtrusionWidth) ++ self.addRaftPerimeters() ++ self.baseIntersectionsTable = {} ++ complexRadius = complex(self.raftOutsetRadius, self.raftOutsetRadius) ++ self.complexHigh = complexRadius + self.cornerMaximumComplex ++ self.complexLow = self.cornerMinimumComplex - complexRadius ++ self.beginLoop = euclidean.getSquareLoopWiddershins(self.cornerMinimumComplex, self.cornerMaximumComplex) ++ if not intercircle.orbitsAreLarge(self.beginLoop, self.temperatureChangeTimeBeforeRaft): ++ self.beginLoop = None ++ if self.repository.baseLayers.value > 0: ++ self.addTemperatureLineIfDifferent(self.baseTemperature) ++ self.addBaseSegments(baseExtrusionWidth) ++ for baseLayerIndex in xrange(self.repository.baseLayers.value): ++ self.addBaseLayer() ++ if self.repository.interfaceLayers.value > 0: ++ self.addTemperatureLineIfDifferent(self.interfaceTemperature) ++ self.interfaceIntersectionsTableKeys.sort() ++ for interfaceLayerIndex in xrange(self.repository.interfaceLayers.value): ++ self.addInterfaceLayer() ++ self.operatingJump = self.extrusionTop + self.layerThickness * self.repository.operatingNozzleLiftOverLayerThickness.value ++ for boundaryLayer in self.boundaryLayers: ++ if self.operatingJump != None: ++ boundaryLayer.z += self.operatingJump ++ if self.repository.baseLayers.value > 0 or self.repository.interfaceLayers.value > 0: ++ boundaryZ = self.boundaryLayers[0].z ++ if self.layerStarted: ++ self.distanceFeedRate.addLine('()') ++ self.layerStarted = False ++ self.distanceFeedRate.addLine('( )') ++ self.addLayerLine(boundaryZ) ++ temperatureChangeTimeBeforeFirstLayer = self.getTemperatureChangeTime(self.objectFirstLayerPerimeterTemperature) ++ self.addTemperatureLineIfDifferent(self.objectFirstLayerPerimeterTemperature) ++ largestOutsetLoop = intercircle.getLargestInsetLoopFromLoop(euclidean.getLargestLoop(outsetSeparateLoops), -self.raftOutsetRadius) ++ intercircle.addOrbitsIfLarge(self.distanceFeedRate, largestOutsetLoop, self.orbitalFeedRatePerSecond, temperatureChangeTimeBeforeFirstLayer, boundaryZ) ++ self.addLineLayerStart = False ++ ++ def addRaftedLine( self, splitLine ): ++ 'Add elevated gcode line with operating feed rate.' ++ self.oldLocation = gcodec.getLocationFromSplitLine(self.oldLocation, splitLine) ++ self.feedRateMinute = gcodec.getFeedRateMinute(self.feedRateMinute, splitLine) ++ z = self.oldLocation.z ++ if self.operatingJump != None: ++ z += self.operatingJump ++ temperature = self.objectNextLayersTemperature ++ if self.layerIndex == 0: ++ if self.isPerimeterPath: ++ temperature = self.objectFirstLayerPerimeterTemperature ++ else: ++ temperature = self.objectFirstLayerInfillTemperature ++ self.addTemperatureLineIfDifferent(temperature) ++ self.distanceFeedRate.addGcodeMovementZWithFeedRate(self.feedRateMinute, self.oldLocation.dropAxis(), z) ++ ++ def addRaftPerimeters(self): ++ 'Add raft perimeters if there is a raft.' ++ interfaceOutset = self.halfPerimeterWidth * self.interfaceLayerThicknessOverLayerThickness ++ for supportLayer in self.supportLayers: ++ supportSegmentTable = supportLayer.supportSegmentTable ++ if len(supportSegmentTable) > 0: ++ outset = interfaceOutset ++ self.addRaftPerimetersByLoops(getLoopsBySegmentsDictionary(supportSegmentTable, self.interfaceStep), outset) ++ if self.repository.baseLayers.value < 1 and self.repository.interfaceLayers.value < 1: ++ return ++ overhangMultiplier = 1.0 + self.repository.infillOverhangOverExtrusionWidth.value + self.repository.infillOverhangOverExtrusionWidth.value ++ outset = self.halfPerimeterWidth ++ if self.repository.interfaceLayers.value > 0: ++ outset = max(interfaceOutset * overhangMultiplier, outset) ++ if self.repository.baseLayers.value > 0: ++ outset = max(self.halfPerimeterWidth * self.baseLayerThicknessOverLayerThickness * overhangMultiplier, outset) ++ self.addRaftPerimetersByLoops(getLoopsBySegmentsDictionary(self.interfaceSegmentsTable, self.interfaceStep), outset) ++ ++ def addRaftPerimetersByLoops(self, loops, outset): ++ 'Add raft perimeters to the gcode for loops.' ++ loops = intercircle.getInsetSeparateLoopsFromLoops(loops, -outset) ++ for loop in loops: ++ self.distanceFeedRate.addLine('()') ++ for point in loop: ++ roundedX = self.distanceFeedRate.getRounded(point.real) ++ roundedY = self.distanceFeedRate.getRounded(point.imag) ++ self.distanceFeedRate.addTagBracketedLine('raftPoint', 'X%s Y%s' % (roundedX, roundedY)) ++ self.distanceFeedRate.addLine('()') ++ ++ def addSegmentTablesToSupportLayers(self): ++ 'Add segment tables to the support layers.' ++ for supportLayer in self.supportLayers: ++ supportLayer.supportSegmentTable = {} ++ xIntersectionsTable = supportLayer.xIntersectionsTable ++ for xIntersectionsTableKey in xIntersectionsTable: ++ y = xIntersectionsTableKey * self.interfaceStep ++ supportLayer.supportSegmentTable[ xIntersectionsTableKey ] = euclidean.getSegmentsFromXIntersections( xIntersectionsTable[ xIntersectionsTableKey ], y ) ++ ++ def addSupportLayerTemperature(self, endpoints, z): ++ 'Add support layer and temperature before the object layer.' ++ self.distanceFeedRate.addLine('()') ++ self.distanceFeedRate.addLinesSetAbsoluteDistanceMode(self.supportStartLines) ++ self.addTemperatureOrbits(endpoints, self.supportedLayersTemperature, z) ++ aroundPixelTable = {} ++ aroundWidth = 0.34321 * self.interfaceStep ++ boundaryLoops = self.boundaryLayers[self.layerIndex].loops ++ halfSupportOutset = 0.5 * self.supportOutset ++ aroundBoundaryLoops = intercircle.getAroundsFromLoops(boundaryLoops, halfSupportOutset) ++ for aroundBoundaryLoop in aroundBoundaryLoops: ++ euclidean.addLoopToPixelTable(aroundBoundaryLoop, aroundPixelTable, aroundWidth) ++ paths = euclidean.getPathsFromEndpoints(endpoints, 1.5 * self.interfaceStep, aroundPixelTable, aroundWidth) ++ feedRateMinuteMultiplied = self.operatingFeedRateMinute ++ supportFlowRateMultiplied = self.supportFlowRate ++ if self.layerIndex == 0: ++ feedRateMinuteMultiplied *= self.objectFirstLayerFeedRateInfillMultiplier ++ if supportFlowRateMultiplied != None: ++ supportFlowRateMultiplied *= self.objectFirstLayerFlowRateInfillMultiplier ++ self.addFlowRate(supportFlowRateMultiplied) ++ for path in paths: ++ self.distanceFeedRate.addGcodeFromFeedRateThreadZ(feedRateMinuteMultiplied, path, self.travelFeedRateMinute, z) ++ self.addFlowRate(self.oldFlowRate) ++ self.addTemperatureOrbits(endpoints, self.supportLayersTemperature, z) ++ self.distanceFeedRate.addLinesSetAbsoluteDistanceMode(self.supportEndLines) ++ self.distanceFeedRate.addLine('()') ++ ++ def addSupportSegmentTable( self, layerIndex ): ++ 'Add support segments from the boundary layers.' ++ aboveLayer = self.boundaryLayers[ layerIndex + 1 ] ++ aboveLoops = aboveLayer.loops ++ supportLayer = self.supportLayers[layerIndex] ++ if len( aboveLoops ) < 1: ++ return ++ boundaryLayer = self.boundaryLayers[layerIndex] ++ rise = aboveLayer.z - boundaryLayer.z ++ outsetSupportLoops = intercircle.getInsetSeparateLoopsFromLoops(boundaryLayer.loops, -self.minimumSupportRatio * rise) ++ numberOfSubSteps = 4 ++ subStepSize = self.interfaceStep / float( numberOfSubSteps ) ++ aboveIntersectionsTable = {} ++ euclidean.addXIntersectionsFromLoopsForTable( aboveLoops, aboveIntersectionsTable, subStepSize ) ++ outsetIntersectionsTable = {} ++ euclidean.addXIntersectionsFromLoopsForTable( outsetSupportLoops, outsetIntersectionsTable, subStepSize ) ++ euclidean.subtractXIntersectionsTable( aboveIntersectionsTable, outsetIntersectionsTable ) ++ for aboveIntersectionsTableKey in aboveIntersectionsTable.keys(): ++ supportIntersectionsTableKey = int( round( float( aboveIntersectionsTableKey ) / numberOfSubSteps ) ) ++ xIntersectionIndexList = [] ++ if supportIntersectionsTableKey in supportLayer.xIntersectionsTable: ++ euclidean.addXIntersectionIndexesFromXIntersections( 0, xIntersectionIndexList, supportLayer.xIntersectionsTable[ supportIntersectionsTableKey ] ) ++ euclidean.addXIntersectionIndexesFromXIntersections( 1, xIntersectionIndexList, aboveIntersectionsTable[ aboveIntersectionsTableKey ] ) ++ supportLayer.xIntersectionsTable[ supportIntersectionsTableKey ] = euclidean.getJoinOfXIntersectionIndexes( xIntersectionIndexList ) ++ ++ def addTemperatureLineIfDifferent(self, temperature): ++ 'Add a line of temperature if different.' ++ if temperature == None: ++ return ++ temperatureOutputString = euclidean.getRoundedToThreePlaces(temperature) ++ if temperatureOutputString == self.oldTemperatureOutputString: ++ return ++ if temperatureOutputString != None: ++ self.distanceFeedRate.addLine('M104 S' + temperatureOutputString) # Set temperature. ++ self.oldTemperatureOutputString = temperatureOutputString ++ ++ def addTemperatureOrbits( self, endpoints, temperature, z ): ++ 'Add the temperature and orbits around the support layer.' ++ if self.layerIndex < 0: ++ return ++ boundaryLoops = self.boundaryLayers[self.layerIndex].loops ++ temperatureTimeChange = self.getTemperatureChangeTime( temperature ) ++ self.addTemperatureLineIfDifferent( temperature ) ++ if len( boundaryLoops ) < 1: ++ layerCornerHigh = complex(-987654321.0, -987654321.0) ++ layerCornerLow = complex(987654321.0, 987654321.0) ++ for endpoint in endpoints: ++ layerCornerHigh = euclidean.getMaximum( layerCornerHigh, endpoint.point ) ++ layerCornerLow = euclidean.getMinimum( layerCornerLow, endpoint.point ) ++ squareLoop = euclidean.getSquareLoopWiddershins( layerCornerLow, layerCornerHigh ) ++ intercircle.addOrbitsIfLarge( self.distanceFeedRate, squareLoop, self.orbitalFeedRatePerSecond, temperatureTimeChange, z ) ++ return ++ perimeterInset = 0.4 * self.perimeterWidth ++ insetBoundaryLoops = intercircle.getInsetLoopsFromLoops(boundaryLoops, perimeterInset) ++ if len( insetBoundaryLoops ) < 1: ++ insetBoundaryLoops = boundaryLoops ++ largestLoop = euclidean.getLargestLoop( insetBoundaryLoops ) ++ intercircle.addOrbitsIfLarge( self.distanceFeedRate, largestLoop, self.orbitalFeedRatePerSecond, temperatureTimeChange, z ) ++ ++ def addToFillXIntersectionIndexTables( self, supportLayer ): ++ 'Add fill segments from the boundary layers.' ++ supportLoops = supportLayer.supportLoops ++ supportLayer.fillXIntersectionsTable = {} ++ if len(supportLoops) < 1: ++ return ++ euclidean.addXIntersectionsFromLoopsForTable( supportLoops, supportLayer.fillXIntersectionsTable, self.interfaceStep ) ++ ++ def extendXIntersections( self, loops, radius, xIntersectionsTable ): ++ 'Extend the support segments.' ++ xIntersectionsTableKeys = xIntersectionsTable.keys() ++ for xIntersectionsTableKey in xIntersectionsTableKeys: ++ lineSegments = euclidean.getSegmentsFromXIntersections( xIntersectionsTable[ xIntersectionsTableKey ], xIntersectionsTableKey ) ++ xIntersectionIndexList = [] ++ loopXIntersections = [] ++ euclidean.addXIntersectionsFromLoops( loops, loopXIntersections, xIntersectionsTableKey ) ++ for lineSegmentIndex in xrange( len( lineSegments ) ): ++ lineSegment = lineSegments[ lineSegmentIndex ] ++ extendedLineSegment = getExtendedLineSegment( radius, lineSegment, loopXIntersections ) ++ if extendedLineSegment != None: ++ euclidean.addXIntersectionIndexesFromSegment( lineSegmentIndex, extendedLineSegment, xIntersectionIndexList ) ++ xIntersections = euclidean.getJoinOfXIntersectionIndexes( xIntersectionIndexList ) ++ if len( xIntersections ) > 0: ++ xIntersectionsTable[ xIntersectionsTableKey ] = xIntersections ++ else: ++ del xIntersectionsTable[ xIntersectionsTableKey ] ++ ++ def getCraftedGcode(self, gcodeText, repository): ++ 'Parse gcode text and store the raft gcode.' ++ self.repository = repository ++ self.minimumSupportRatio = math.tan( math.radians( repository.supportMinimumAngle.value ) ) ++ self.supportEndLines = settings.getAlterationFileLines(repository.nameOfSupportEndFile.value) ++ self.supportStartLines = settings.getAlterationFileLines(repository.nameOfSupportStartFile.value) ++ self.lines = archive.getTextLines(gcodeText) ++ self.parseInitialization() ++ self.temperatureChangeTimeBeforeRaft = 0.0 ++ if self.repository.initialCircling.value: ++ maxBaseInterfaceTemperature = max(self.baseTemperature, self.interfaceTemperature) ++ firstMaxTemperature = max(maxBaseInterfaceTemperature, self.objectFirstLayerPerimeterTemperature) ++ self.temperatureChangeTimeBeforeRaft = self.getTemperatureChangeTime(firstMaxTemperature) ++ if repository.addRaftElevateNozzleOrbitSetAltitude.value: ++ self.addRaft() ++ self.addTemperatureLineIfDifferent( self.objectFirstLayerPerimeterTemperature ) ++ for line in self.lines[self.lineIndex :]: ++ self.parseLine(line) ++ return gcodec.getGcodeWithoutDuplication('M108', self.distanceFeedRate.output.getvalue()) ++ ++ def getElevatedBoundaryLine( self, splitLine ): ++ 'Get elevated boundary gcode line.' ++ location = gcodec.getLocationFromSplitLine(None, splitLine) ++ if self.operatingJump != None: ++ location.z += self.operatingJump ++ return self.distanceFeedRate.getBoundaryLine( location ) ++ ++ def getInsetLoops( self, boundaryLayerIndex ): ++ 'Inset the support loops if they are not already inset.' ++ if boundaryLayerIndex not in self.insetTable: ++ self.insetTable[ boundaryLayerIndex ] = intercircle.getInsetSeparateLoopsFromLoops(self.boundaryLayers[ boundaryLayerIndex ].loops, self.quarterPerimeterWidth) ++ return self.insetTable[ boundaryLayerIndex ] ++ ++ def getInsetLoopsAbove( self, boundaryLayerIndex ): ++ 'Get the inset loops above the boundary layer index.' ++ for aboveLayerIndex in xrange( boundaryLayerIndex + 1, len(self.boundaryLayers) ): ++ if len( self.boundaryLayers[ aboveLayerIndex ].loops ) > 0: ++ return self.getInsetLoops( aboveLayerIndex ) ++ return [] ++ ++ def getInsetLoopsBelow( self, boundaryLayerIndex ): ++ 'Get the inset loops below the boundary layer index.' ++ for belowLayerIndex in xrange( boundaryLayerIndex - 1, - 1, - 1 ): ++ if len( self.boundaryLayers[ belowLayerIndex ].loops ) > 0: ++ return self.getInsetLoops( belowLayerIndex ) ++ return [] ++ ++ def getStepsUntilEnd( self, begin, end, stepSize ): ++ 'Get steps from the beginning until the end.' ++ step = begin ++ steps = [] ++ while step < end: ++ steps.append( step ) ++ step += stepSize ++ return steps ++ ++ def getSupportEndpoints(self): ++ 'Get the support layer segments.' ++ if len(self.supportLayers) <= self.layerIndex: ++ return [] ++ supportSegmentTable = self.supportLayers[self.layerIndex].supportSegmentTable ++ if self.layerIndex % 2 == 1 and self.repository.supportCrossHatch.value: ++ return getVerticalEndpoints(supportSegmentTable, self.interfaceStep, 0.1 * self.perimeterWidth, self.interfaceStep) ++ return euclidean.getEndpointsFromSegmentTable(supportSegmentTable) ++ ++ def getTemperatureChangeTime( self, temperature ): ++ 'Get the temperature change time.' ++ if temperature == None: ++ return 0.0 ++ oldTemperature = 25.0 # typical chamber temperature ++ if self.oldTemperatureOutputString != None: ++ oldTemperature = float( self.oldTemperatureOutputString ) ++ if temperature == oldTemperature: ++ return 0.0 ++ if temperature > oldTemperature: ++ return ( temperature - oldTemperature ) / self.heatingRate ++ return ( oldTemperature - temperature ) / abs( self.coolingRate ) ++ ++ 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.baseTemperature = float(splitLine[1]) ++ elif firstWord == '(': ++ self.coolingRate = float(splitLine[1]) ++ elif firstWord == '()': ++ self.distanceFeedRate.addTagBracketedProcedure('raft') ++ elif firstWord == '(': ++ self.heatingRate = float(splitLine[1]) ++ elif firstWord == '(': ++ self.interfaceTemperature = float(splitLine[1]) ++ elif firstWord == '(': ++ return ++ elif firstWord == '(': ++ self.layerThickness = float(splitLine[1]) ++ elif firstWord == '(': ++ self.objectFirstLayerFeedRateInfillMultiplier = float(splitLine[1]) ++ elif firstWord == '(': ++ self.objectFirstLayerFlowRateInfillMultiplier = float(splitLine[1]) ++ elif firstWord == '(': ++ self.objectFirstLayerInfillTemperature = float(splitLine[1]) ++ elif firstWord == '(': ++ self.objectFirstLayerPerimeterTemperature = float(splitLine[1]) ++ elif firstWord == '(': ++ self.objectNextLayersTemperature = float(splitLine[1]) ++ elif firstWord == '(': ++ self.orbitalFeedRatePerSecond = float(splitLine[1]) ++ elif firstWord == '(': ++ self.operatingFeedRateMinute = 60.0 * float(splitLine[1]) ++ self.feedRateMinute = self.operatingFeedRateMinute ++ elif firstWord == '(': ++ self.oldFlowRate = float(splitLine[1]) ++ self.supportFlowRate = self.oldFlowRate * self.repository.supportFlowRateOverOperatingFlowRate.value ++ elif firstWord == '(': ++ self.perimeterWidth = float(splitLine[1]) ++ self.halfPerimeterWidth = 0.5 * self.perimeterWidth ++ self.quarterPerimeterWidth = 0.25 * self.perimeterWidth ++ self.supportOutset = self.perimeterWidth + self.perimeterWidth * self.repository.supportGapOverPerimeterExtrusionWidth.value ++ elif firstWord == '(': ++ self.supportLayersTemperature = float(splitLine[1]) ++ elif firstWord == '(': ++ self.supportedLayersTemperature = float(splitLine[1]) ++ elif firstWord == '(': ++ self.travelFeedRateMinute = 60.0 * float(splitLine[1]) ++ self.distanceFeedRate.addLine(line) ++ ++ def parseLine(self, line): ++ 'Parse a gcode line and add it to the raft skein.' ++ splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) ++ if len(splitLine) < 1: ++ return ++ firstWord = splitLine[0] ++ if firstWord == 'G1': ++ if self.extrusionStart: ++ self.addRaftedLine(splitLine) ++ return ++ elif firstWord == 'M101': ++ if self.isStartupEarly: ++ self.isStartupEarly = False ++ return ++ elif firstWord == 'M108': ++ self.oldFlowRate = float(splitLine[1][1 :]) ++ elif firstWord == '(': ++ line = self.getElevatedBoundaryLine(splitLine) ++ elif firstWord == '()': ++ self.extrusionStart = False ++ self.distanceFeedRate.addLine( self.operatingLayerEndLine ) ++ elif firstWord == '(': ++ self.layerIndex += 1 ++ settings.printProgress(self.layerIndex, 'raft') ++ boundaryLayer = None ++ layerZ = self.extrusionTop + float(splitLine[1]) ++ if len(self.boundaryLayers) > 0: ++ boundaryLayer = self.boundaryLayers[self.layerIndex] ++ layerZ = boundaryLayer.z ++ if self.operatingJump != None: ++ line = '( %s )' % self.distanceFeedRate.getRounded( layerZ ) ++ if self.layerStarted and self.addLineLayerStart: ++ self.distanceFeedRate.addLine('()') ++ self.layerStarted = False ++ if self.layerIndex > len(self.supportLayers) + 1: ++ self.distanceFeedRate.addLine( self.operatingLayerEndLine ) ++ self.operatingLayerEndLine = '' ++ if self.addLineLayerStart: ++ self.distanceFeedRate.addLine(line) ++ self.addLineLayerStart = True ++ line = '' ++ endpoints = self.getSupportEndpoints() ++ if self.layerIndex == 1: ++ if len(endpoints) < 1: ++ temperatureChangeTimeBeforeNextLayers = self.getTemperatureChangeTime( self.objectNextLayersTemperature ) ++ self.addTemperatureLineIfDifferent( self.objectNextLayersTemperature ) ++ if self.repository.addRaftElevateNozzleOrbitSetAltitude.value and len( boundaryLayer.loops ) > 0: ++ self.addOperatingOrbits( boundaryLayer.loops, euclidean.getXYComplexFromVector3( self.oldLocation ), temperatureChangeTimeBeforeNextLayers, layerZ ) ++ if len(endpoints) > 0: ++ self.addSupportLayerTemperature( endpoints, layerZ ) ++ elif firstWord == '(' or firstWord == '()': ++ self.isPerimeterPath = True ++ elif firstWord == '()' or firstWord == '()': ++ self.isPerimeterPath = False ++ self.distanceFeedRate.addLine(line) ++ ++ def setBoundaryLayers(self): ++ 'Set the boundary layers.' ++ if self.repository.supportChoiceNone.value: ++ return ++ if len(self.boundaryLayers) < 2: ++ return ++ if self.repository.supportChoiceEmptyLayersOnly.value: ++ supportLayer = SupportLayer([]) ++ self.supportLayers.append(supportLayer) ++ for boundaryLayerIndex in xrange(1, len(self.boundaryLayers) -1): ++ self.addEmptyLayerSupport(boundaryLayerIndex) ++ self.truncateSupportSegmentTables() ++ self.addSegmentTablesToSupportLayers() ++ return ++ for boundaryLayer in self.boundaryLayers: ++ # thresholdRadius of 0.8 is needed to avoid the ripple inset bug http://hydraraptor.blogspot.com/2010/12/crackers.html ++ supportLoops = intercircle.getInsetSeparateLoopsFromLoops(boundaryLayer.loops, -self.supportOutset, 0.8) ++ supportLayer = SupportLayer(supportLoops) ++ self.supportLayers.append(supportLayer) ++ for supportLayerIndex in xrange(len(self.supportLayers) - 1): ++ self.addSupportSegmentTable(supportLayerIndex) ++ self.truncateSupportSegmentTables() ++ for supportLayerIndex in xrange(len(self.supportLayers) - 1): ++ boundaryLoops = self.boundaryLayers[supportLayerIndex].loops ++ self.extendXIntersections( boundaryLoops, self.supportOutset, self.supportLayers[supportLayerIndex].xIntersectionsTable) ++ for supportLayer in self.supportLayers: ++ self.addToFillXIntersectionIndexTables(supportLayer) ++ if self.repository.supportChoiceExteriorOnly.value: ++ for supportLayerIndex in xrange(1, len(self.supportLayers)): ++ self.subtractJoinedFill(supportLayerIndex) ++ for supportLayer in self.supportLayers: ++ euclidean.subtractXIntersectionsTable(supportLayer.xIntersectionsTable, supportLayer.fillXIntersectionsTable) ++ for supportLayerIndex in xrange(len(self.supportLayers) - 2, -1, -1): ++ xIntersectionsTable = self.supportLayers[supportLayerIndex].xIntersectionsTable ++ aboveXIntersectionsTable = self.supportLayers[supportLayerIndex + 1].xIntersectionsTable ++ euclidean.joinXIntersectionsTables(aboveXIntersectionsTable, xIntersectionsTable) ++ for supportLayerIndex in xrange(len(self.supportLayers)): ++ supportLayer = self.supportLayers[supportLayerIndex] ++ self.extendXIntersections(supportLayer.supportLoops, self.raftOutsetRadius, supportLayer.xIntersectionsTable) ++ for supportLayer in self.supportLayers: ++ euclidean.subtractXIntersectionsTable(supportLayer.xIntersectionsTable, supportLayer.fillXIntersectionsTable) ++ self.addSegmentTablesToSupportLayers() ++ ++ def setCornersZ(self): ++ 'Set maximum and minimum corners and z.' ++ boundaryLoop = None ++ boundaryLayer = None ++ layerIndex = - 1 ++ self.cornerMaximumComplex = complex(-912345678.0, -912345678.0) ++ self.cornerMinimum = Vector3(912345678.0, 912345678.0, 912345678.0) ++ self.firstLayerLoops = [] ++ for line in self.lines[self.lineIndex :]: ++ splitLine = gcodec.getSplitLineBeforeBracketSemicolon(line) ++ firstWord = gcodec.getFirstWord(splitLine) ++ if firstWord == '()': ++ boundaryLoop = None ++ elif firstWord == '(': ++ location = gcodec.getLocationFromSplitLine(None, splitLine) ++ if boundaryLoop == None: ++ boundaryLoop = [] ++ boundaryLayer.loops.append(boundaryLoop) ++ boundaryLoop.append(location.dropAxis()) ++ self.cornerMaximumComplex = euclidean.getMaximum(self.cornerMaximumComplex, location.dropAxis()) ++ self.cornerMinimum.minimize(location) ++ elif firstWord == '(': ++ z = float(splitLine[1]) ++ boundaryLayer = euclidean.LoopLayer(z) ++ self.boundaryLayers.append(boundaryLayer) ++ elif firstWord == '(': ++ layerIndex += 1 ++ if self.repository.supportChoiceNone.value: ++ if layerIndex > 1: ++ return ++ ++ def subtractJoinedFill( self, supportLayerIndex ): ++ 'Join the fill then subtract it from the support layer table.' ++ supportLayer = self.supportLayers[supportLayerIndex] ++ fillXIntersectionsTable = supportLayer.fillXIntersectionsTable ++ belowFillXIntersectionsTable = self.supportLayers[ supportLayerIndex - 1 ].fillXIntersectionsTable ++ euclidean.joinXIntersectionsTables( belowFillXIntersectionsTable, supportLayer.fillXIntersectionsTable ) ++ euclidean.subtractXIntersectionsTable( supportLayer.xIntersectionsTable, supportLayer.fillXIntersectionsTable ) ++ ++ def truncateSupportSegmentTables(self): ++ 'Truncate the support segments after the last support segment which contains elements.' ++ for supportLayerIndex in xrange( len(self.supportLayers) - 1, - 1, - 1 ): ++ if len( self.supportLayers[supportLayerIndex].xIntersectionsTable ) > 0: ++ self.supportLayers = self.supportLayers[ : supportLayerIndex + 1 ] ++ return ++ self.supportLayers = [] ++ ++ ++class SupportLayer: ++ 'Support loops with segment tables.' ++ def __init__( self, supportLoops ): ++ self.supportLoops = supportLoops ++ self.supportSegmentTable = {} ++ self.xIntersectionsTable = {} ++ ++ def __repr__(self): ++ 'Get the string representation of this loop layer.' ++ return '%s' % ( self.supportLoops ) ++ ++ ++def main(): ++ 'Display the raft dialog.' ++ if len(sys.argv) > 1: ++ writeOutput(' '.join(sys.argv[1 :])) ++ else: ++ settings.startMainLoopFromConstructor(getNewRepository()) ++ ++if __name__ == '__main__': ++ main() +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/speed.py ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/speed.py +@@ -180,18 +180,22 @@ + self.dutyCycleAtBeginning = settings.FloatSpin().getFromValue( 0.0, 'Duty Cyle at Beginning (portion):', self, 1.0, 1.0 ) + self.dutyCycleAtEnding = settings.FloatSpin().getFromValue( 0.0, 'Duty Cyle at Ending (portion):', self, 1.0, 0.0 ) + settings.LabelSeparator().getFromRepository(self) +- self.feedRatePerSecond = settings.FloatSpin().getFromValue( 2.0, 'Feed Rate (mm/s):', self, 50.0, 16.0 ) +- self.flowRateSetting = settings.FloatSpin().getFromValue( 50.0, 'Flow Rate Setting (float):', self, 250.0, 210.0 ) ++ self.feedRatePerSecond = settings.FloatSpin().getFromValue( 2.0, 'Feed Rate (mm/s):', self, 250.0, 50.0 ) ++ self.flowRateSetting = settings.FloatSpin().getFromValue( 50.0, 'Flow Rate Setting (float):', self, 250.0, 50.0 ) + settings.LabelSeparator().getFromRepository(self) +- settings.LabelDisplay().getFromName('- Object First Layer -', self) ++ settings.LabelDisplay().getFromName('- Object First Layers -', self) + self.objectFirstLayerFeedRateInfillMultiplier = settings.FloatSpin().getFromValue( + 0.2, 'Object First Layer Feed Rate Infill Multiplier (ratio):', self, 1.0, 0.4) + self.objectFirstLayerFeedRatePerimeterMultiplier = settings.FloatSpin().getFromValue( + 0.2, 'Object First Layer Feed Rate Perimeter Multiplier (ratio):', self, 1.0, 0.4) ++ self.objectFirstLayerFeedRateTravelMultiplier = settings.FloatSpin().getFromValue( ++ 0.2, 'Object First Layer Feed Rate Travel Multiplier (ratio):', self, 1.0, 0.4) + self.objectFirstLayerFlowRateInfillMultiplier = settings.FloatSpin().getFromValue( + 0.2, 'Object First Layer Flow Rate Infill Multiplier (ratio):', self, 1.0, 0.4) + self.objectFirstLayerFlowRatePerimeterMultiplier = settings.FloatSpin().getFromValue( + 0.2, 'Object First Layer Flow Rate Perimeter Multiplier (ratio):', self, 1.0, 0.4) ++ self.objectFirstLayersLayerAmount = settings.IntSpin().getFromValue( ++ 1, 'Object First Layers Amount Of Layers For Speed Change:', self, 10, 3) + settings.LabelSeparator().getFromRepository(self) + self.orbitalFeedRateOverOperatingFeedRate = settings.FloatSpin().getFromValue( 0.1, 'Orbital Feed Rate over Operating Feed Rate (ratio):', self, 0.9, 0.5 ) + self.maximumZFeedRatePerSecond = settings.FloatSpin().getFromValue(0.5, 'Maximum Z Feed Rate (mm/s):', self, 10.0, 1.0) +@@ -200,7 +204,7 @@ + self.perimeterFeedRateMultiplier = settings.FloatSpin().getFromValue(0.5, 'Perimeter Feed Rate Multiplier (ratio):', self, 1.0, 1.0) + self.perimeterFlowRateMultiplier = settings.FloatSpin().getFromValue(0.5, 'Perimeter Flow Rate Multiplier (ratio):', self, 1.0, 1.0) + settings.LabelSeparator().getFromRepository(self) +- self.travelFeedRatePerSecond = settings.FloatSpin().getFromValue( 2.0, 'Travel Feed Rate (mm/s):', self, 50.0, 16.0 ) ++ self.travelFeedRatePerSecond = settings.FloatSpin().getFromValue( 2.0, 'Travel Feed Rate (mm/s):', self, 350.0, 250.0 ) + self.executeTitle = 'Speed' + + def execute(self): +@@ -233,11 +237,11 @@ + flowRate *= self.repository.bridgeFlowRateMultiplier.value + if self.isPerimeterPath: + flowRate *= self.repository.perimeterFlowRateMultiplier.value +- if self.layerIndex == 0: ++ if self.layerIndex < self.repository.objectFirstLayersLayerAmount.value: + if self.isPerimeterPath: +- flowRate *= self.repository.objectFirstLayerFlowRatePerimeterMultiplier.value ++ flowRate *= ((self.repository.objectFirstLayerFlowRatePerimeterMultiplier.value * (self.repository.objectFirstLayersLayerAmount.value - self.layerIndex)) + self.layerIndex) / self.repository.objectFirstLayersLayerAmount.value + else: +- flowRate *= self.repository.objectFirstLayerFlowRateInfillMultiplier.value ++ flowRate *= ((self.repository.objectFirstLayerFlowRateInfillMultiplier.value * (self.repository.objectFirstLayersLayerAmount.value - self.layerIndex)) + self.layerIndex) / self.repository.objectFirstLayersLayerAmount.value + if flowRate != self.oldFlowRate: + self.distanceFeedRate.addLine('M108 S' + euclidean.getFourSignificantFigures(flowRate)) + self.oldFlowRate = flowRate +@@ -270,14 +274,16 @@ + feedRateMinute *= self.repository.bridgeFeedRateMultiplier.value + if self.isPerimeterPath: + feedRateMinute *= self.repository.perimeterFeedRateMultiplier.value +- if self.layerIndex == 0: ++ if self.layerIndex < self.repository.objectFirstLayersLayerAmount.value: + if self.isPerimeterPath: +- feedRateMinute *= self.repository.objectFirstLayerFeedRatePerimeterMultiplier.value ++ feedRateMinute *= ((self.repository.objectFirstLayerFeedRatePerimeterMultiplier.value * (self.repository.objectFirstLayersLayerAmount.value - self.layerIndex)) + self.layerIndex) / self.repository.objectFirstLayersLayerAmount.value + else: +- feedRateMinute *= self.repository.objectFirstLayerFeedRateInfillMultiplier.value ++ feedRateMinute *= ((self.repository.objectFirstLayerFeedRateInfillMultiplier.value * (self.repository.objectFirstLayersLayerAmount.value - self.layerIndex)) + self.layerIndex) / self.repository.objectFirstLayersLayerAmount.value + self.addFlowRateLine() + if not self.isExtruderActive: + feedRateMinute = self.travelFeedRateMinute ++ if self.layerIndex < self.repository.objectFirstLayersLayerAmount.value: ++ feedRateMinute *= ((self.repository.objectFirstLayerFeedRateTravelMultiplier.value * (self.repository.objectFirstLayersLayerAmount.value - self.layerIndex)) + self.layerIndex) / self.repository.objectFirstLayersLayerAmount.value + return self.distanceFeedRate.getLineWithFeedRate(feedRateMinute, line, splitLine) + + def parseInitialization(self): +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/temperature.py ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/temperature.py +@@ -126,7 +126,7 @@ + skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.temperature.html', self ) + self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Temperature', self, '') + self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Temperature') +- self.activateTemperature = settings.BooleanSetting().getFromValue('Activate Temperature', self, True ) ++ self.activateTemperature = settings.BooleanSetting().getFromValue('Activate Temperature', self, False ) + settings.LabelSeparator().getFromRepository(self) + settings.LabelDisplay().getFromName('- Rate -', self ) + self.coolingRate = settings.FloatSpin().getFromValue( 1.0, 'Cooling Rate (Celcius/second):', self, 20.0, 3.0 ) +--- ori/46/skeinforge_application/skeinforge_plugins/craft_plugins/temperature.py.orig ++++ target/SF46/skeinforge_application/skeinforge_plugins/craft_plugins/temperature.py.orig +@@ -0,0 +1,204 @@ ++""" ++This page is in the table of contents. ++Temperature is a plugin to set the temperature for the entire extrusion. ++ ++The temperature manual page is at: ++http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Temperature ++ ++==Operation== ++The default 'Activate Temperature' checkbox is on. When it is on, the functions described below will work, when it is off, nothing will be done. ++ ++==Settings== ++===Rate=== ++The default cooling rate and heating rate for the extruder were both been derived from bothacker's graph at: ++http://bothacker.com/wp-content/uploads/2009/09/18h5m53s9.29.2009.png ++ ++====Cooling Rate==== ++Default is three degrees Celcius per second. ++ ++Defines the cooling rate of the extruder. ++ ++====Heating Rate==== ++Default is ten degrees Celcius per second. ++ ++Defines the heating rate of the extruder. ++ ++===Temperature=== ++====Base Temperature==== ++Default for ABS is two hundred degrees Celcius. ++ ++Defines the raft base temperature. ++ ++====Interface Temperature==== ++Default for ABS is two hundred degrees Celcius. ++ ++Defines the raft interface temperature. ++ ++====Object First Layer Infill Temperature==== ++Default for ABS is 195 degrees Celcius. ++ ++Defines the infill temperature of the first layer of the object. ++ ++====Object First Layer Perimeter Temperature==== ++Default for ABS is two hundred and twenty degrees Celcius. ++ ++Defines the perimeter temperature of the first layer of the object. ++ ++====Object Next Layers Temperature==== ++Default for ABS is two hundred and thirty degrees Celcius. ++ ++Defines the temperature of the next layers of the object. ++ ++====Support Layers Temperature==== ++Default for ABS is two hundred degrees Celcius. ++ ++Defines the support layers temperature. ++ ++====Supported Layers Temperature==== ++Default for ABS is two hundred and thirty degrees Celcius. ++ ++Defines the temperature of the supported layers of the object, those layers which are right above a support layer. ++ ++==Examples== ++The following examples add temperature information to the file Screw Holder Bottom.stl. The examples are run in a terminal in the folder which contains Screw Holder Bottom.stl and temperature.py. ++ ++> python temperature.py ++This brings up the temperature dialog. ++ ++> python temperature.py Screw Holder Bottom.stl ++The temperature tool is parsing the file: ++Screw Holder Bottom.stl ++.. ++The temperature tool has created the file: ++.. Screw Holder Bottom_temperature.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 fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret ++from fabmetheus_utilities import archive ++from fabmetheus_utilities import euclidean ++from fabmetheus_utilities import gcodec ++from fabmetheus_utilities import intercircle ++from fabmetheus_utilities import settings ++from skeinforge_application.skeinforge_utilities import skeinforge_craft ++from skeinforge_application.skeinforge_utilities import skeinforge_polyfile ++from skeinforge_application.skeinforge_utilities import skeinforge_profile ++import math ++import sys ++ ++ ++__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' ++__date__ = '$Date: 2008/21/04 $' ++__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' ++ ++ ++def getCraftedText( fileName, text='', repository=None): ++ "Temperature the file or text." ++ return getCraftedTextFromText(archive.getTextIfEmpty(fileName, text), repository) ++ ++def getCraftedTextFromText(gcodeText, repository=None): ++ "Temperature a gcode linear move text." ++ if gcodec.isProcedureDoneOrFileIsEmpty( gcodeText, 'temperature'): ++ return gcodeText ++ if repository == None: ++ repository = settings.getReadRepository( TemperatureRepository() ) ++ if not repository.activateTemperature.value: ++ return gcodeText ++ return TemperatureSkein().getCraftedGcode(gcodeText, repository) ++ ++def getNewRepository(): ++ 'Get new repository.' ++ return TemperatureRepository() ++ ++def writeOutput(fileName, shouldAnalyze=True): ++ "Temperature a gcode linear move file." ++ skeinforge_craft.writeChainTextWithNounMessage(fileName, 'temperature', shouldAnalyze) ++ ++ ++class TemperatureRepository: ++ "A class to handle the temperature settings." ++ def __init__(self): ++ "Set the default settings, execute title & settings fileName." ++ skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge_plugins.craft_plugins.temperature.html', self ) ++ self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Temperature', self, '') ++ self.openWikiManualHelpPage = settings.HelpPage().getOpenFromAbsolute('http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge_Temperature') ++ self.activateTemperature = settings.BooleanSetting().getFromValue('Activate Temperature', self, True ) ++ settings.LabelSeparator().getFromRepository(self) ++ settings.LabelDisplay().getFromName('- Rate -', self ) ++ self.coolingRate = settings.FloatSpin().getFromValue( 1.0, 'Cooling Rate (Celcius/second):', self, 20.0, 3.0 ) ++ self.heatingRate = settings.FloatSpin().getFromValue( 1.0, 'Heating Rate (Celcius/second):', self, 20.0, 10.0 ) ++ settings.LabelSeparator().getFromRepository(self) ++ settings.LabelDisplay().getFromName('- Temperature -', self ) ++ self.baseTemperature = settings.FloatSpin().getFromValue( 140.0, 'Base Temperature (Celcius):', self, 260.0, 200.0 ) ++ self.interfaceTemperature = settings.FloatSpin().getFromValue( 140.0, 'Interface Temperature (Celcius):', self, 260.0, 200.0 ) ++ self.objectFirstLayerInfillTemperature = settings.FloatSpin().getFromValue( 140.0, 'Object First Layer Infill Temperature (Celcius):', self, 260.0, 195.0 ) ++ self.objectFirstLayerPerimeterTemperature = settings.FloatSpin().getFromValue( 140.0, 'Object First Layer Perimeter Temperature (Celcius):', self, 260.0, 220.0 ) ++ self.objectNextLayersTemperature = settings.FloatSpin().getFromValue( 140.0, 'Object Next Layers Temperature (Celcius):', self, 260.0, 230.0 ) ++ self.supportLayersTemperature = settings.FloatSpin().getFromValue( 140.0, 'Support Layers Temperature (Celcius):', self, 260.0, 200.0 ) ++ self.supportedLayersTemperature = settings.FloatSpin().getFromValue( 140.0, 'Supported Layers Temperature (Celcius):', self, 260.0, 230.0 ) ++ self.executeTitle = 'Temperature' ++ ++ def execute(self): ++ "Temperature button has been clicked." ++ fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) ++ for fileName in fileNames: ++ writeOutput(fileName) ++ ++ ++class TemperatureSkein: ++ "A class to temperature a skein of extrusions." ++ def __init__(self): ++ self.distanceFeedRate = gcodec.DistanceFeedRate() ++ self.lineIndex = 0 ++ self.lines = None ++ ++ def getCraftedGcode(self, gcodeText, repository): ++ "Parse gcode text and store the temperature gcode." ++ self.repository = repository ++ self.lines = archive.getTextLines(gcodeText) ++ if self.repository.coolingRate.value < 0.1: ++ print('The cooling rate should be more than 0.1, any cooling rate less than 0.1 will be treated as 0.1.') ++ self.repository.coolingRate.value = 0.1 ++ if self.repository.heatingRate.value < 0.1: ++ print('The heating rate should be more than 0.1, any heating rate less than 0.1 will be treated as 0.1.') ++ self.repository.heatingRate.value = 0.1 ++ self.parseInitialization() ++ self.distanceFeedRate.addLines( self.lines[self.lineIndex :] ) ++ return self.distanceFeedRate.output.getvalue() ++ ++ 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.addTagBracketedProcedure('temperature') ++ return ++ elif firstWord == '(': ++ self.distanceFeedRate.addTagBracketedLine('coolingRate', self.repository.coolingRate.value ) ++ self.distanceFeedRate.addTagBracketedLine('heatingRate', self.repository.heatingRate.value ) ++ self.distanceFeedRate.addTagBracketedLine('baseTemperature', self.repository.baseTemperature.value ) ++ self.distanceFeedRate.addTagBracketedLine('interfaceTemperature', self.repository.interfaceTemperature.value ) ++ self.distanceFeedRate.addTagBracketedLine('objectFirstLayerInfillTemperature', self.repository.objectFirstLayerInfillTemperature.value ) ++ self.distanceFeedRate.addTagBracketedLine('objectFirstLayerPerimeterTemperature', self.repository.objectFirstLayerPerimeterTemperature.value ) ++ self.distanceFeedRate.addTagBracketedLine('objectNextLayersTemperature', self.repository.objectNextLayersTemperature.value ) ++ self.distanceFeedRate.addTagBracketedLine('supportLayersTemperature', self.repository.supportLayersTemperature.value ) ++ self.distanceFeedRate.addTagBracketedLine('supportedLayersTemperature', self.repository.supportedLayersTemperature.value ) ++ self.distanceFeedRate.addLine(line) ++ ++ ++def main(): ++ "Display the temperature dialog." ++ if len(sys.argv) > 1: ++ writeOutput(' '.join(sys.argv[1 :])) ++ else: ++ settings.startMainLoopFromConstructor(getNewRepository()) ++ ++if __name__ == "__main__": ++ main() +--- ori/46/skeinforge_application/skeinforge.py ++++ target/SF46/skeinforge_application/skeinforge.py +@@ -228,6 +228,8 @@ + from skeinforge_application.skeinforge_utilities import skeinforge_profile + import os + import sys ++import platform ++import subprocess + + + # document raft, stretch, then carve, comb, fill, inset, oozebane, splodge, temperature, speed once they are updated +@@ -563,7 +565,6 @@ + repository = getNewRepository() + repository.fileNameInput.value = fileName + repository.execute() +- settings.startMainLoopFromConstructor(repository) + + + class SkeinforgeRepository: +@@ -580,13 +581,35 @@ + settings.LabelDisplay().getFromName('', self) + importantFileNames = ['craft', 'profile'] + getRadioPluginsAddPluginGroupFrame(archive.getSkeinforgePluginsPath(), importantFileNames, getPluginFileNames(), self) +- self.executeTitle = 'Skeinforge' ++ self.executeTitle = 'Skeinforge a file...' ++ ++ def getPyPyExe(self): ++ pypyExe = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../pypy-1.7/pypy.exe")); ++ if os.path.exists(pypyExe): ++ return pypyExe ++ pypyExe = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../../pypy-1.7/bin/pypy")); ++ if os.path.exists(pypyExe): ++ return pypyExe ++ pypyExe = "/bin/pypy"; ++ if os.path.exists(pypyExe): ++ return pypyExe ++ pypyExe = "/usr/bin/pypy"; ++ if os.path.exists(pypyExe): ++ return pypyExe ++ pypyExe = "/usr/local/bin/pypy"; ++ if os.path.exists(pypyExe): ++ return pypyExe ++ return False + + def execute(self): + 'Skeinforge button has been clicked.' + fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) ++ pypyExe = self.getPyPyExe() + for fileName in fileNames: +- skeinforge_craft.writeOutput(fileName) ++ if pypyExe == False or platform.python_implementation() == "PyPy": ++ skeinforge_craft.writeOutput(fileName) ++ else: ++ subprocess.call([pypyExe, fileName]) + + def save(self): + 'Profile has been saved and profile menu should be updated.' +--- ori/46/skeinforge_application/skeinforge.py.orig ++++ target/SF46/skeinforge_application/skeinforge.py.orig +@@ -0,0 +1,627 @@ ++#!/usr/bin/python ++""" ++This page is in the table of contents. ++==Overview== ++===Introduction=== ++Skeinforge is a GPL tool chain to forge a gcode skein for a model. ++ ++The tool chain starts with carve, which carves the model into layers, then the layers are modified by other tools in turn like fill, comb, tower, raft, stretch, hop, wipe, fillet & export. Each tool automatically gets the gcode from the previous tool. So if you want a carved & filled gcode, call the fill tool and it will call carve, then it will fill and output the gcode. If you want to use all the tools, call export and it will call in turn all the other tools down the chain to produce the gcode file. ++ ++If you do not want a tool after preface to modify the output, deselect the Activate checkbox for that tool. When the Activate checkbox is off, the tool will just hand off the gcode to the next tool without modifying it. ++ ++The skeinforge module provides a single place to call up all the setting dialogs. When the 'Skeinforge' button is clicked, skeinforge calls export, since that is the end of the chain. ++ ++The plugin buttons which are commonly used are bolded and the ones which are rarely used have normal font weight. ++ ++There are also tools which handle settings for the chain, like polyfile. ++ ++The analyze tool calls plugins in the analyze_plugins folder, which will analyze the gcode in some way when it is generated if their Activate checkbox is selected. ++ ++The interpret tool accesses and displays the import plugins. ++ ++The default settings are similar to those on Nophead's machine. A setting which is often different is the 'Layer Thickness' in carve. ++ ++===Command Line Interface=== ++To bring up the skeinforge dialog without a file name, type: ++python skeinforge_application/skeinforge.py ++ ++Slicing a file from skeinforge_utilities/skeinforge_craft.py, for example: ++python skeinforge_application/skeinforge_utilities/skeinforge_craft.py test.stl ++ ++will slice the file and exit. This is the correct option for programs which use skeinforge to only generate a gcode file. ++ ++Slicing a file from skeinforge.py, for example: ++python skeinforge_application/skeinforge.py test.stl ++ ++will slice the file and bring up the skeinforge window and the analyze windows and then skeinforge will wait for user input. ++ ++Slicing a file from skeinforge_plugins/craft.py, for example: ++python skeinforge_application/skeinforge_plugins/craft.py test.stl ++ ++will slice the file and bring up the analyze windows only and then skeinforge will wait for user input. ++ ++===Contribute=== ++You can contribute by helping develop the manual at: ++http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge ++ ++There is also a forum thread about how to contribute to skeinforge development at: ++http://dev.forums.reprap.org/read.php?12,27562 ++ ++I will only reply to emails from contributors or to complete bug reports. ++ ++===Documentation=== ++There is a manual at: ++http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge ++ ++There is also documentation is in the documentation folder, in the doc strings for each module and it can be called from the '?' button or the menu or by clicking F1 in each setting dialog. ++ ++A list of other tutorials is at: ++http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge#Tutorials ++ ++Skeinforge tagged pages on thingiverse can be searched for at: ++http://www.thingiverse.com/search?cx=015525747728168968820%3Arqnsgx1xxcw&cof=FORID%3A9&ie=UTF-8&q=skeinforge&sa=Search&siteurl=www.thingiverse.com%2F#944 ++ ++===Fabrication=== ++To fabricate a model with gcode and the Arduino you can use the send.py in the fabricate folder. The documentation for it is in the folder as send.html and at: ++http://reprap.org/bin/view/Main/ArduinoSend ++ ++Another way is to use an EMC2 or similar computer controlled milling machine, as described in the "ECM2 based repstrap" forum thread at: ++http://forums.reprap.org/read.php?1,12143 ++ ++using the M-Apps package, which is at: ++http://forums.reprap.org/file.php?1,file=772 ++ ++Another way is to use Zach's ReplicatorG at: ++http://replicat.org/ ++ ++There is also an older Processing script at: ++http://reprap.svn.sourceforge.net/viewvc/reprap/trunk/users/hoeken/arduino/GCode_Host/ ++ ++Yet another way is to use the reprap host, written in Java, to load and print gcode: ++http://dev.www.reprap.org/bin/view/Main/DriverSoftware#Load_GCode ++ ++For jogging, the Metalab group wrote their own exerciser, also in Processing: ++http://reprap.svn.sourceforge.net/viewvc/reprap/trunk/users/metalab/processing/GCode_Exerciser/ ++ ++The Metalab group has descriptions of skeinforge in action and their adventures are described at: ++http://reprap.soup.io/ ++ ++There is a board about printing issues at: ++http://www.bitsfrombytes.com/fora/user/index.php?board=5.0 ++ ++You can buy the Rapman (an improved Darwin) from Bits from Bytes at: ++http://www.bitsfrombytes.com/ ++ ++You can buy the Makerbot from Makerbot Industries at: ++http://www.makerbot.com/ ++ ++===File Formats=== ++An explanation of the gcodes is at: ++http://reprap.org/bin/view/Main/Arduino_GCode_Interpreter ++ ++and at: ++http://reprap.org/bin/view/Main/MCodeReference ++ ++A gode example is at: ++http://forums.reprap.org/file.php?12,file=565 ++ ++The settings are saved as tab separated .csv files in the .skeinforge folder in your home directory. The settings can be set in the tool dialogs. The .csv files can also be edited with a text editor or a spreadsheet program set to separate tabs. ++ ++The Scalable Vector Graphics file produced by vectorwrite can be opened by an SVG viewer or an SVG capable browser like Mozilla: ++http://www.mozilla.com/firefox/ ++ ++A good triangle surface format is the GNU Triangulated Surface format, which is supported by Mesh Viewer and described at: ++http://gts.sourceforge.net/reference/gts-surfaces.html#GTS-SURFACE-WRITE ++ ++You can export GTS files from Art of Illusion with the Export GNU Triangulated Surface.bsh script in the Art of Illusion Scripts folder. ++ ++STL is an inferior triangle surface format, described at: ++http://en.wikipedia.org/wiki/STL_(file_format) ++ ++If you're using an STL file and you can't even carve it, try converting it to a GNU Triangulated Surface file in Art of Illusion. If it still doesn't carve, then follow the advice in the troubleshooting section. ++ ++===Getting Skeinforge=== ++The latest version is at: ++http://members.axion.net/~enrique/reprap_python_beanshell.zip ++ ++a sometimes out of date version is in the last reprap_python_beanshell.zip attachment in the last post of the Fabmetheus blog at: ++http://fabmetheus.blogspot.com/ ++ ++another sometimes out of date version is at: ++https://reprap.svn.sourceforge.net/svnroot/reprap/trunk/reprap/miscellaneous/python-beanshell-scripts/ ++ ++===Getting Started=== ++For skeinforge to run, install python 2.x on your machine, which is available from: ++http://www.python.org/download/ ++ ++To use the settings dialog you'll also need Tkinter, which probably came with the python installation. If it did not, look for it at: ++http://www.tcl.tk/software/tcltk/ ++ ++If you want python and Tkinter together on MacOS, you can try: ++http://www.astro.washington.edu/users/rowen/ROPackage/Overview.html ++ ++If you want python and Tkinter together on all platforms and don't mind filling out forms, you can try the ActivePython package from Active State at: ++http://www.activestate.com/Products/activepython/feature_list.mhtml ++ ++The computation intensive python modules will use psyco if it is available and run about twice as fast. Psyco is described at: ++http://psyco.sourceforge.net/index.html ++ ++The psyco download page is: ++http://psyco.sourceforge.net/download.html ++ ++Skeinforge imports Stereolithography (.stl) files or GNU Triangulated Surface (.gts) files. If importing an STL file directly doesn't work, an indirect way to import an STL file is by turning it into a GTS file is by using the Export GNU Triangulated Surface script at: ++http://members.axion.net/~enrique/Export%20GNU%20Triangulated%20Surface.bsh ++ ++The Export GNU Triangulated Surface script is also in the Art of Illusion folder, which is in the same folder as skeinforge.py. To bring the script into Art of Illusion, drop it into the folder ArtOfIllusion/Scripts/Tools/. Then import the STL file using the STL import plugin in the import submenu of the Art of Illusion file menu. Then from the Scripts submenu in the Tools menu, choose 'Export GNU Triangulated Surface' and select the imported STL shape. Click the 'Export Selected' checkbox and click OK. Once you've created the GTS file, you can turn it into gcode by typing in a shell in the same folder as skeinforge: ++> python skeinforge.py ++ ++When the skeinforge dialog pops up, click 'Skeinforge', choose the file which you exported in 'Export GNU Triangulated Surface' and the gcode file will be saved with the suffix '_export.gcode'. ++ ++Or you can turn files into gcode by adding the file name, for example: ++> python skeinforge.py Screw Holder Bottom.stl ++ ++===License=== ++GNU Affero General Public License ++http://www.gnu.org/licenses/agpl.html ++ ++===Motto=== ++I may be slow, but I get there in the end. ++ ++===Troubleshooting=== ++If there's a bug, try downloading the very latest version because skeinforge is often updated without an announcement. The very latest version is at: ++http://members.axion.net/~enrique/reprap_python_beanshell.zip ++ ++If there is still a bug, then first prepare the following files: ++ ++1. stl file ++2. pictures explaining the problem ++3. your settings (pack the whole .skeinforge directory with all your settings) ++4. alterations folder, if you have any active alterations files ++ ++Then zip all the files. ++ ++Second, write a description of the error, send the description and the archive to the developer, enrique ( perez_enrique AT yahoo.com.removethispart ). After a bug fix is released, test the new version and report the results to enrique, whether the fix was successful or not. ++ ++If the dialog window is too big for the screen, on most Linux window managers you can move a window by holding down the Alt key and then drag the window with the left mouse button to get to the off screen widgets. ++ ++If you can't use the graphical interface, you can change the settings for skeinforge by using a text editor or spreadsheet to change the settings in the profiles folder in the .skeinforge folder in your home directory. ++ ++Comments and suggestions are welcome, however, I won't reply unless you are a contributor. Likewise, I will only answer your questions if you contribute to skeinforge in some way. Some ways of contributing to skeinforge are in the contributions thread at: ++http://dev.forums.reprap.org/read.php?12,27562 ++ ++You could also contribute articles to demozendium on any topic: ++http://fabmetheus.crsndoo.com/wiki/index.php/Main_Page ++ ++If you contribute in a significant way to another open source project, I will consider that also. ++ ++When I answered everyone's questions, eventually I received more questions than I had time to answer, so now I only answer questions from contributors. ++ ++I reserve the right to make any correspondence public. Do not send me any correspondence marked confidential. If you do I will delete it. ++ ++ ++==Examples== ++The following examples forge the STL file Screw Holder.stl. The examples are run in a terminal in the folder which contains Screw Holder.gts and skeinforge.py. ++ ++> python skeinforge.py ++This brings up the dialog, after clicking 'Skeinforge', the following is printed: ++The exported file is saved as Screw Holder_export.gcode ++ ++> python skeinforge.py Screw Holder.stl ++The exported file is saved as Screw Holder_export.gcode ++ ++To run only fill for example, type in the craft_plugins folder which fill is in: ++> python fill.py ++ ++""" ++ ++from __future__ import absolute_import ++import __init__ ++ ++from fabmetheus_utilities.fabmetheus_tools import fabmetheus_interpret ++from fabmetheus_utilities import archive ++from fabmetheus_utilities import euclidean ++from fabmetheus_utilities import gcodec ++from fabmetheus_utilities import settings ++from optparse import OptionParser ++from skeinforge_application.skeinforge_utilities import skeinforge_craft ++from skeinforge_application.skeinforge_utilities import skeinforge_polyfile ++from skeinforge_application.skeinforge_utilities import skeinforge_profile ++import os ++import sys ++ ++ ++# document raft, stretch, then carve, comb, fill, inset, oozebane, splodge, temperature, speed once they are updated ++# wiki document help, description, polyfile ++# subplugins like export static, maybe later mill cut and coil plugins, maybe later still export plugins & change file extension to output file extension http://fabmetheus.crsndoo.com/wiki/index.php/Skeinforge ++# ++# backup demozendium links ++# contract wipe titles ++# replace layer thickness with layer height, replace baseLayerThickness.. with baseLayerHeightMultiplier, consolidate Object First Layer Flow ++# announce alteration removeRedundantMcode ++# announce chamber gradual bed temperature Bed Temperature Begin Change Height, End Change Height, End Temperature ++# announce comb running jump ++# question, should 'Infill Odd Layer Extra Rotation' be dropped ++# ++# unimportant ++# minor outline problem when an end path goes through a path, like in the letter A ++# view profile 1 mm thickness ++# analyze doesn't save skeinlayer settings, remember xy in skeiniso ++# ++# ++# ++# retraction step leave ++# melt _extrusion ++# think about http://code.google.com/p/skeinarchiver/ and/or undo ++# add volume fraction to fill ++# getStrokeRadius default to edgeWidth ++# look at loop end removed bug in upper loop of layer 8 of Screw_Holder_alteration ++# fix tower perimeter line start problem ++# use fileSettingName to change perimeter width to extrusion width, globalSubstitutionDictionary ++# check globalExecutionOrder, ensure that bottom order is really high ++# set temperature in temperature ++# maybe rename geometry_plugins xml ++# maybe add carve preview, opening it up in browser ++# dwindle or dawdle or taper ++# voronoi average location intersection looped inset intercircles ++# skin layers without something over the infill ++# check for last existing then remove unneeded fill code (getLastExistingFillLoops) from euclidean, add fill in penultimate loops, if there is no fill it should not use perimeter - skin should work ++# delete commented addInfillPerimeter ++# unpause slow flow rate instead of speeding feed rate ++# maybe in svgReader if loop intersection with previous union else add ++# add links download manual svg_writer, add left right arrow keys to layer ++# delete location from wipe, in other words Arrival X instead of Location Arrival X, also convert Location Arrival to Arrival Location ++# command ++# manipulation derivations ++# cutting ahmet ++# ++# When opening a file for craft I wondered if there is an option to set the file type to .stl as it currently defaults to .xml ++# then add Retraction Scaling Exponent ++# check inset loop for intersection with loopLayer.loops ++# maybe make vectorwrite prominent, not skeiniso, probably not because it doesn't work on Mac ++# close, getPillarByLoopLists, addConcave, polymorph original graph section, loop, add step object, add continuous object ++# chamber: heated bed off at a layer http://blog.makerbot.com/2011/03/17/if-you-cant-stand-the-heat/ ++# profile copy / rename / delete, maybe move craft type to profile ++# think about rectangular getVector3RemoveByPre.. ++# del previous, add begin & end if far get actual path ++# bridge infill modifiers only in the bridge infill loop ++# linearbearingexample 15 x 1 x 2, linearbearingcage ++# polling ++# connectionfrom, to, connect, xaxis ++# move replace from export to alterations ++# lathe, transform normal in getRemaining, getConnection ++# add overview link to crnsdoo index and svg page ++# getConnection of some kind like getConnectionVertexes, getConnection ++# incorporate actual thickness from feed rate and flow rate in statistics for dimension ++# update stretch pictures By design, distance between parallel sides in hexagonal hole are 13mm, 7mm, 6.5mm, round hole diameter's are 8mm, 4mm and 3mm. http://fabmetheus.crsndoo.com/wiki/images/Stretch.png http://fabmetheus.crsndoo.com/wiki/images/thumb/NormalHole.png/180px-NormalHole.png http://fabmetheus.crsndoo.com/wiki/images/thumb/StretchDeformedHole.png/180px-StretchDeformedHole.png ++# xml_creation ++# 'fileName, text, repository' commandLineInterface ++# delete: text = text.replace(('\nName %sValue\n' % globalSpreadsheetSeparator), ('\n_Name %sValue\n' % globalSpreadsheetSeparator)) ++# comment search from home panel when there is an input field ++# ++# ++# multiply to table + boundary bedBound bedWidth bedHeight bedFile.csv ++# getNormal, getIsFlat? ++# info statistics, procedures, xml if any ++# test solid arguments ++# combine xmlelement with csvelement using example.csv & geometry.csv, csv _format, _column, _row, _text ++# pixel, voxel, surfaxel/boxel, lattice, mesh ++# probably not replace getOverlapRatio with getOverlap if getOverlapRatio is never small, always 0.0 ++# mesh. for cube, then cylinder, then sphere after lathe ++# dimension extrude diameter, density ++# superformula http://www.thingiverse.com/thing:12419 ++# maybe get rid of testLoops once they are no longer needed ++# thermistor lookup table ++# stretch maybe add back addAlong ++# import, write, copy examples ++# maybe remove default warnings from scale, rotate, translate, transform ++# easy helix ++# write tool; maybe write one deep ++# ++# ++# tube ++# rotor ++# coin ++# demozendium privacy policy, maybe thumbnail logo ++# pymethe ++# test translate ++# full lathe ++# pyramid ++# round extrusion ?, fillet ++# make html statistics, move statistics to folder ++# manipulate solid, maybe manipulate around elements ++# boolean loop corner outset ++# mechaslab advanced drainage, shingles ++# dovetail ++# maybe not getNewObject, getNew, addToBoolean ++# work out close and radius ++# maybe have add function as well as append for list and string ++# maybe move and give geometryOutput to cube, cylinder, sphere ++# ++# maybe move widen before bottom ++# maybe add 1 to max layer input to iso in layer_template.svg ++# maybe save all generated_files option ++# table to dictionary ++# remove cool set at end of layer ++# add fan on when hot in chamber ++# maybe measuring rod ++# getLayerThickness from xml ++# maybe center for xy plane ++# remove comments from clip, bend ++# winding into coiling, coil into wind & weave ++# later, precision ++# documentation ++# http://wiki.makerbot.com/configuring-skeinforge ++# ++# ++# remove index from CircleIntersection remove ahead or behind from CircleIntersection _speed ++# probably not speed up CircleIntersection by performing isWithinCircles before creation _speed ++# don't remove brackets in early craft tools _speed ++# check bounding box when subtracting or intersecting boolean geometry ++# get arounds in inset, the inside become extrude loops and the outside below loops _speed ++# ++# ++# add hook _extrusion ++# integral thin width _extrusion ++# layer color, for multilayer start http://reprap.org/pub/Main/MultipleMaterialsFiles/legend.xml _extrusion ++# maybe raft triple layer base, middle interface with hot loop or ties ++# somehow, add pattern to outside, http://blog.makerbot.com/2010/09/03/lampshades/ ++# implement acceleration & collinear removal in penultimate viewers _extrusion ++# ++# rename skeinforge_profile.addListsToCraftTypeRepository to skeinforge_profile.addToCraftTypeRepository after skirt ++# basic basedit tool ++# arch, ceiling ++# meta setting, rename setting _setting ++# add polish, has perimeter, has cut first layer (False) ++# probably not set addedLocation in distanceFeedRate after arc move ++# maybe horizontal bridging and/or check to see if the ends are standing on anything ++# thin self? check when removing intersecting paths in inset ++# save all analyze viewers of the same name except itself, update help menu self.wikiManualPrimary.setUpdateFunction ++# check alterations folder first, if there is something copy it to the home directory, if not check the home directory ++# add links to demozendium in help ++# maybe add hop only if long option ++# ++# ++# ++# help primary menu item refresh ++# add plugin help menu, add craft below menu ++# give option of saving when switching profiles ++# xml & svg more forgiving, svg make defaults for layerThickness ++# option of surrounding lines in display ++# maybe add connecting line in display line ++# maybe check inset loops to see if they are smaller, but this would be slow ++# maybe status bar ++# maybe measurement ruler mouse tool ++# search rss from blogs, add search links for common materials, combine created on or progress bar with searchable help ++# boundaries, center radius z bottom top, alterations file, circular or rectangular, polygon, put cool minimum radius orbits within boundaries, bound.. ++# move & rotate model ++# possible jitter bug http://cpwebste.blogspot.com/2010/04/hydras-first-print.html ++# trial, meta in a grid settings ++# maybe interpret svg_convex_mesh ++#laminate tool head ++#maybe use 5x5 radius search in circle node ++#maybe add layer updates in behold, skeinlayer and maybe others ++#lathe winding, extrusion and cutting; synonym for rotation or turning, loop angle ++# maybe split into source code and documentation sections ++# transform plugins, start with sarrus http://www.thingiverse.com/thing:1425 ++# maybe make setting backups ++# move skeinforge_utilities to fabmetheus_utilities ++# maybe lathe cutting ++# maybe lathe extrusion ++# maybe lathe milling ++# maybe lathe winding & weaving ++# ++# ++# ++# pick and place ++# search items, search links, choice entry field ++# svg triangle mesh, svg polygon mesh ++# simulate ++#transform ++# juricator ++# probably not run along sparse infill to avoid stops ++#custom inclined plane, inclined plane from model, screw, fillet travel as well maybe ++# probably not stretch single isLoop ++#maybe much afterwards make congajure multistep view ++#maybe stripe although model colors alone can handle it ++#stretch fiber around shape, maybe modify winding for asymmetric shapes ++#multiple heads around edge ++#maybe add rarely used tool option ++#angle shape for overhang extrusions ++#maybe m111? countdown ++#first time tool tip ++#individual tool tip to place in text ++# maybe try to simplify raft layer start ++# maybe make temp directory ++# maybe carve aoi xml testing and check xml gcode ++# maybe cross hatch support polishing??? ++# maybe print svg view from current layer or zero layer in single view ++# maybe check if tower is picking the closest island ++# maybe combine skein classes in fillet ++# maybe isometric svg option ++ ++#Manual ++#10,990 ++#11,1776,786 ++#12,3304,1528 ++#1,4960,1656 ++#2, 7077,2117 ++#3, 9598,2521 ++#4 12014,2305 ++#5 14319,2536 ++#6 16855,3226 ++#7 20081, 2189 ++#8 22270, 2625 ++#9 24895, 2967, 98 ++#10 27862, 3433, 110 ++#11 31295, 3327 ++#12 34622 ++#85 jan7, 86jan11, 87 jan13, 88 jan15, 91 jan21, 92 jan23, 95 jan30, 98 feb6 ++#make one piece electromagnet spool ++#stepper rotor with ceramic disk magnet in middle, electromagnet with long thin spool line? ++#stepper motor ++#make plastic coated thread in vat with pulley ++#tensile stuart platform ++#kayak ++#gear vacuum pump ++#gear turbine ++#heat engine ++#solar power ++#sailboat ++#yacht ++#house ++#condo with reflected gardens in between buildings ++#medical equipment ++#cell counter, etc.. ++#pipe clamp lathe ++# square tube driller & cutter ++ ++# archihedrongagglevoteindexium ++# outline images ++# look from top of intersection circle plane to look for next, add a node; tree out until all are stepped on then connect, when more than three intersections are close ++# when loading a file, we should have a preview of the part and orientation in space ++# second (and most important in my opinion) would be the ability to rotate the part on X/Y/Z axis to chose it's orientation ++# third, a routine to detect the largest face and orient the part accordingly. Mat http://reprap.kumy.net/ ++# concept, three perpendicular slices to get display spheres ++# extend lines around short segment after cross hatched boolean ++# concept, donation, postponement, rotate ad network, cached search options ++# concept, local ad server, every time the program runs it changes the iamge which all the documentation points to from a pool of ads ++# concept, join cross slices, go from vertex to two orthogonal edges, then from edges to each other, if not to a common point, then simplify polygons by removing points which do not change the area much ++# concept, each node is fourfold, use sorted intersectionindexes to find close, connect each double sided edge, don't overlap more than two triangles on an edge ++# concept, diamond cross section loops ++# concept, in file, store polygon mesh and centers ++# concept, display spheres or polygons would have original triangle for work plane ++# .. then again no point with slices ++# concept, filled slices, about 2 mm thick ++# concept, rgb color triangle switch to get inside color, color golden ratio on 5:11 slope with a modulo 3 face ++# concept, interlaced bricks at corners ( length proportional to corner angle ) ++# concept, new links to archi, import links to archi and adds skeinforge tool menu item, back on skeinforge named execute tool is added ++# concept, trnsnt ++# concept, indexium expand condense remove, single text, pymetheus ++# concept, inscribed key silencer ++# concept, spreadsheet to python and/or javascript ++# concept, range voting for posters, informative, complainer, funny, insightful, rude, spammer, literacy, troll? ++# concept, intermittent cloud with multiple hash functions ++ ++ ++__author__ = 'Enrique Perez (perez_enrique@yahoo.com)' ++__credits__ = """ ++Adrian Bowyer ++Brendan Erwin ++Greenarrow ++Ian England ++John Gilmore ++Jonwise ++Kyle Corbitt ++Michael Duffin ++Marius Kintel ++Nophead ++PJR ++Reece.Arnott ++Wade ++Xsainnz ++Zach Hoeken ++ ++Organizations: ++Art of Illusion """ ++__date__ = '$Date: 2008/02/05 $' ++__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' ++ ++ ++def addToProfileMenu(profileSelection, profileType, repository): ++ 'Add a profile menu.' ++ pluginFileNames = skeinforge_profile.getPluginFileNames() ++ craftTypeName = skeinforge_profile.getCraftTypeName() ++ pluginModule = skeinforge_profile.getCraftTypePluginModule() ++ profilePluginSettings = settings.getReadRepository(pluginModule.getNewRepository()) ++ for pluginFileName in pluginFileNames: ++ skeinforge_profile.ProfileTypeMenuRadio().getFromMenuButtonDisplay(profileType, pluginFileName, repository, craftTypeName == pluginFileName) ++ for profileName in profilePluginSettings.profileList.value: ++ skeinforge_profile.ProfileSelectionMenuRadio().getFromMenuButtonDisplay(profileSelection, profileName, repository, profileName == profilePluginSettings.profileListbox.value) ++ ++def getNewRepository(): ++ 'Get new repository.' ++ return SkeinforgeRepository() ++ ++def getPluginFileNames(): ++ 'Get skeinforge plugin fileNames.' ++ return archive.getPluginFileNamesFromDirectoryPath(archive.getSkeinforgePluginsPath()) ++ ++def getRadioPluginsAddPluginGroupFrame(directoryPath, importantFileNames, names, repository): ++ 'Get the radio plugins and add the plugin frame.' ++ repository.pluginGroupFrame = settings.PluginGroupFrame() ++ radioPlugins = [] ++ for name in names: ++ radioPlugin = settings.RadioPlugin().getFromRadio(name in importantFileNames, repository.pluginGroupFrame.latentStringVar, name, repository, name == importantFileNames[0]) ++ radioPlugin.updateFunction = repository.pluginGroupFrame.update ++ radioPlugins.append( radioPlugin ) ++ defaultRadioButton = settings.getSelectedRadioPlugin(importantFileNames + [radioPlugins[0].name], radioPlugins) ++ repository.pluginGroupFrame.getFromPath(defaultRadioButton, directoryPath, repository) ++ return radioPlugins ++ ++def writeOutput(fileName): ++ 'Craft a file, display dialog.' ++ repository = getNewRepository() ++ repository.fileNameInput.value = fileName ++ repository.execute() ++ settings.startMainLoopFromConstructor(repository) ++ ++ ++class SkeinforgeRepository: ++ 'A class to handle the skeinforge settings.' ++ def __init__(self): ++ 'Set the default settings, execute title & settings fileName.' ++ skeinforge_profile.addListsToCraftTypeRepository('skeinforge_application.skeinforge.html', self) ++ self.fileNameInput = settings.FileNameInput().getFromFileName( fabmetheus_interpret.getGNUTranslatorGcodeFileTypeTuples(), 'Open File for Skeinforge', self, '') ++ self.profileType = settings.MenuButtonDisplay().getFromName('Profile Type: ', self ) ++ self.profileType.columnspan = 6 ++ self.profileSelection = settings.MenuButtonDisplay().getFromName('Profile Selection: ', self) ++ self.profileSelection.columnspan = 6 ++ addToProfileMenu( self.profileSelection, self.profileType, self ) ++ settings.LabelDisplay().getFromName('', self) ++ importantFileNames = ['craft', 'profile'] ++ getRadioPluginsAddPluginGroupFrame(archive.getSkeinforgePluginsPath(), importantFileNames, getPluginFileNames(), self) ++ self.executeTitle = 'Skeinforge' ++ ++ def execute(self): ++ 'Skeinforge button has been clicked.' ++ fileNames = skeinforge_polyfile.getFileOrDirectoryTypesUnmodifiedGcode(self.fileNameInput.value, fabmetheus_interpret.getImportPluginFileNames(), self.fileNameInput.wasCancelled) ++ for fileName in fileNames: ++ skeinforge_craft.writeOutput(fileName) ++ ++ def save(self): ++ 'Profile has been saved and profile menu should be updated.' ++ self.profileType.removeMenus() ++ self.profileSelection.removeMenus() ++ addToProfileMenu(self.profileSelection, self.profileType, self) ++ self.profileType.addRadiosToDialog(self.repositoryDialog) ++ self.profileSelection.addRadiosToDialog(self.repositoryDialog) ++ ++ ++def main(): ++ 'Display the skeinforge dialog.' ++ parser = OptionParser() ++ parser.add_option( ++ '-p', '--prefdir', help='set path to preference directory', action='store', type='string', dest='preferencesDirectory') ++ parser.add_option( ++ '-s', '--start', help='set start file to use', action='store', type='string', dest='startFile') ++ parser.add_option( ++ '-e', '--end', help='set end file to use', action='store', type='string', dest='endFile') ++ parser.add_option( ++ '-o', '--option', help='set an individual option in the format "module:preference=value"', ++ action='append', type='string', dest='preferences') ++ (options, args) = parser.parse_args() ++ if options.preferencesDirectory: ++ archive.globalTemporarySettingsPath = options.preferencesDirectory ++ if options.preferences: ++ for prefSpec in options.preferences: ++ (moduleName, prefSpec) = prefSpec.split(':', 1) ++ (prefName, valueName) = prefSpec.split('=', 1) ++ settings.addPreferenceOverride(moduleName, prefName, valueName) ++ sys.argv = [sys.argv[0]] + args ++ if len( args ) > 0: ++ writeOutput( ' '.join(args) ) ++ else: ++ settings.startMainLoopFromConstructor(getNewRepository()) ++ ++if __name__ == '__main__': ++ main()