Revamped ChangeAtZ

Added an enabled flag, allowing users to enable/disable ChangeAtZ layers at will without removing them
Improved performance of GCodeCommand, deferred parsing of arguments to when it is first requested as opposed to all the time
Removed type hints because the supported python version in Cura is too low
This commit is contained in:
novamxd 2020-04-13 22:22:21 -05:00
parent 943d04a734
commit 8b206751f8

View file

@ -67,6 +67,12 @@ class ChangeAtZ(Script):
"metadata": {},
"version": 2,
"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.",
@ -345,6 +351,9 @@ class ChangeAtZ(Script):
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")
@ -421,10 +430,13 @@ class ChangeAtZ(Script):
class GCodeCommand:
# The GCode command itself (ex: G10)
Command: str = None,
Command = None,
# Contains any arguments passed to the command. The key is the argument name, the value is the value of the argument.
Arguments: Dict[str, any] = {}
Arguments = {}
# Contains the components of the command broken into pieces
Components = []
# Constructor. Sets up defaults
def __init__(self):
@ -446,7 +458,7 @@ class GCodeCommand:
line = re.sub(r";.*$", "", line)
# break into the individual components
command_pieces: List[str] = line.strip().split(" ")
command_pieces = line.strip().split(" ")
# our return command details
command = GCodeCommand()
@ -455,6 +467,9 @@ class GCodeCommand:
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]
@ -462,25 +477,6 @@ class GCodeCommand:
if len(command_pieces) == 1:
return None
# remove the command from the pieces
del command_pieces[0]
# iterate and index all of our parameters
for param in command_pieces:
# get the first character of the parameter, which is the name
param_name:str = param[0]
# get the value of the parameter (the rest of the string
param_value:str = None
# get our value if we have one
if len(param) > 1:
param_value = param[1:]
# index the argument
command.Arguments[param_name] = param_value
# return our indexed command
return command
@ -508,6 +504,9 @@ class GCodeCommand:
# 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
@ -590,6 +589,35 @@ class GCodeCommand:
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:
@ -606,52 +634,55 @@ class GCodeCommand:
class ChangeAtZProcessor:
# Holds our current height
CurrentZ: float = None
CurrentZ = None
# Holds our current layer number
CurrentLayer: int = None
CurrentLayer = None
# Indicates if we're only supposed to apply our settings to a single layer or multiple layers
IsApplyToSingleLayer: bool = False
IsApplyToSingleLayer = False
# Indicates if this should emit the changes as they happen to the LCD
IsDisplayingChangesToLcd: bool = False
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: bool = False
IsInsideTargetLayer = False
# Indicates if we have restored the previous values from before we started our pass
IsLastValuesRestored: bool = False
IsLastValuesRestored = False
# Indicates if the user has opted for linear move retractions or firmware retractions
IsLinearRetraction: bool = True
IsLinearRetraction = True
# Indicates if we're targetting by layer or height value
IsTargetByLayer: bool = True
IsTargetByLayer = True
# Indicates if we have injected our changed values for the given layer yet
IsTargetValuesInjected: bool = False
IsTargetValuesInjected = False
# Holds the last extrusion value, used with detecting when a retraction is made
LastE: float = None
LastE = None
# An index of our gcodes which we're monitoring
LastValues: Dict[str, any] = {}
LastValues = {}
# The detected layer height from the gcode
LayerHeight: float = None
LayerHeight = None
# The target layer
TargetLayer: int = None
TargetLayer = None
# Holds the values the user has requested to change
TargetValues: Dict[str, any] = {}
TargetValues = {}
# The target height in mm
TargetZ: float = None
TargetZ = None
# Used to track if we've been inside our target layer yet
WasInsideTargetLayer: bool = False
WasInsideTargetLayer = False
# boots up the class with defaults
def __init__(self):
@ -660,19 +691,23 @@ class ChangeAtZProcessor:
# 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: int = 0
index = 0
for active_layer in data:
# will hold our updated gcode
modified_gcode: str = ""
modified_gcode = ""
# mark all the defaults for deletion
active_layer = self.markChangesForDeletion(active_layer)
# break apart the layer into commands
lines: List[str] = active_layer.split("\n")
lines = active_layer.split("\n")
# evaluate each command individually
for line in lines:
@ -714,7 +749,7 @@ class ChangeAtZProcessor:
def getChangedLastValues(self) -> Dict[str, any]:
# capture the values that we've changed
changed: Dict[str, any] = {}
changed = {}
# for each of our target values, get the value to restore
# no point in restoring values we haven't changed
@ -738,7 +773,7 @@ class ChangeAtZProcessor:
return ""
# will hold all the default settings for the target layer
codes: List[str] = []
codes = []
# looking for wait for bed temp
if "bedTemp" in values:
@ -807,7 +842,7 @@ class ChangeAtZProcessor:
def getCodeFromValues(self, values: Dict[str, any]) -> str:
# will hold all the desired settings for the target layer
codes: List[str] = self.getCodeLinesFromValues(values)
codes = self.getCodeLinesFromValues(values)
# stop here if there are no values that require changing
if len(codes) == 0:
@ -820,7 +855,7 @@ class ChangeAtZProcessor:
def getCodeLinesFromValues(self, values: Dict[str, any]) -> List[str]:
# will hold all the default settings for the target layer
codes: List[str] = []
codes = []
# looking for wait for bed temp
if "bedTemp" in values:
@ -1020,7 +1055,7 @@ class ChangeAtZProcessor:
def processLine(self, line: str) -> str:
# used to change the given line of code
modified_gcode: str = ""
modified_gcode = ""
# track any values that we may be interested in
self.trackChangeableValues(line)
@ -1065,21 +1100,21 @@ class ChangeAtZProcessor:
line = self.getOriginalLine(line)
# get our command from the line
linear_command: Optional[GCodeCommand] = GCodeCommand.getLinearMoveCommand(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: float = linear_command.Arguments["F"]
x_coord: float = linear_command.Arguments["X"]
y_coord: float = linear_command.Arguments["Y"]
z_coord: float = linear_command.Arguments["Z"]
extrude_length: float = linear_command.Arguments["E"]
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: str = line
new_line = line
# handle retract length
new_line = self.processRetractLength(extrude_length, feed_rate, new_line, x_coord, y_coord, z_coord)
@ -1108,14 +1143,14 @@ class ChangeAtZProcessor:
return new_line
# get our requested print speed
print_speed: int = int(self.TargetValues["printspeed"])
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: float = GCodeCommand.getDirectArgumentAsFloat(new_line, "F") * (float(print_speed) / 100.0)
feed_rate = GCodeCommand.getDirectArgumentAsFloat(new_line, "F") * (float(print_speed) / 100.0)
# change our feed rate
return GCodeCommand.replaceDirectArgument(new_line, "F", feed_rate)
@ -1278,6 +1313,7 @@ class ChangeAtZProcessor:
self.IsTargetValuesInjected = False
self.IsLastValuesRestored = False
self.WasInsideTargetLayer = False
self.IsEnabled = True
# Sets the original GCODE line in a given GCODE command
@staticmethod
@ -1300,7 +1336,7 @@ class ChangeAtZProcessor:
line = line.replace(";RETRACTLENGTH ", "M207 S")
# get our gcode command
command: Optional[GCodeCommand] = GCodeCommand.getFromLine(line)
command = GCodeCommand.getFromLine(line)
# stop here if it isn't a G or M command
if command is None:
@ -1334,7 +1370,7 @@ class ChangeAtZProcessor:
if command.Command == "M104" or command.Command == "M109":
# get our tempurature
tempurature: float = command.getArgumentAsFloat("S")
tempurature = command.getArgumentAsFloat("S")
# don't bother if we don't have a tempurature
if tempurature is None:
@ -1367,7 +1403,7 @@ class ChangeAtZProcessor:
if command.Command == "M221":
# get our flow rate
tempurature: float = command.getArgumentAsFloat("S")
tempurature = command.getArgumentAsFloat("S")
# don't bother if we don't have a flow rate (for some reason)
if tempurature is None: