Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/Ultimaker/Cura.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfieldOfView <aldo@fieldofview.com>2020-05-14 09:45:38 +0300
committerfieldOfView <aldo@fieldofview.com>2020-05-14 09:45:38 +0300
commit172e6a0759b06ff2a628ae5d30f208c939bfbaa5 (patch)
tree1cbbd0e3df21da2079a7a54d1470c7dcccdbd26f /plugins/PostProcessingPlugin
parent7ea3891da0e0fb20c915bae98c39e485d483e22b (diff)
parent1111041a5bedaf126bf60a62446eeb529cf3dbfb (diff)
Merge branch 'master' into feature_unify_pause_at_height
# Conflicts: # plugins/PostProcessingPlugin/scripts/BQ_PauseAtHeight.py
Diffstat (limited to 'plugins/PostProcessingPlugin')
-rw-r--r--plugins/PostProcessingPlugin/PostProcessingPlugin.py19
-rw-r--r--plugins/PostProcessingPlugin/plugin.json2
-rw-r--r--plugins/PostProcessingPlugin/scripts/ChangeAtZ.py1528
-rw-r--r--plugins/PostProcessingPlugin/scripts/ColorMix.py2
-rw-r--r--plugins/PostProcessingPlugin/scripts/PauseAtHeight.py44
-rw-r--r--plugins/PostProcessingPlugin/scripts/RetractContinue.py63
-rw-r--r--plugins/PostProcessingPlugin/scripts/Stretch.py7
7 files changed, 1309 insertions, 356 deletions
diff --git a/plugins/PostProcessingPlugin/PostProcessingPlugin.py b/plugins/PostProcessingPlugin/PostProcessingPlugin.py
index f4f0e23378..9bf8062ffd 100644
--- a/plugins/PostProcessingPlugin/PostProcessingPlugin.py
+++ b/plugins/PostProcessingPlugin/PostProcessingPlugin.py
@@ -16,7 +16,7 @@ from UM.Extension import Extension
from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry
from UM.Resources import Resources
-from UM.Trust import Trust
+from UM.Trust import Trust, TrustBasics
from UM.i18n import i18nCatalog
from cura import ApplicationMetadata
from cura.CuraApplication import CuraApplication
@@ -156,6 +156,23 @@ class PostProcessingPlugin(QObject, Extension):
# This should probably only be done on init.
# \param path Path to check for scripts.
def loadScripts(self, path: str) -> None:
+
+ if ApplicationMetadata.IsEnterpriseVersion:
+ # Delete all __pycache__ not in installation folder, as it may present a security risk.
+ # It prevents this very strange scenario (should already be prevented on enterprise because signed-fault):
+ # - Copy an existing script from the postprocessing-script folder to the appdata scripts folder.
+ # - Also copy the entire __pycache__ folder from the first to the last location.
+ # - Leave the __pycache__ as is, but write malicious code just before the class begins.
+ # - It'll execute, despite that the script has not been signed.
+ # It's not known if these reproduction steps are minimal, but it does at least happen in this case.
+ install_prefix = os.path.abspath(CuraApplication.getInstance().getInstallPrefix())
+ try:
+ is_in_installation_path = os.path.commonpath([install_prefix, path]).startswith(install_prefix)
+ except ValueError:
+ is_in_installation_path = False
+ if not is_in_installation_path:
+ TrustBasics.removeCached(path)
+
## Load all scripts in the scripts folders
scripts = pkgutil.iter_modules(path = [path])
for loader, script_name, ispkg in scripts:
diff --git a/plugins/PostProcessingPlugin/plugin.json b/plugins/PostProcessingPlugin/plugin.json
index 814499ca0f..21a7cedb75 100644
--- a/plugins/PostProcessingPlugin/plugin.json
+++ b/plugins/PostProcessingPlugin/plugin.json
@@ -2,7 +2,7 @@
"name": "Post Processing",
"author": "Ultimaker",
"version": "2.2.1",
- "api": "7.1",
+ "api": "7.2.0",
"description": "Extension that allows for user created scripts for post processing",
"catalog": "cura"
} \ No newline at end of file
diff --git a/plugins/PostProcessingPlugin/scripts/ChangeAtZ.py b/plugins/PostProcessingPlugin/scripts/ChangeAtZ.py
index cdbb4a79ef..f4041b8650 100644
--- a/plugins/PostProcessingPlugin/scripts/ChangeAtZ.py
+++ b/plugins/PostProcessingPlugin/scripts/ChangeAtZ.py
@@ -4,71 +4,86 @@
# It runs with the PostProcessingPlugin which is released under the terms of the AGPLv3 or higher.
# This script is licensed under the Creative Commons - Attribution - Share Alike (CC BY-SA) terms
-#Authors of the ChangeAtZ plugin / script:
+# Authors of the ChangeAtZ plugin / script:
# Written by Steven Morlock, smorloc@gmail.com
# Modified by Ricardo Gomez, ricardoga@otulook.com, to add Bed Temperature and make it work with Cura_13.06.04+
# Modified by Stefan Heule, Dim3nsioneer@gmx.ch since V3.0 (see changelog below)
# Modified by Jaime van Kessel (Ultimaker), j.vankessel@ultimaker.com to make it work for 15.10 / 2.x
# Modified by Ruben Dulek (Ultimaker), r.dulek@ultimaker.com, to debug.
+# Modified by Wes Hanney, https://github.com/novamxd, Retract Length + Speed, Clean up
-##history / changelog:
-##V3.0.1: TweakAtZ-state default 1 (i.e. the plugin works without any TweakAtZ comment)
-##V3.1: Recognizes UltiGCode and deactivates value reset, fan speed added, alternatively layer no. to tweak at,
-## extruder three temperature disabled by "#Ex3"
-##V3.1.1: Bugfix reset flow rate
-##V3.1.2: Bugfix disable TweakAtZ on Cool Head Lift
-##V3.2: Flow rate for specific extruder added (only for 2 extruders), bugfix parser,
-## added speed reset at the end of the print
-##V4.0: Progress bar, tweaking over multiple layers, M605&M606 implemented, reset after one layer option,
-## extruder three code removed, tweaking print speed, save call of Publisher class,
-## uses previous value from other plugins also on UltiGCode
-##V4.0.1: Bugfix for doubled G1 commands
-##V4.0.2: uses Cura progress bar instead of its own
-##V4.0.3: Bugfix for cool head lift (contributed by luisonoff)
-##V4.9.91: First version for Cura 15.06.x and PostProcessingPlugin
-##V4.9.92: Modifications for Cura 15.10
-##V4.9.93: Minor bugfixes (input settings) / documentation
-##V4.9.94: Bugfix Combobox-selection; remove logger
-##V5.0: Bugfix for fall back after one layer and doubled G0 commands when using print speed tweak, Initial version for Cura 2.x
-##V5.0.1: Bugfix for calling unknown property 'bedTemp' of previous settings storage and unkown variable 'speed'
-##V5.1: API Changes included for use with Cura 2.2
-
-## Uses -
-## M220 S<factor in percent> - set speed factor override percentage
-## M221 S<factor in percent> - set flow factor override percentage
-## M221 S<factor in percent> T<0-#toolheads> - set flow factor override percentage for single extruder
-## M104 S<temp> T<0-#toolheads> - set extruder <T> to target temperature <S>
-## M140 S<temp> - set bed target temperature
-## M106 S<PWM> - set fan speed to target speed <S>
-## M605/606 to save and recall material settings on the UM2
+# history / changelog:
+# V3.0.1: TweakAtZ-state default 1 (i.e. the plugin works without any TweakAtZ comment)
+# V3.1: Recognizes UltiGCode and deactivates value reset, fan speed added, alternatively layer no. to tweak at,
+# extruder three temperature disabled by "#Ex3"
+# V3.1.1: Bugfix reset flow rate
+# V3.1.2: Bugfix disable TweakAtZ on Cool Head Lift
+# V3.2: Flow rate for specific extruder added (only for 2 extruders), bugfix parser,
+# added speed reset at the end of the print
+# V4.0: Progress bar, tweaking over multiple layers, M605&M606 implemented, reset after one layer option,
+# extruder three code removed, tweaking print speed, save call of Publisher class,
+# uses previous value from other plugins also on UltiGCode
+# V4.0.1: Bugfix for doubled G1 commands
+# V4.0.2: Uses Cura progress bar instead of its own
+# V4.0.3: Bugfix for cool head lift (contributed by luisonoff)
+# V4.9.91: First version for Cura 15.06.x and PostProcessingPlugin
+# V4.9.92: Modifications for Cura 15.10
+# V4.9.93: Minor bugfixes (input settings) / documentation
+# V4.9.94: Bugfix Combobox-selection; remove logger
+# V5.0: Bugfix for fall back after one layer and doubled G0 commands when using print speed tweak, Initial version for Cura 2.x
+# V5.0.1: Bugfix for calling unknown property 'bedTemp' of previous settings storage and unkown variable 'speed'
+# V5.1: API Changes included for use with Cura 2.2
+# V5.2.0: Wes Hanney. Added support for changing Retract Length and Speed. Removed layer spread option. Fixed issue of cumulative ChangeZ
+# mods so they can now properly be stacked on top of each other. Applied code refactoring to clean up various coding styles. Added comments.
+# Broke up functions for clarity. Split up class so it can be debugged outside of Cura.
+# V5.2.1: Wes Hanney. Added support for firmware based retractions. Fixed issue of properly restoring previous values in single layer option.
+# Added support for outputting changes to LCD (untested). Added type hints to most functions and variables. Added more comments. Created GCodeCommand
+# class for better detection of G1 vs G10 or G11 commands, and accessing arguments. Moved most GCode methods to GCodeCommand class. Improved wording
+# of Single Layer vs Keep Layer to better reflect what was happening.
+# Uses -
+# M220 S<factor in percent> - set speed factor override percentage
+# M221 S<factor in percent> - set flow factor override percentage
+# M221 S<factor in percent> T<0-#toolheads> - set flow factor override percentage for single extruder
+# M104 S<temp> T<0-#toolheads> - set extruder <T> to target temperature <S>
+# M140 S<temp> - set bed target temperature
+# M106 S<PWM> - set fan speed to target speed <S>
+# M207 S<mm> F<mm/m> - set the retract length <S> or feed rate <F>
+# M117 - output the current changes
+
+from typing import List, Optional, Dict
from ..Script import Script
-#from UM.Logger import Logger
import re
+
+# this was broken up into a separate class so the main ChangeZ script could be debugged outside of Cura
class ChangeAtZ(Script):
- version = "5.1.1"
- def __init__(self):
- super().__init__()
+ version = "5.2.1"
def getSettingDataString(self):
return """{
- "name":"ChangeAtZ """ + self.version + """ (Experimental)",
- "key":"ChangeAtZ",
+ "name": "ChangeAtZ """ + self.version + """(Experimental)",
+ "key": "ChangeAtZ",
"metadata": {},
"version": 2,
- "settings":
- {
- "a_trigger":
- {
+ "settings": {
+ "caz_enabled": {
+ "label": "Enabled",
+ "description": "Allows adding multiple ChangeZ mods and disabling them as needed.",
+ "type": "bool",
+ "default_value": true
+ },
+ "a_trigger": {
"label": "Trigger",
"description": "Trigger at height or at layer no.",
"type": "enum",
- "options": {"height":"Height","layer_no":"Layer No."},
+ "options": {
+ "height": "Height",
+ "layer_no": "Layer No."
+ },
"default_value": "height"
},
- "b_targetZ":
- {
+ "b_targetZ": {
"label": "Change Height",
"description": "Z height to change at",
"unit": "mm",
@@ -79,8 +94,7 @@ class ChangeAtZ(Script):
"maximum_value_warning": "230",
"enabled": "a_trigger == 'height'"
},
- "b_targetL":
- {
+ "b_targetL": {
"label": "Change Layer",
"description": "Layer no. to change at",
"unit": "",
@@ -90,34 +104,29 @@ class ChangeAtZ(Script):
"minimum_value_warning": "-1",
"enabled": "a_trigger == 'layer_no'"
},
- "c_behavior":
- {
- "label": "Behavior",
- "description": "Select behavior: Change value and keep it for the rest, Change value for single layer only",
+ "c_behavior": {
+ "label": "Apply To",
+ "description": "Target Layer + Subsequent Layers is good for testing changes between ranges of layers, ex: Layer 0 to 10 or 0mm to 5mm. Single layer is good for testing changes at a single layer, ex: at Layer 10 or 5mm only.",
"type": "enum",
- "options": {"keep_value":"Keep value","single_layer":"Single Layer"},
+ "options": {
+ "keep_value": "Target Layer + Subsequent Layers",
+ "single_layer": "Target Layer Only"
+ },
"default_value": "keep_value"
},
- "d_twLayers":
- {
- "label": "Layer Spread",
- "description": "The change will be gradual over this many layers. Enter 1 to make the change immediate.",
- "unit": "",
- "type": "int",
- "default_value": 1,
- "minimum_value": "1",
- "maximum_value_warning": "50",
- "enabled": "c_behavior == 'keep_value'"
- },
- "e1_Change_speed":
- {
+ "caz_output_to_display": {
+ "label": "Output to Display",
+ "description": "Displays the current changes to the LCD",
+ "type": "bool",
+ "default_value": false
+ },
+ "e1_Change_speed": {
"label": "Change Speed",
"description": "Select if total speed (print and travel) has to be changed",
"type": "bool",
"default_value": false
},
- "e2_speed":
- {
+ "e2_speed": {
"label": "Speed",
"description": "New total speed (print and travel)",
"unit": "%",
@@ -128,15 +137,13 @@ class ChangeAtZ(Script):
"maximum_value_warning": "200",
"enabled": "e1_Change_speed"
},
- "f1_Change_printspeed":
- {
+ "f1_Change_printspeed": {
"label": "Change Print Speed",
"description": "Select if print speed has to be changed",
"type": "bool",
"default_value": false
},
- "f2_printspeed":
- {
+ "f2_printspeed": {
"label": "Print Speed",
"description": "New print speed",
"unit": "%",
@@ -147,15 +154,13 @@ class ChangeAtZ(Script):
"maximum_value_warning": "200",
"enabled": "f1_Change_printspeed"
},
- "g1_Change_flowrate":
- {
+ "g1_Change_flowrate": {
"label": "Change Flow Rate",
"description": "Select if flow rate has to be changed",
"type": "bool",
"default_value": false
},
- "g2_flowrate":
- {
+ "g2_flowrate": {
"label": "Flow Rate",
"description": "New Flow rate",
"unit": "%",
@@ -166,15 +171,13 @@ class ChangeAtZ(Script):
"maximum_value_warning": "200",
"enabled": "g1_Change_flowrate"
},
- "g3_Change_flowrateOne":
- {
+ "g3_Change_flowrateOne": {
"label": "Change Flow Rate 1",
"description": "Select if first extruder flow rate has to be changed",
"type": "bool",
"default_value": false
},
- "g4_flowrateOne":
- {
+ "g4_flowrateOne": {
"label": "Flow Rate One",
"description": "New Flow rate Extruder 1",
"unit": "%",
@@ -185,15 +188,13 @@ class ChangeAtZ(Script):
"maximum_value_warning": "200",
"enabled": "g3_Change_flowrateOne"
},
- "g5_Change_flowrateTwo":
- {
+ "g5_Change_flowrateTwo": {
"label": "Change Flow Rate 2",
"description": "Select if second extruder flow rate has to be changed",
"type": "bool",
"default_value": false
},
- "g6_flowrateTwo":
- {
+ "g6_flowrateTwo": {
"label": "Flow Rate two",
"description": "New Flow rate Extruder 2",
"unit": "%",
@@ -204,15 +205,13 @@ class ChangeAtZ(Script):
"maximum_value_warning": "200",
"enabled": "g5_Change_flowrateTwo"
},
- "h1_Change_bedTemp":
- {
+ "h1_Change_bedTemp": {
"label": "Change Bed Temp",
"description": "Select if Bed Temperature has to be changed",
"type": "bool",
"default_value": false
},
- "h2_bedTemp":
- {
+ "h2_bedTemp": {
"label": "Bed Temp",
"description": "New Bed Temperature",
"unit": "C",
@@ -223,15 +222,13 @@ class ChangeAtZ(Script):
"maximum_value_warning": "120",
"enabled": "h1_Change_bedTemp"
},
- "i1_Change_extruderOne":
- {
+ "i1_Change_extruderOne": {
"label": "Change Extruder 1 Temp",
"description": "Select if First Extruder Temperature has to be changed",
"type": "bool",
"default_value": false
},
- "i2_extruderOne":
- {
+ "i2_extruderOne": {
"label": "Extruder 1 Temp",
"description": "New First Extruder Temperature",
"unit": "C",
@@ -242,15 +239,13 @@ class ChangeAtZ(Script):
"maximum_value_warning": "250",
"enabled": "i1_Change_extruderOne"
},
- "i3_Change_extruderTwo":
- {
+ "i3_Change_extruderTwo": {
"label": "Change Extruder 2 Temp",
"description": "Select if Second Extruder Temperature has to be changed",
"type": "bool",
"default_value": false
},
- "i4_extruderTwo":
- {
+ "i4_extruderTwo": {
"label": "Extruder 2 Temp",
"description": "New Second Extruder Temperature",
"unit": "C",
@@ -261,239 +256,1176 @@ class ChangeAtZ(Script):
"maximum_value_warning": "250",
"enabled": "i3_Change_extruderTwo"
},
- "j1_Change_fanSpeed":
- {
+ "j1_Change_fanSpeed": {
"label": "Change Fan Speed",
"description": "Select if Fan Speed has to be changed",
"type": "bool",
"default_value": false
},
- "j2_fanSpeed":
- {
+ "j2_fanSpeed": {
"label": "Fan Speed",
- "description": "New Fan Speed (0-255)",
- "unit": "PWM",
+ "description": "New Fan Speed (0-100)",
+ "unit": "%",
"type": "int",
- "default_value": 255,
+ "default_value": 100,
"minimum_value": "0",
- "minimum_value_warning": "15",
- "maximum_value_warning": "255",
+ "minimum_value_warning": "0",
+ "maximum_value_warning": "100",
"enabled": "j1_Change_fanSpeed"
- }
+ },
+ "caz_change_retract": {
+ "label": "Change Retraction",
+ "description": "Indicates you would like to modify retraction properties.",
+ "type": "bool",
+ "default_value": false
+ },
+ "caz_retractstyle": {
+ "label": "Retract Style",
+ "description": "Specify if you're using firmware retraction or linear move based retractions. Check your printer settings to see which you're using.",
+ "type": "enum",
+ "options": {
+ "linear": "Linear Move",
+ "firmware": "Firmware"
+ },
+ "default_value": "linear",
+ "enabled": "caz_change_retract"
+ },
+ "caz_change_retractfeedrate": {
+ "label": "Change Retract Feed Rate",
+ "description": "Changes the retraction feed rate during print",
+ "type": "bool",
+ "default_value": false,
+ "enabled": "caz_change_retract"
+ },
+ "caz_retractfeedrate": {
+ "label": "Retract Feed Rate",
+ "description": "New Retract Feed Rate (mm/s)",
+ "unit": "mm/s",
+ "type": "float",
+ "default_value": 40,
+ "minimum_value": "0",
+ "minimum_value_warning": "0",
+ "maximum_value_warning": "100",
+ "enabled": "caz_change_retractfeedrate"
+ },
+ "caz_change_retractlength": {
+ "label": "Change Retract Length",
+ "description": "Changes the retraction length during print",
+ "type": "bool",
+ "default_value": false,
+ "enabled": "caz_change_retract"
+ },
+ "caz_retractlength": {
+ "label": "Retract Length",
+ "description": "New Retract Length (mm)",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 6,
+ "minimum_value": "0",
+ "minimum_value_warning": "0",
+ "maximum_value_warning": "20",
+ "enabled": "caz_change_retractlength"
+ }
}
}"""
- def getValue(self, line, key, default = None): #replace default getvalue due to comment-reading feature
- if not key in line or (";" in line and line.find(key) > line.find(";") and
- not ";ChangeAtZ" in key and not ";LAYER:" in key):
+ def __init__(self):
+ super().__init__()
+
+ def execute(self, data):
+
+ caz_instance = ChangeAtZProcessor()
+
+ caz_instance.TargetValues = {}
+
+ # copy over our settings to our change z class
+ self.setIntSettingIfEnabled(caz_instance, "e1_Change_speed", "speed", "e2_speed")
+ self.setIntSettingIfEnabled(caz_instance, "f1_Change_printspeed", "printspeed", "f2_printspeed")
+ self.setIntSettingIfEnabled(caz_instance, "g1_Change_flowrate", "flowrate", "g2_flowrate")
+ self.setIntSettingIfEnabled(caz_instance, "g3_Change_flowrateOne", "flowrateOne", "g4_flowrateOne")
+ self.setIntSettingIfEnabled(caz_instance, "g5_Change_flowrateTwo", "flowrateTwo", "g6_flowrateTwo")
+ self.setFloatSettingIfEnabled(caz_instance, "h1_Change_bedTemp", "bedTemp", "h2_bedTemp")
+ self.setFloatSettingIfEnabled(caz_instance, "i1_Change_extruderOne", "extruderOne", "i2_extruderOne")
+ self.setFloatSettingIfEnabled(caz_instance, "i3_Change_extruderTwo", "extruderTwo", "i4_extruderTwo")
+ self.setIntSettingIfEnabled(caz_instance, "j1_Change_fanSpeed", "fanSpeed", "j2_fanSpeed")
+ self.setFloatSettingIfEnabled(caz_instance, "caz_change_retractfeedrate", "retractfeedrate", "caz_retractfeedrate")
+ self.setFloatSettingIfEnabled(caz_instance, "caz_change_retractlength", "retractlength", "caz_retractlength")
+
+ # is this mod enabled?
+ caz_instance.IsEnabled = self.getSettingValueByKey("caz_enabled")
+
+ # are we emitting data to the LCD?
+ caz_instance.IsDisplayingChangesToLcd = self.getSettingValueByKey("caz_output_to_display")
+
+ # are we doing linear move retractions?
+ caz_instance.IsLinearRetraction = self.getSettingValueByKey("caz_retractstyle") == "linear"
+
+ # see if we're applying to a single layer or to all layers hence forth
+ caz_instance.IsApplyToSingleLayer = self.getSettingValueByKey("c_behavior") == "single_layer"
+
+ # used for easy reference of layer or height targeting
+ caz_instance.IsTargetByLayer = self.getSettingValueByKey("a_trigger") == "layer_no"
+
+ # change our target based on what we're targeting
+ caz_instance.TargetLayer = self.getIntSettingByKey("b_targetL", None)
+ caz_instance.TargetZ = self.getFloatSettingByKey("b_targetZ", None)
+
+ # run our script
+ return caz_instance.execute(data)
+
+ # Sets the given TargetValue in the ChangeAtZ instance if the trigger is specified
+ def setIntSettingIfEnabled(self, caz_instance, trigger, target, setting):
+
+ # stop here if our trigger isn't enabled
+ if not self.getSettingValueByKey(trigger):
+ return
+
+ # get our value from the settings
+ value = self.getIntSettingByKey(setting, None)
+
+ # skip if there's no value or we can't interpret it
+ if value is None:
+ return
+
+ # set our value in the target settings
+ caz_instance.TargetValues[target] = value
+
+ # Sets the given TargetValue in the ChangeAtZ instance if the trigger is specified
+ def setFloatSettingIfEnabled(self, caz_instance, trigger, target, setting):
+
+ # stop here if our trigger isn't enabled
+ if not self.getSettingValueByKey(trigger):
+ return
+
+ # get our value from the settings
+ value = self.getFloatSettingByKey(setting, None)
+
+ # skip if there's no value or we can't interpret it
+ if value is None:
+ return
+
+ # set our value in the target settings
+ caz_instance.TargetValues[target] = value
+
+ # Returns the given settings value as an integer or the default if it cannot parse it
+ def getIntSettingByKey(self, key, default):
+
+ # change our target based on what we're targeting
+ try:
+ return int(self.getSettingValueByKey(key))
+ except:
return default
- subPart = line[line.find(key) + len(key):] #allows for string lengths larger than 1
+
+ # Returns the given settings value as an integer or the default if it cannot parse it
+ def getFloatSettingByKey(self, key, default):
+
+ # change our target based on what we're targeting
+ try:
+ return float(self.getSettingValueByKey(key))
+ except:
+ return default
+
+
+# This is a utility class for getting details of gcodes from a given line
+class GCodeCommand:
+
+ # The GCode command itself (ex: G10)
+ Command = None,
+
+ # Contains any arguments passed to the command. The key is the argument name, the value is the value of the argument.
+ Arguments = {}
+
+ # Contains the components of the command broken into pieces
+ Components = []
+
+ # Constructor. Sets up defaults
+ def __init__(self):
+ self.reset()
+
+ # Gets a GCode Command from the given single line of GCode
+ @staticmethod
+ def getFromLine(line: str):
+
+ # obviously if we don't have a command, we can't return anything
+ if line is None or len(line) == 0:
+ return None
+
+ # we only support G or M commands
+ if line[0] != "G" and line[0] != "M":
+ return None
+
+ # remove any comments
+ line = re.sub(r";.*$", "", line)
+
+ # break into the individual components
+ command_pieces = line.strip().split(" ")
+
+ # our return command details
+ command = GCodeCommand()
+
+ # stop here if we don't even have something to interpret
+ if len(command_pieces) == 0:
+ return None
+
+ # stores all the components of the command within the class for later
+ command.Components = command_pieces
+
+ # set the actual command
+ command.Command = command_pieces[0]
+
+ # stop here if we don't have any parameters
+ if len(command_pieces) == 1:
+ return None
+
+ # return our indexed command
+ return command
+
+ # Handy function for reading a linear move command
+ @staticmethod
+ def getLinearMoveCommand(line: str):
+
+ # get our command from the line
+ linear_command = GCodeCommand.getFromLine(line)
+
+ # if it's not a linear move, we don't care
+ if linear_command is None or (linear_command.Command != "G0" and linear_command.Command != "G1"):
+ return None
+
+ # convert our values to floats (or defaults)
+ linear_command.Arguments["F"] = linear_command.getArgumentAsFloat("F", None)
+ linear_command.Arguments["X"] = linear_command.getArgumentAsFloat("X", None)
+ linear_command.Arguments["Y"] = linear_command.getArgumentAsFloat("Y", None)
+ linear_command.Arguments["Z"] = linear_command.getArgumentAsFloat("Z", None)
+ linear_command.Arguments["E"] = linear_command.getArgumentAsFloat("E", None)
+
+ # return our new command
+ return linear_command
+
+ # Gets the value of a parameter or returns the default if there is none
+ def getArgument(self, name: str, default: str = None) -> str:
+
+ # parse our arguments (only happens once)
+ self.parseArguments()
+
+ # if we don't have the parameter, return the default
+ if name not in self.Arguments:
+ return default
+
+ # otherwise return the value
+ return self.Arguments[name]
+
+ # Gets the value of a parameter as a float or returns the default
+ def getArgumentAsFloat(self, name: str, default: float = None) -> float:
+
+ # try to parse as a float, otherwise return the default
+ try:
+ return float(self.getArgument(name, default))
+ except:
+ return default
+
+ # Gets the value of a parameter as an integer or returns the default
+ def getArgumentAsInt(self, name: str, default: int = None) -> int:
+
+ # try to parse as a integer, otherwise return the default
+ try:
+ return int(self.getArgument(name, default))
+ except:
+ return default
+
+ # Allows retrieving values from the given GCODE line
+ @staticmethod
+ def getDirectArgument(line: str, key: str, default: str = None) -> str:
+
+ if key not in line or (";" in line and line.find(key) > line.find(";") and ";ChangeAtZ" not in key and ";LAYER:" not in key):
+ return default
+
+ # allows for string lengths larger than 1
+ sub_part = line[line.find(key) + len(key):]
+
if ";ChangeAtZ" in key:
- m = re.search("^[0-4]", subPart)
+ m = re.search("^[0-4]", sub_part)
elif ";LAYER:" in key:
- m = re.search("^[+-]?[0-9]*", subPart)
+ m = re.search("^[+-]?[0-9]*", sub_part)
else:
- #the minus at the beginning allows for negative values, e.g. for delta printers
- m = re.search("^[-]?[0-9]*\.?[0-9]*", subPart)
- if m == None:
+ # the minus at the beginning allows for negative values, e.g. for delta printers
+ m = re.search(r"^[-]?[0-9]*\.?[0-9]*", sub_part)
+ if m is None:
return default
+
try:
- return float(m.group(0))
+ return m.group(0)
except:
return default
- def execute(self, data):
- #Check which changes should apply
- ChangeProp = {"speed": self.getSettingValueByKey("e1_Change_speed"),
- "flowrate": self.getSettingValueByKey("g1_Change_flowrate"),
- "flowrateOne": self.getSettingValueByKey("g3_Change_flowrateOne"),
- "flowrateTwo": self.getSettingValueByKey("g5_Change_flowrateTwo"),
- "bedTemp": self.getSettingValueByKey("h1_Change_bedTemp"),
- "extruderOne": self.getSettingValueByKey("i1_Change_extruderOne"),
- "extruderTwo": self.getSettingValueByKey("i3_Change_extruderTwo"),
- "fanSpeed": self.getSettingValueByKey("j1_Change_fanSpeed")}
- ChangePrintSpeed = self.getSettingValueByKey("f1_Change_printspeed")
- ChangeStrings = {"speed": "M220 S%f\n",
- "flowrate": "M221 S%f\n",
- "flowrateOne": "M221 T0 S%f\n",
- "flowrateTwo": "M221 T1 S%f\n",
- "bedTemp": "M140 S%f\n",
- "extruderOne": "M104 S%f T0\n",
- "extruderTwo": "M104 S%f T1\n",
- "fanSpeed": "M106 S%d\n"}
- target_values = {"speed": self.getSettingValueByKey("e2_speed"),
- "printspeed": self.getSettingValueByKey("f2_printspeed"),
- "flowrate": self.getSettingValueByKey("g2_flowrate"),
- "flowrateOne": self.getSettingValueByKey("g4_flowrateOne"),
- "flowrateTwo": self.getSettingValueByKey("g6_flowrateTwo"),
- "bedTemp": self.getSettingValueByKey("h2_bedTemp"),
- "extruderOne": self.getSettingValueByKey("i2_extruderOne"),
- "extruderTwo": self.getSettingValueByKey("i4_extruderTwo"),
- "fanSpeed": self.getSettingValueByKey("j2_fanSpeed")}
- old = {"speed": -1, "flowrate": 100, "flowrateOne": -1, "flowrateTwo": -1, "platformTemp": -1, "extruderOne": -1,
- "extruderTwo": -1, "bedTemp": -1, "fanSpeed": -1, "state": -1}
- twLayers = self.getSettingValueByKey("d_twLayers")
- if self.getSettingValueByKey("c_behavior") == "single_layer":
- behavior = 1
- else:
- behavior = 0
+ # Converts the command parameter to a int or returns the default
+ @staticmethod
+ def getDirectArgumentAsFloat(line: str, key: str, default: float = None) -> float:
+
+ # get the value from the command
+ value = GCodeCommand.getDirectArgument(line, key, default)
+
+ # stop here if it's the default
+ if value == default:
+ return value
+
try:
- twLayers = max(int(twLayers),1) #for the case someone entered something as "funny" as -1
+ return float(value)
except:
- twLayers = 1
- pres_ext = 0
- done_layers = 0
- z = 0
- x = None
- y = None
- layer = -100000 #layer no. may be negative (raft) but never that low
- # state 0: deactivated, state 1: activated, state 2: active, but below z,
- # state 3: active and partially executed (multi layer), state 4: active and passed z
- state = 1
- # IsUM2: Used for reset of values (ok for Marlin/Sprinter),
- # has to be set to 1 for UltiGCode (work-around for missing default values)
- IsUM2 = False
- oldValueUnknown = False
- TWinstances = 0
-
- if self.getSettingValueByKey("a_trigger") == "layer_no":
- targetL_i = int(self.getSettingValueByKey("b_targetL"))
- targetZ = 100000
- else:
- targetL_i = -100000
- targetZ = self.getSettingValueByKey("b_targetZ")
+ return default
+
+ # Converts the command parameter to a int or returns the default
+ @staticmethod
+ def getDirectArgumentAsInt(line: str, key: str, default: int = None) -> int:
+
+ # get the value from the command
+ value = GCodeCommand.getDirectArgument(line, key, default)
+
+ # stop here if it's the default
+ if value == default:
+ return value
+
+ try:
+ return int(value)
+ except:
+ return default
+
+ # Parses the arguments of the command on demand, only once
+ def parseArguments(self):
+
+ # stop here if we don't have any remaining components
+ if len(self.Components) <= 1:
+ return None
+
+ # iterate and index all of our parameters, skip the first component as it's the command
+ for i in range(1, len(self.Components)):
+
+ # get our component
+ component = self.Components[i]
+
+ # get the first character of the parameter, which is the name
+ component_name = component[0]
+
+ # get the value of the parameter (the rest of the string
+ component_value = None
+
+ # get our value if we have one
+ if len(component) > 1:
+ component_value = component[1:]
+
+ # index the argument
+ self.Arguments[component_name] = component_value
+
+ # clear the components to we don't process again
+ self.Components = []
+
+ # Easy function for replacing any GCODE parameter variable in a given GCODE command
+ @staticmethod
+ def replaceDirectArgument(line: str, key: str, value: str) -> str:
+ return re.sub(r"(^|\s)" + key + r"[\d\.]+(\s|$)", r"\1" + key + str(value) + r"\2", line)
+
+ # Resets the model back to defaults
+ def reset(self):
+ self.Command = None
+ self.Arguments = {}
+
+
+# The primary ChangeAtZ class that does all the gcode editing. This was broken out into an
+# independent class so it could be debugged using a standard IDE
+class ChangeAtZProcessor:
+
+ # Holds our current height
+ CurrentZ = None
+
+ # Holds our current layer number
+ CurrentLayer = None
+
+ # Indicates if we're only supposed to apply our settings to a single layer or multiple layers
+ IsApplyToSingleLayer = False
+
+ # Indicates if this should emit the changes as they happen to the LCD
+ IsDisplayingChangesToLcd = False
+
+ # Indicates that this mod is still enabled (or not)
+ IsEnabled = True
+
+ # Indicates if we're processing inside the target layer or not
+ IsInsideTargetLayer = False
+
+ # Indicates if we have restored the previous values from before we started our pass
+ IsLastValuesRestored = False
+
+ # Indicates if the user has opted for linear move retractions or firmware retractions
+ IsLinearRetraction = True
+
+ # Indicates if we're targetting by layer or height value
+ IsTargetByLayer = True
+
+ # Indicates if we have injected our changed values for the given layer yet
+ IsTargetValuesInjected = False
+
+ # Holds the last extrusion value, used with detecting when a retraction is made
+ LastE = None
+
+ # An index of our gcodes which we're monitoring
+ LastValues = {}
+
+ # The detected layer height from the gcode
+ LayerHeight = None
+
+ # The target layer
+ TargetLayer = None
+
+ # Holds the values the user has requested to change
+ TargetValues = {}
+
+ # The target height in mm
+ TargetZ = None
+
+ # Used to track if we've been inside our target layer yet
+ WasInsideTargetLayer = False
+
+ # boots up the class with defaults
+ def __init__(self):
+ self.reset()
+
+ # Modifies the given GCODE and injects the commands at the various targets
+ def execute(self, data):
+
+ # short cut the whole thing if we're not enabled
+ if not self.IsEnabled:
+ return data
+
+ # our layer cursor
index = 0
+
for active_layer in data:
+
+ # will hold our updated gcode
modified_gcode = ""
+
+ # mark all the defaults for deletion
+ active_layer = self.markChangesForDeletion(active_layer)
+
+ # break apart the layer into commands
lines = active_layer.split("\n")
+
+ # evaluate each command individually
for line in lines:
- if line.strip() == "":
+
+ # trim or command
+ line = line.strip()
+
+ # skip empty lines
+ if len(line) == 0:
continue
- if ";Generated with Cura_SteamEngine" in line:
- TWinstances += 1
- modified_gcode += ";ChangeAtZ instances: %d\n" % TWinstances
- if not ("M84" in line or "M25" in line or ("G1" in line and ChangePrintSpeed and (state==3 or state==4)) or
- ";ChangeAtZ instances:" in line):
- modified_gcode += line + "\n"
- IsUM2 = ("FLAVOR:UltiGCode" in line) or IsUM2 #Flavor is UltiGCode!
- if ";ChangeAtZ-state" in line: #checks for state change comment
- state = self.getValue(line, ";ChangeAtZ-state", state)
- if ";ChangeAtZ instances:" in line:
- try:
- tempTWi = int(line[20:])
- except:
- tempTWi = TWinstances
- TWinstances = tempTWi
- if ";Small layer" in line: #checks for begin of Cool Head Lift
- old["state"] = state
- state = 0
- if ";LAYER:" in line: #new layer no. found
- if state == 0:
- state = old["state"]
- layer = self.getValue(line, ";LAYER:", layer)
- if targetL_i > -100000: #target selected by layer no.
- if (state == 2 or targetL_i == 0) and layer == targetL_i: #determine targetZ from layer no.; checks for change on layer 0
- state = 2
- targetZ = z + 0.001
- if (self.getValue(line, "T", None) is not None) and (self.getValue(line, "M", None) is None): #looking for single T-cmd
- pres_ext = self.getValue(line, "T", pres_ext)
- if "M190" in line or "M140" in line and state < 3: #looking for bed temp, stops after target z is passed
- old["bedTemp"] = self.getValue(line, "S", old["bedTemp"])
- if "M109" in line or "M104" in line and state < 3: #looking for extruder temp, stops after target z is passed
- if self.getValue(line, "T", pres_ext) == 0:
- old["extruderOne"] = self.getValue(line, "S", old["extruderOne"])
- elif self.getValue(line, "T", pres_ext) == 1:
- old["extruderTwo"] = self.getValue(line, "S", old["extruderTwo"])
- if "M107" in line: #fan is stopped; is always updated in order not to miss switch off for next object
- old["fanSpeed"] = 0
- if "M106" in line and state < 3: #looking for fan speed
- old["fanSpeed"] = self.getValue(line, "S", old["fanSpeed"])
- if "M221" in line and state < 3: #looking for flow rate
- tmp_extruder = self.getValue(line, "T", None)
- if tmp_extruder == None: #check if extruder is specified
- old["flowrate"] = self.getValue(line, "S", old["flowrate"])
- if old["flowrate"] == -1:
- old["flowrate"] = 100.0
- elif tmp_extruder == 0: #first extruder
- old["flowrateOne"] = self.getValue(line, "S", old["flowrateOne"])
- elif tmp_extruder == 1: #second extruder
- old["flowrateTwo"] = self.getValue(line, "S", old["flowrateTwo"])
- if ("M84" in line or "M25" in line):
- if state>0 and ChangeProp["speed"]: #"finish" commands for UM Original and UM2
- modified_gcode += "M220 S100 ; speed reset to 100% at the end of print\n"
- modified_gcode += "M117 \n"
- modified_gcode += line + "\n"
- if "G1" in line or "G0" in line:
- newZ = self.getValue(line, "Z", z)
- x = self.getValue(line, "X", None)
- y = self.getValue(line, "Y", None)
- e = self.getValue(line, "E", None)
- f = self.getValue(line, "F", None)
- if 'G1' in line and ChangePrintSpeed and (state==3 or state==4):
- # check for pure print movement in target range:
- if x != None and y != None and f != None and e != None and newZ==z:
- modified_gcode += "G1 F%d X%1.3f Y%1.3f E%1.5f\n" % (int(f / 100.0 * float(target_values["printspeed"])), self.getValue(line, "X"),
- self.getValue(line, "Y"), self.getValue(line, "E"))
- else: #G1 command but not a print movement
- modified_gcode += line + "\n"
- # no changing on retraction hops which have no x and y coordinate:
- if (newZ != z) and (x is not None) and (y is not None):
- z = newZ
- if z < targetZ and state == 1:
- state = 2
- if z >= targetZ and state == 2:
- state = 3
- done_layers = 0
- for key in ChangeProp:
- if ChangeProp[key] and old[key]==-1: #old value is not known
- oldValueUnknown = True
- if oldValueUnknown: #the changing has to happen within one layer
- twLayers = 1
- if IsUM2: #Parameters have to be stored in the printer (UltiGCode=UM2)
- modified_gcode += "M605 S%d;stores parameters before changing\n" % (TWinstances-1)
- if behavior == 1: #single layer change only and then reset
- twLayers = 1
- if ChangePrintSpeed and behavior == 0:
- twLayers = done_layers + 1
- if state==3:
- if twLayers-done_layers>0: #still layers to go?
- if targetL_i > -100000:
- modified_gcode += ";ChangeAtZ V%s: executed at Layer %d\n" % (self.version,layer)
- modified_gcode += "M117 Printing... ch@L%4d\n" % layer
- else:
- modified_gcode += (";ChangeAtZ V%s: executed at %1.2f mm\n" % (self.version,z))
- modified_gcode += "M117 Printing... ch@%5.1f\n" % z
- for key in ChangeProp:
- if ChangeProp[key]:
- modified_gcode += ChangeStrings[key] % float(old[key]+(float(target_values[key])-float(old[key]))/float(twLayers)*float(done_layers+1))
- done_layers += 1
- else:
- state = 4
- if behavior == 1: #reset values after one layer
- if targetL_i > -100000:
- modified_gcode += ";ChangeAtZ V%s: reset on Layer %d\n" % (self.version,layer)
- else:
- modified_gcode += ";ChangeAtZ V%s: reset at %1.2f mm\n" % (self.version,z)
- if IsUM2 and oldValueUnknown: #executes on UM2 with Ultigcode and machine setting
- modified_gcode += "M606 S%d;recalls saved settings\n" % (TWinstances-1)
- else: #executes on RepRap, UM2 with Ultigcode and Cura setting
- for key in ChangeProp:
- if ChangeProp[key]:
- modified_gcode += ChangeStrings[key] % float(old[key])
- # re-activates the plugin if executed by pre-print G-command, resets settings:
- if (z < targetZ or layer == 0) and state >= 3: #resets if below change level or at level 0
- state = 2
- done_layers = 0
- if targetL_i > -100000:
- modified_gcode += ";ChangeAtZ V%s: reset below Layer %d\n" % (self.version, targetL_i)
- else:
- modified_gcode += ";ChangeAtZ V%s: reset below %1.2f mm\n" % (self.version, targetZ)
- if IsUM2 and oldValueUnknown: #executes on UM2 with Ultigcode and machine setting
- modified_gcode += "M606 S%d;recalls saved settings\n" % (TWinstances-1)
- else: #executes on RepRap, UM2 with Ultigcode and Cura setting
- for key in ChangeProp:
- if ChangeProp[key]:
- modified_gcode += ChangeStrings[key] % float(old[key])
+
+ # update our layer number if applicable
+ self.processLayerNumber(line)
+
+ # update our layer height if applicable
+ self.processLayerHeight(line)
+
+ # check if we're at the target layer or not
+ self.processTargetLayer()
+
+ # process any changes to the gcode
+ modified_gcode += self.processLine(line)
+
+ # remove any marked defaults
+ modified_gcode = self.removeMarkedChanges(modified_gcode)
+
+ # append our modified line
data[index] = modified_gcode
+
index += 1
+
+ # return our modified gcode
return data
+
+ # Builds the restored layer settings based on the previous settings and returns the relevant GCODE lines
+ def getChangedLastValues(self) -> Dict[str, any]:
+
+ # capture the values that we've changed
+ changed = {}
+
+ # for each of our target values, get the value to restore
+ # no point in restoring values we haven't changed
+ for key in self.TargetValues:
+
+ # skip target values we can't restore
+ if key not in self.LastValues:
+ continue
+
+ # save into our changed
+ changed[key] = self.LastValues[key]
+
+ # return our collection of changed values
+ return changed
+
+ # Builds the relevant display feedback for each of the values
+ def getDisplayChangesFromValues(self, values: Dict[str, any]) -> str:
+
+ # stop here if we're not outputting data
+ if not self.IsDisplayingChangesToLcd:
+ return ""
+
+ # will hold all the default settings for the target layer
+ codes = []
+
+ # looking for wait for bed temp
+ if "bedTemp" in values:
+ codes.append("BedTemp: " + str(values["bedTemp"]))
+
+ # set our extruder one temp (if specified)
+ if "extruderOne" in values:
+ codes.append("Extruder 1 Temp: " + str(values["extruderOne"]))
+
+ # set our extruder two temp (if specified)
+ if "extruderTwo" in values:
+ codes.append("Extruder 2 Temp: " + str(values["extruderTwo"]))
+
+ # set global flow rate
+ if "flowrate" in values:
+ codes.append("Extruder A Flow Rate: " + str(values["flowrate"]))
+
+ # set extruder 0 flow rate
+ if "flowrateOne" in values:
+ codes.append("Extruder 1 Flow Rate: " + str(values["flowrateOne"]))
+
+ # set second extruder flow rate
+ if "flowrateTwo" in values:
+ codes.append("Extruder 2 Flow Rate: " + str(values["flowrateTwo"]))
+
+ # set our fan speed
+ if "fanSpeed" in values:
+ codes.append("Fan Speed: " + str(values["fanSpeed"]))
+
+ # set feedrate percentage
+ if "speed" in values:
+ codes.append("Print Speed: " + str(values["speed"]))
+
+ # set print rate percentage
+ if "printspeed" in values:
+ codes.append("Linear Print Speed: " + str(values["printspeed"]))
+
+ # set retract rate
+ if "retractfeedrate" in values:
+ codes.append("Retract Feed Rate: " + str(values["retractfeedrate"]))
+
+ # set retract length
+ if "retractlength" in values:
+ codes.append("Retract Length: " + str(values["retractlength"]))
+
+ # stop here if there's nothing to output
+ if len(codes) == 0:
+ return ""
+
+ # output our command to display the data
+ return "M117 " + ", ".join(codes) + "\n"
+
+ # Converts the last values to something that can be output on the LCD
+ def getLastDisplayValues(self) -> str:
+
+ # convert our last values to something we can output
+ return self.getDisplayChangesFromValues(self.getChangedLastValues())
+
+ # Converts the target values to something that can be output on the LCD
+ def getTargetDisplayValues(self) -> str:
+
+ # convert our target values to something we can output
+ return self.getDisplayChangesFromValues(self.TargetValues)
+
+ # Builds the the relevant GCODE lines from the given collection of values
+ def getCodeFromValues(self, values: Dict[str, any]) -> str:
+
+ # will hold all the desired settings for the target layer
+ codes = self.getCodeLinesFromValues(values)
+
+ # stop here if there are no values that require changing
+ if len(codes) == 0:
+ return ""
+
+ # return our default block for this layer
+ return ";[CAZD:\n" + "\n".join(codes) + "\n;:CAZD]"
+
+ # Builds the relevant GCODE lines from the given collection of values
+ def getCodeLinesFromValues(self, values: Dict[str, any]) -> List[str]:
+
+ # will hold all the default settings for the target layer
+ codes = []
+
+ # looking for wait for bed temp
+ if "bedTemp" in values:
+ codes.append("M140 S" + str(values["bedTemp"]))
+
+ # set our extruder one temp (if specified)
+ if "extruderOne" in values:
+ codes.append("M104 S" + str(values["extruderOne"]) + " T0")
+
+ # set our extruder two temp (if specified)
+ if "extruderTwo" in values:
+ codes.append("M104 S" + str(values["extruderTwo"]) + " T1")
+
+ # set our fan speed
+ if "fanSpeed" in values:
+
+ # convert our fan speed percentage to PWM
+ fan_speed = int((float(values["fanSpeed"]) / 100.0) * 255)
+
+ # add our fan speed to the defaults
+ codes.append("M106 S" + str(fan_speed))
+
+ # set global flow rate
+ if "flowrate" in values:
+ codes.append("M221 S" + str(values["flowrate"]))
+
+ # set extruder 0 flow rate
+ if "flowrateOne" in values:
+ codes.append("M221 S" + str(values["flowrateOne"]) + " T0")
+
+ # set second extruder flow rate
+ if "flowrateTwo" in values:
+ codes.append("M221 S" + str(values["flowrateTwo"]) + " T1")
+
+ # set feedrate percentage
+ if "speed" in values:
+ codes.append("M220 S" + str(values["speed"]) + " T1")
+
+ # set print rate percentage
+ if "printspeed" in values:
+ codes.append(";PRINTSPEED " + str(values["printspeed"]) + "")
+
+ # set retract rate
+ if "retractfeedrate" in values:
+
+ if self.IsLinearRetraction:
+ codes.append(";RETRACTFEEDRATE " + str(values["retractfeedrate"] * 60) + "")
+ else:
+ codes.append("M207 F" + str(values["retractfeedrate"] * 60) + "")
+
+ # set retract length
+ if "retractlength" in values:
+
+ if self.IsLinearRetraction:
+ codes.append(";RETRACTLENGTH " + str(values["retractlength"]) + "")
+ else:
+ codes.append("M207 S" + str(values["retractlength"]) + "")
+
+ return codes
+
+ # Builds the restored layer settings based on the previous settings and returns the relevant GCODE lines
+ def getLastValues(self) -> str:
+
+ # build the gcode to restore our last values
+ return self.getCodeFromValues(self.getChangedLastValues())
+
+ # Builds the gcode to inject either the changed values we want or restore the previous values
+ def getInjectCode(self) -> str:
+
+ # if we're now outside of our target layer and haven't restored our last values, do so now
+ if not self.IsInsideTargetLayer and self.WasInsideTargetLayer and not self.IsLastValuesRestored:
+
+ # mark that we've injected the last values
+ self.IsLastValuesRestored = True
+
+ # inject the defaults
+ return self.getLastValues() + "\n" + self.getLastDisplayValues()
+
+ # if we're inside our target layer but haven't added our values yet, do so now
+ if self.IsInsideTargetLayer and not self.IsTargetValuesInjected:
+
+ # mark that we've injected the target values
+ self.IsTargetValuesInjected = True
+
+ # inject the defaults
+ return self.getTargetValues() + "\n" + self.getTargetDisplayValues()
+
+ # nothing to do
+ return ""
+
+ # Returns the unmodified GCODE line from previous ChangeZ edits
+ @staticmethod
+ def getOriginalLine(line: str) -> str:
+
+ # get the change at z original (cazo) details
+ original_line = re.search(r"\[CAZO:(.*?):CAZO\]", line)
+
+ # if we didn't get a hit, this is the original line
+ if original_line is None:
+ return line
+
+ return original_line.group(1)
+
+ # Builds the target layer settings based on the specified values and returns the relevant GCODE lines
+ def getTargetValues(self) -> str:
+
+ # build the gcode to change our current values
+ return self.getCodeFromValues(self.TargetValues)
+
+ # Determines if the current line is at or below the target required to start modifying
+ def isTargetLayerOrHeight(self) -> bool:
+
+ # target selected by layer no.
+ if self.IsTargetByLayer:
+
+ # if we don't have a current layer, we're not there yet
+ if self.CurrentLayer is None:
+ return False
+
+ # if we're applying to a single layer, stop if our layer is not identical
+ if self.IsApplyToSingleLayer:
+ return self.CurrentLayer == self.TargetLayer
+ else:
+ return self.CurrentLayer >= self.TargetLayer
+
+ else:
+
+ # if we don't have a current Z, we're not there yet
+ if self.CurrentZ is None:
+ return False
+
+ # if we're applying to a single layer, stop if our Z is not identical
+ if self.IsApplyToSingleLayer:
+ return self.CurrentZ == self.TargetZ
+ else:
+ return self.CurrentZ >= self.TargetZ
+
+ # Marks any current ChangeZ layer defaults in the layer for deletion
+ @staticmethod
+ def markChangesForDeletion(layer: str):
+ return re.sub(r";\[CAZD:", ";[CAZD:DELETE:", layer)
+
+ # Grabs the current height
+ def processLayerHeight(self, line: str):
+
+ # stop here if we haven't entered a layer yet
+ if self.CurrentLayer is None:
+ return
+
+ # get our gcode command
+ command = GCodeCommand.getFromLine(line)
+
+ # skip if it's not a command we're interested in
+ if command is None:
+ return
+
+ # stop here if this isn't a linear move command
+ if command.Command != "G0" and command.Command != "G1":
+ return
+
+ # get our value from the command
+ current_z = command.getArgumentAsFloat("Z", None)
+
+ # stop here if we don't have a Z value defined, we can't get the height from this command
+ if current_z is None:
+ return
+
+ # stop if there's no change
+ if current_z == self.CurrentZ:
+ return
+
+ # set our current Z value
+ self.CurrentZ = current_z
+
+ # if we don't have a layer height yet, set it based on the current Z value
+ if self.LayerHeight is None:
+ self.LayerHeight = self.CurrentZ
+
+ # Grabs the current layer number
+ def processLayerNumber(self, line: str):
+
+ # if this isn't a layer comment, stop here, nothing to update
+ if ";LAYER:" not in line:
+ return
+
+ # get our current layer number
+ current_layer = GCodeCommand.getDirectArgumentAsInt(line, ";LAYER:", None)
+
+ # this should never happen, but if our layer number hasn't changed, stop here
+ if current_layer == self.CurrentLayer:
+ return
+
+ # update our current layer
+ self.CurrentLayer = current_layer
+
+ # Makes any linear move changes and also injects either target or restored values depending on the plugin state
+ def processLine(self, line: str) -> str:
+
+ # used to change the given line of code
+ modified_gcode = ""
+
+ # track any values that we may be interested in
+ self.trackChangeableValues(line)
+
+ # if we're not inside the target layer, simply read the any
+ # settings we can and revert any ChangeAtZ deletions
+ if not self.IsInsideTargetLayer:
+
+ # read any settings if we haven't hit our target layer yet
+ if not self.WasInsideTargetLayer:
+ self.processSetting(line)
+
+ # if we haven't hit our target yet, leave the defaults as is (unmark them for deletion)
+ if "[CAZD:DELETE:" in line:
+ line = line.replace("[CAZD:DELETE:", "[CAZD:")
+
+ # if we're targeting by Z, we want to add our values before the first linear move
+ if "G1 " in line or "G0 " in line:
+ modified_gcode += self.getInjectCode()
+
+ # modify our command if we're still inside our target layer, otherwise pass unmodified
+ if self.IsInsideTargetLayer:
+ modified_gcode += self.processLinearMove(line) + "\n"
+ else:
+ modified_gcode += line + "\n"
+
+ # if we're targetting by layer we want to add our values just after the layer label
+ if ";LAYER:" in line:
+ modified_gcode += self.getInjectCode()
+
+ # return our changed code
+ return modified_gcode
+
+ # Handles any linear moves in the current line
+ def processLinearMove(self, line: str) -> str:
+
+ # if it's not a linear motion command we're not interested
+ if not ("G1 " in line or "G0 " in line):
+ return line
+
+ # always get our original line, otherwise the effect will be cumulative
+ line = self.getOriginalLine(line)
+
+ # get our command from the line
+ linear_command = GCodeCommand.getLinearMoveCommand(line)
+
+ # if it's not a linear move, we don't care
+ if linear_command is None:
+ return
+
+ # get our linear move parameters
+ feed_rate = linear_command.Arguments["F"]
+ x_coord = linear_command.Arguments["X"]
+ y_coord = linear_command.Arguments["Y"]
+ z_coord = linear_command.Arguments["Z"]
+ extrude_length = linear_command.Arguments["E"]
+
+ # set our new line to our old line
+ new_line = line
+
+ # handle retract length
+ new_line = self.processRetractLength(extrude_length, feed_rate, new_line, x_coord, y_coord, z_coord)
+
+ # handle retract feed rate
+ new_line = self.processRetractFeedRate(extrude_length, feed_rate, new_line, x_coord, y_coord, z_coord)
+
+ # handle print speed adjustments
+ new_line = self.processPrintSpeed(feed_rate, new_line)
+
+ # set our current extrude position
+ self.LastE = extrude_length if extrude_length is not None else self.LastE
+
+ # if no changes have been made, stop here
+ if new_line == line:
+ return line
+
+ # return our updated command
+ return self.setOriginalLine(new_line, line)
+
+ # Handles any changes to print speed for the given linear motion command
+ def processPrintSpeed(self, feed_rate: float, new_line: str) -> str:
+
+ # if we're not setting print speed or we don't have a feed rate, stop here
+ if "printspeed" not in self.TargetValues or feed_rate is None:
+ return new_line
+
+ # get our requested print speed
+ print_speed = int(self.TargetValues["printspeed"])
+
+ # if they requested no change to print speed (ie: 100%), stop here
+ if print_speed == 100:
+ return new_line
+
+ # get our feed rate from the command
+ feed_rate = GCodeCommand.getDirectArgumentAsFloat(new_line, "F") * (float(print_speed) / 100.0)
+
+ # change our feed rate
+ return GCodeCommand.replaceDirectArgument(new_line, "F", feed_rate)
+
+ # Handles any changes to retraction length for the given linear motion command
+ def processRetractLength(self, extrude_length: float, feed_rate: float, new_line: str, x_coord: float, y_coord: float, z_coord: float) -> str:
+
+ # if we don't have a retract length in the file we can't add one
+ if "retractlength" not in self.LastValues or self.LastValues["retractlength"] == 0:
+ return new_line
+
+ # if we're not changing retraction length, stop here
+ if "retractlength" not in self.TargetValues:
+ return new_line
+
+ # retractions are only F (feed rate) and E (extrude), at least in cura
+ if x_coord is not None or y_coord is not None or z_coord is not None:
+ return new_line
+
+ # since retractions require both F and E, and we don't have either, we can't process
+ if feed_rate is None or extrude_length is None:
+ return new_line
+
+ # stop here if we don't know our last extrude value
+ if self.LastE is None:
+ return new_line
+
+ # if there's no change in extrude we have nothing to change
+ if self.LastE == extrude_length:
+ return new_line
+
+ # if our last extrude was lower than our current, we're restoring, so skip
+ if self.LastE < extrude_length:
+ return new_line
+
+ # get our desired retract length
+ retract_length = float(self.TargetValues["retractlength"])
+
+ # subtract the difference between the default and the desired
+ extrude_length -= (retract_length - self.LastValues["retractlength"])
+
+ # replace our extrude amount
+ return GCodeCommand.replaceDirectArgument(new_line, "E", extrude_length)
+
+ # Used for picking out the retract length set by Cura
+ def processRetractLengthSetting(self, line: str):
+
+ # skip if we're not doing linear retractions
+ if not self.IsLinearRetraction:
+ return
+
+ # get our command from the line
+ linear_command = GCodeCommand.getLinearMoveCommand(line)
+
+ # if it's not a linear move, we don't care
+ if linear_command is None:
+ return
+
+ # get our linear move parameters
+ feed_rate = linear_command.Arguments["F"]
+ x_coord = linear_command.Arguments["X"]
+ y_coord = linear_command.Arguments["Y"]
+ z_coord = linear_command.Arguments["Z"]
+ extrude_length = linear_command.Arguments["E"]
+
+ # the command we're looking for only has extrude and feed rate
+ if x_coord is not None or y_coord is not None or z_coord is not None:
+ return
+
+ # if either extrude or feed is missing we're likely looking at the wrong command
+ if extrude_length is None or feed_rate is None:
+ return
+
+ # cura stores the retract length as a negative E just before it starts printing
+ extrude_length = extrude_length * -1
+
+ # if it's a negative extrude after being inverted, it's not our retract length
+ if extrude_length < 0:
+ return
+
+ # what ever the last negative retract length is it wins
+ self.LastValues["retractlength"] = extrude_length
+
+ # Handles any changes to retraction feed rate for the given linear motion command
+ def processRetractFeedRate(self, extrude_length: float, feed_rate: float, new_line: str, x_coord: float, y_coord: float, z_coord: float) -> str:
+
+ # skip if we're not doing linear retractions
+ if not self.IsLinearRetraction:
+ return new_line
+
+ # if we're not changing retraction length, stop here
+ if "retractfeedrate" not in self.TargetValues:
+ return new_line
+
+ # retractions are only F (feed rate) and E (extrude), at least in cura
+ if x_coord is not None or y_coord is not None or z_coord is not None:
+ return new_line
+
+ # since retractions require both F and E, and we don't have either, we can't process
+ if feed_rate is None or extrude_length is None:
+ return new_line
+
+ # get our desired retract feed rate
+ retract_feed_rate = float(self.TargetValues["retractfeedrate"])
+
+ # convert to units/min
+ retract_feed_rate *= 60
+
+ # replace our feed rate
+ return GCodeCommand.replaceDirectArgument(new_line, "F", retract_feed_rate)
+
+ # Used for finding settings in the print file before we process anything else
+ def processSetting(self, line: str):
+
+ # if we're in layers already we're out of settings
+ if self.CurrentLayer is not None:
+ return
+
+ # check our retract length
+ self.processRetractLengthSetting(line)
+
+ # Sets the flags if we're at the target layer or not
+ def processTargetLayer(self):
+
+ # skip this line if we're not there yet
+ if not self.isTargetLayerOrHeight():
+
+ # flag that we're outside our target layer
+ self.IsInsideTargetLayer = False
+
+ # skip to the next line
+ return
+
+ # flip if we hit our target layer
+ self.WasInsideTargetLayer = True
+
+ # flag that we're inside our target layer
+ self.IsInsideTargetLayer = True
+
+ # Removes all the ChangeZ layer defaults from the given layer
+ @staticmethod
+ def removeMarkedChanges(layer: str) -> str:
+ return re.sub(r";\[CAZD:DELETE:[\s\S]+?:CAZD\](\n|$)", "", layer)
+
+ # Resets the class contents to defaults
+ def reset(self):
+
+ self.TargetValues = {}
+ self.IsApplyToSingleLayer = False
+ self.LastE = None
+ self.CurrentZ = None
+ self.CurrentLayer = None
+ self.IsTargetByLayer = True
+ self.TargetLayer = None
+ self.TargetZ = None
+ self.LayerHeight = None
+ self.LastValues = {}
+ self.IsLinearRetraction = True
+ self.IsInsideTargetLayer = False
+ self.IsTargetValuesInjected = False
+ self.IsLastValuesRestored = False
+ self.WasInsideTargetLayer = False
+ self.IsEnabled = True
+
+ # Sets the original GCODE line in a given GCODE command
+ @staticmethod
+ def setOriginalLine(line, original) -> str:
+ return line + ";[CAZO:" + original + ":CAZO]"
+
+ # Tracks the change in gcode values we're interested in
+ def trackChangeableValues(self, line: str):
+
+ # simulate a print speed command
+ if ";PRINTSPEED" in line:
+ line = line.replace(";PRINTSPEED ", "M220 S")
+
+ # simulate a retract feedrate command
+ if ";RETRACTFEEDRATE" in line:
+ line = line.replace(";RETRACTFEEDRATE ", "M207 F")
+
+ # simulate a retract length command
+ if ";RETRACTLENGTH" in line:
+ line = line.replace(";RETRACTLENGTH ", "M207 S")
+
+ # get our gcode command
+ command = GCodeCommand.getFromLine(line)
+
+ # stop here if it isn't a G or M command
+ if command is None:
+ return
+
+ # handle retract length changes
+ if command.Command == "M207":
+
+ # get our retract length if provided
+ if "S" in command.Arguments:
+ self.LastValues["retractlength"] = command.getArgumentAsFloat("S")
+
+ # get our retract feedrate if provided, convert from mm/m to mm/s
+ if "F" in command.Arguments:
+ self.LastValues["retractfeedrate"] = command.getArgumentAsFloat("F") / 60.0
+
+ # move to the next command
+ return
+
+ # handle bed temp changes
+ if command.Command == "M140" or command.Command == "M190":
+
+ # get our bed temp if provided
+ if "S" in command.Arguments:
+ self.LastValues["bedTemp"] = command.getArgumentAsFloat("S")
+
+ # move to the next command
+ return
+
+ # handle extruder temp changes
+ if command.Command == "M104" or command.Command == "M109":
+
+ # get our tempurature
+ tempurature = command.getArgumentAsFloat("S")
+
+ # don't bother if we don't have a tempurature
+ if tempurature is None:
+ return
+
+ # get our extruder, default to extruder one
+ extruder = command.getArgumentAsInt("T", None)
+
+ # set our extruder temp based on the extruder
+ if extruder is None or extruder == 0:
+ self.LastValues["extruderOne"] = tempurature
+
+ if extruder is None or extruder == 1:
+ self.LastValues["extruderTwo"] = tempurature
+
+ # move to the next command
+ return
+
+ # handle fan speed changes
+ if command.Command == "M106":
+
+ # get our bed temp if provided
+ if "S" in command.Arguments:
+ self.LastValues["fanSpeed"] = (command.getArgumentAsInt("S") / 255.0) * 100
+
+ # move to the next command
+ return
+
+ # handle flow rate changes
+ if command.Command == "M221":
+
+ # get our flow rate
+ tempurature = command.getArgumentAsFloat("S")
+
+ # don't bother if we don't have a flow rate (for some reason)
+ if tempurature is None:
+ return
+
+ # get our extruder, default to global
+ extruder = command.getArgumentAsInt("T", None)
+
+ # set our extruder temp based on the extruder
+ if extruder is None:
+ self.LastValues["flowrate"] = tempurature
+ elif extruder == 1:
+ self.LastValues["flowrateOne"] = tempurature
+ elif extruder == 1:
+ self.LastValues["flowrateTwo"] = tempurature
+
+ # move to the next command
+ return
+
+ # handle print speed changes
+ if command.Command == "M220":
+
+ # get our speed if provided
+ if "S" in command.Arguments:
+ self.LastValues["speed"] = command.getArgumentAsInt("S")
+
+ # move to the next command
+ return
diff --git a/plugins/PostProcessingPlugin/scripts/ColorMix.py b/plugins/PostProcessingPlugin/scripts/ColorMix.py
index 050b9bbce6..45b2a0ad70 100644
--- a/plugins/PostProcessingPlugin/scripts/ColorMix.py
+++ b/plugins/PostProcessingPlugin/scripts/ColorMix.py
@@ -170,7 +170,7 @@ class ColorMix(Script):
modelNumber = 0
for active_layer in data:
modified_gcode = ""
- lineIndex = 0;
+ lineIndex = 0
lines = active_layer.split("\n")
for line in lines:
#dont leave blanks
diff --git a/plugins/PostProcessingPlugin/scripts/PauseAtHeight.py b/plugins/PostProcessingPlugin/scripts/PauseAtHeight.py
index e1d7faf5b3..b6e7460d46 100644
--- a/plugins/PostProcessingPlugin/scripts/PauseAtHeight.py
+++ b/plugins/PostProcessingPlugin/scripts/PauseAtHeight.py
@@ -132,13 +132,12 @@ class PauseAtHeight(Script):
"default_value": 3.3333,
"enabled": "pause_method not in [\\\"griffin\\\", \\\"repetier\\\"]"
},
- "redo_layers":
+ "redo_layer":
{
- "label": "Redo Layers",
- "description": "Redo a number of previous layers after a pause to increases adhesion.",
- "unit": "layers",
- "type": "int",
- "default_value": 0
+ "label": "Redo Layer",
+ "description": "Redo the last layer before the pause, to get the filament flowing again after having oozed a bit during the pause.",
+ "type": "bool",
+ "default_value": false
},
"standby_temperature":
{
@@ -226,7 +225,7 @@ class PauseAtHeight(Script):
park_y = self.getSettingValueByKey("head_park_y")
move_z = self.getSettingValueByKey("head_move_z")
layers_started = False
- redo_layers = self.getSettingValueByKey("redo_layers")
+ redo_layer = self.getSettingValueByKey("redo_layer")
standby_temperature = self.getSettingValueByKey("standby_temperature")
firmware_retract = Application.getInstance().getGlobalContainerStack().getProperty("machine_firmware_retract", "value")
control_temperatures = Application.getInstance().getGlobalContainerStack().getProperty("machine_nozzle_temp_enabled", "value")
@@ -335,24 +334,23 @@ class PauseAtHeight(Script):
if current_e >= 0:
break
- # include a number of previous layers
- for i in range(1, redo_layers + 1):
- prev_layer = data[index - i]
+ # Maybe redo the last layer.
+ if redo_layer:
+ prev_layer = data[index - 1]
layer = prev_layer + layer
# Get extruder's absolute position at the
- # beginning of the first layer redone
+ # beginning of the redone layer.
# see https://github.com/nallath/PostProcessingPlugin/issues/55
- if i == redo_layers:
- # Get X and Y from the next layer (better position for
- # the nozzle)
- x, y = self.getNextXY(layer)
- prev_lines = prev_layer.split("\n")
- for lin in prev_lines:
- new_e = self.getValue(lin, "E", current_e)
- if new_e != current_e:
- current_e = new_e
- break
+ # Get X and Y from the next layer (better position for
+ # the nozzle)
+ x, y = self.getNextXY(layer)
+ prev_lines = prev_layer.split("\n")
+ for lin in prev_lines:
+ new_e = self.getValue(lin, "E", current_e)
+ if new_e != current_e:
+ current_e = new_e
+ break
prepend_gcode = ";TYPE:CUSTOM\n"
prepend_gcode += ";added code by post processing\n"
@@ -481,8 +479,8 @@ class PauseAtHeight(Script):
prepend_gcode += self.putValue(M = 82) + " ; switch back to absolute E values\n"
- # reset extrude value to pre pause value
- prepend_gcode += self.putValue(G = 92, E = current_e) + "\n"
+ # reset extrude value to pre pause value
+ prepend_gcode += self.putValue(G = 92, E = current_e) + "\n"
layer = prepend_gcode + layer
diff --git a/plugins/PostProcessingPlugin/scripts/RetractContinue.py b/plugins/PostProcessingPlugin/scripts/RetractContinue.py
index b0af9cd95e..e437439287 100644
--- a/plugins/PostProcessingPlugin/scripts/RetractContinue.py
+++ b/plugins/PostProcessingPlugin/scripts/RetractContinue.py
@@ -29,45 +29,52 @@ class RetractContinue(Script):
current_e = 0
current_x = 0
current_y = 0
+ current_z = 0
extra_retraction_speed = self.getSettingValueByKey("extra_retraction_speed")
for layer_number, layer in enumerate(data):
lines = layer.split("\n")
for line_number, line in enumerate(lines):
- if self.getValue(line, "G") in {0, 1}: # Track X,Y location.
+ if self.getValue(line, "G") in {0, 1}: # Track X,Y,Z location.
current_x = self.getValue(line, "X", current_x)
current_y = self.getValue(line, "Y", current_y)
+ current_z = self.getValue(line, "Z", current_z)
if self.getValue(line, "G") == 1:
- if self.getValue(line, "E"):
- new_e = self.getValue(line, "E")
- if new_e >= current_e: # Not a retraction.
+ if not self.getValue(line, "E"): # Either None or 0: Not a retraction then.
+ continue
+ new_e = self.getValue(line, "E")
+ if new_e - current_e >= -0.0001: # Not a retraction. Account for floating point rounding errors.
+ current_e = new_e
+ continue
+ # A retracted travel move may consist of multiple commands, due to combing.
+ # This continues retracting over all of these moves and only unretracts at the end.
+ delta_line = 1
+ dx = current_x # Track the difference in X for this move only to compute the length of the travel.
+ dy = current_y
+ dz = current_z
+ while line_number + delta_line < len(lines) and self.getValue(lines[line_number + delta_line], "G") != 1:
+ travel_move = lines[line_number + delta_line]
+ if self.getValue(travel_move, "G") != 0:
+ delta_line += 1
continue
- # A retracted travel move may consist of multiple commands, due to combing.
- # This continues retracting over all of these moves and only unretracts at the end.
- delta_line = 1
- dx = current_x # Track the difference in X for this move only to compute the length of the travel.
- dy = current_y
- while line_number + delta_line < len(lines) and self.getValue(lines[line_number + delta_line], "G") != 1:
- travel_move = lines[line_number + delta_line]
- if self.getValue(travel_move, "G") != 0:
- delta_line += 1
- continue
- travel_x = self.getValue(travel_move, "X", dx)
- travel_y = self.getValue(travel_move, "Y", dy)
- f = self.getValue(travel_move, "F", "no f")
- length = math.sqrt((travel_x - dx) * (travel_x - dx) + (travel_y - dy) * (travel_y - dy)) # Length of the travel move.
- new_e -= length * extra_retraction_speed # New retraction is by ratio of this travel move.
- if f == "no f":
- new_travel_move = "G1 X{travel_x} Y{travel_y} E{new_e}".format(travel_x = travel_x, travel_y = travel_y, new_e = new_e)
- else:
- new_travel_move = "G1 F{f} X{travel_x} Y{travel_y} E{new_e}".format(f = f, travel_x = travel_x, travel_y = travel_y, new_e = new_e)
- lines[line_number + delta_line] = new_travel_move
+ travel_x = self.getValue(travel_move, "X", dx)
+ travel_y = self.getValue(travel_move, "Y", dy)
+ travel_z = self.getValue(travel_move, "Z", dz)
+ f = self.getValue(travel_move, "F", "no f")
+ length = math.sqrt((travel_x - dx) * (travel_x - dx) + (travel_y - dy) * (travel_y - dy) + (travel_z - dz) * (travel_z - dz)) # Length of the travel move.
+ new_e -= length * extra_retraction_speed # New retraction is by ratio of this travel move.
+ if f == "no f":
+ new_travel_move = "G1 X{travel_x} Y{travel_y} Z{travel_z} E{new_e}".format(travel_x = travel_x, travel_y = travel_y, travel_z = travel_z, new_e = new_e)
+ else:
+ new_travel_move = "G1 F{f} X{travel_x} Y{travel_y} Z{travel_z} E{new_e}".format(f = f, travel_x = travel_x, travel_y = travel_y, travel_z = travel_z, new_e = new_e)
+ lines[line_number + delta_line] = new_travel_move
- delta_line += 1
- dx = travel_x
- dy = travel_y
+ delta_line += 1
+ dx = travel_x
+ dy = travel_y
+ dz = travel_z
- current_e = new_e
+ current_e = new_e
new_layer = "\n".join(lines)
data[layer_number] = new_layer
diff --git a/plugins/PostProcessingPlugin/scripts/Stretch.py b/plugins/PostProcessingPlugin/scripts/Stretch.py
index 3899bd2c50..480ba60606 100644
--- a/plugins/PostProcessingPlugin/scripts/Stretch.py
+++ b/plugins/PostProcessingPlugin/scripts/Stretch.py
@@ -10,10 +10,10 @@ WARNING This script has never been tested with several extruders
from ..Script import Script
import numpy as np
from UM.Logger import Logger
-from UM.Application import Application
import re
from cura.Settings.ExtruderManager import ExtruderManager
+
def _getValue(line, key, default=None):
"""
Convenience function that finds the value in a line of g-code.
@@ -30,6 +30,7 @@ def _getValue(line, key, default=None):
return default
return float(number.group(0))
+
class GCodeStep():
"""
Class to store the current value of each G_Code parameter
@@ -85,7 +86,7 @@ class GCodeStep():
# Execution part of the stretch plugin
-class Stretcher():
+class Stretcher:
"""
Execution part of the stretch algorithm
"""
@@ -207,7 +208,6 @@ class Stretcher():
return False
return True # New sequence
-
def processLayer(self, layer_steps):
"""
Computes the new coordinates of g-code steps
@@ -291,7 +291,6 @@ class Stretcher():
else:
self.layergcode = self.layergcode + layer_steps[i].comment + "\n"
-
def workOnSequence(self, orig_seq, modif_seq):
"""
Computes new coordinates for a sequence