From 9861e450a054d08663778b9d4667ab665d4b9393 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 20 Jan 2023 17:34:39 +0100 Subject: [PATCH 1/3] Fix/rewrite for relative extrusion mode. For relative mode, not only needs the retractions created to be actually relative, but it also needs to compensate on the next G1 (as opposed to absolute mode, where you'd want to go to the same absolute E position after). Rather than massively complicating the already gnarly code (once it found a G1 retraction, it scanned forwards to find G0 statements (which it rewrote to G1), so it would go over those _again_ in the middle (layer) loop). While this worked for absolute mode, but would be a nightmare to make work for relative mode as-is (if only because the compensation could _also_ potentially involve keeping track of things over the outer loop). As a bonus I think the resulting code is actually easier to read. part of CURA-10092 -- should fix #14100 --- .../scripts/RetractContinue.py | 125 +++++++++++------- 1 file changed, 79 insertions(+), 46 deletions(-) diff --git a/plugins/PostProcessingPlugin/scripts/RetractContinue.py b/plugins/PostProcessingPlugin/scripts/RetractContinue.py index 3e095bd395..349c6f20aa 100644 --- a/plugins/PostProcessingPlugin/scripts/RetractContinue.py +++ b/plugins/PostProcessingPlugin/scripts/RetractContinue.py @@ -1,10 +1,11 @@ -# Copyright (c) 2019 Ultimaker B.V. +# Copyright (c) 2023 UltiMaker B.V. # The PostProcessingPlugin is released under the terms of the AGPLv3 or higher. -import math - from ..Script import Script +from UM.Application import Application # To get current absolute/relative setting. +from UM.Math.Vector import Vector + class RetractContinue(Script): """Continues retracting during all travel moves.""" @@ -27,58 +28,90 @@ class RetractContinue(Script): } }""" + def _getTravelMove(self, travel_move, default_pos): + travel = Vector( + self.getValue(travel_move, "X", default_pos.x), + self.getValue(travel_move, "Y", default_pos.y), + self.getValue(travel_move, "Z", default_pos.z) + ) + f = self.getValue(travel_move, "F", -1.0) + return travel, f + + def _travelMoveString(self, travel, f, out_e): + # Note that only G1 moves are written, since extrusion is included. + if f <= 0.0: + return f"G1 X{travel.x:.5f} Y{travel.y:.5f} Z{travel.z:.5f} E{out_e:.5f}" + else: + return f"G1 F{f:.5f} X{travel.x:.5f} Y{travel.y:.5f} Z{travel.z:.5f} E{out_e:.5f}" + def execute(self, data): - current_e = 0 - current_x = 0 - current_y = 0 - current_z = 0 + current_e = 0.0 + to_compensate = 0 # Used when extrusion mode is relative. + is_active = False # Whether retract-continue is in effect. + + current_pos = Vector(0.0, 0.0, 0.0) + last_pos = Vector(0.0, 0.0, 0.0) + extra_retraction_speed = self.getSettingValueByKey("extra_retraction_speed") + relative_extrusion = Application.getInstance().getGlobalContainerStack().getProperty( + "relative_extrusion", "value" + ) 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,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 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 - 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 - dz = travel_z + # Focus on move-type lines. + code_g = self.getValue(line, "G") + if code_g not in [0, 1]: + continue - current_e = new_e + # Track X,Y,Z location. + last_pos = last_pos.set(current_pos.x, current_pos.y, current_pos.z) + current_pos = current_pos.set( + self.getValue(line, "X", current_pos.x), + self.getValue(line, "Y", current_pos.y), + self.getValue(line, "Z", current_pos.z) + ) + + # Track extrusion 'axis' position. + last_e = current_e + e_value = self.getValue(line, "E") + if e_value: + current_e = (current_e if relative_extrusion else 0) + e_value + + # Handle lines: Detect retractions and compensate relative if G1, potential retract-continue if G0. + if code_g == 1: + if last_e > (current_e + 0.0001): # Account for floating point inaccuracies. + + # There is a retraction, each following G0 command needs to continue the retraction. + is_active = True + continue + + elif relative_extrusion and is_active: + + # If 'relative', the first G1 command after the total retraction will have to compensate more. + travel, f = self._getTravelMove(lines[line_number], current_pos) + lines[line_number] = self._travelMoveString(travel, f, to_compensate + e_value) + to_compensate = 0.0 + + # There is no retraction (see continue in the retract-clause) and everything else has been handled. + is_active = False + + elif code_g == 0: + if not is_active: + continue + + # The retract-continue is active, so each G0 until the next extrusion needs to continue retraction. + travel, f = self._getTravelMove(lines[line_number], current_pos) + travel_length = (current_pos - last_pos).length() + extra_retract = travel_length * extra_retraction_speed + new_e = (0 if relative_extrusion else current_e) - extra_retract + to_compensate += extra_retract + current_e -= extra_retract + lines[line_number] = self._travelMoveString(travel, f, new_e) new_layer = "\n".join(lines) data[layer_number] = new_layer - return data \ No newline at end of file + return data From e2f7ef8b8b508493fc37561cc47efe38be50a610 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 20 Jan 2023 17:58:42 +0100 Subject: [PATCH 2/3] Add typing to Retract-Continue script. part of CURA-10092 --- .../PostProcessingPlugin/scripts/RetractContinue.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/PostProcessingPlugin/scripts/RetractContinue.py b/plugins/PostProcessingPlugin/scripts/RetractContinue.py index 349c6f20aa..fb41c4d593 100644 --- a/plugins/PostProcessingPlugin/scripts/RetractContinue.py +++ b/plugins/PostProcessingPlugin/scripts/RetractContinue.py @@ -6,11 +6,13 @@ from ..Script import Script from UM.Application import Application # To get current absolute/relative setting. from UM.Math.Vector import Vector +from typing import List, Tuple + class RetractContinue(Script): """Continues retracting during all travel moves.""" - def getSettingDataString(self): + def getSettingDataString(self) -> str: return """{ "name": "Retract Continue", "key": "RetractContinue", @@ -28,7 +30,7 @@ class RetractContinue(Script): } }""" - def _getTravelMove(self, travel_move, default_pos): + def _getTravelMove(self, travel_move: str, default_pos: Vector) -> Tuple[Vector, float]: travel = Vector( self.getValue(travel_move, "X", default_pos.x), self.getValue(travel_move, "Y", default_pos.y), @@ -37,14 +39,14 @@ class RetractContinue(Script): f = self.getValue(travel_move, "F", -1.0) return travel, f - def _travelMoveString(self, travel, f, out_e): + def _travelMoveString(self, travel: Vector, f: float, out_e: float) -> str: # Note that only G1 moves are written, since extrusion is included. if f <= 0.0: return f"G1 X{travel.x:.5f} Y{travel.y:.5f} Z{travel.z:.5f} E{out_e:.5f}" else: return f"G1 F{f:.5f} X{travel.x:.5f} Y{travel.y:.5f} Z{travel.z:.5f} E{out_e:.5f}" - def execute(self, data): + def execute(self, data: List[str]) -> List[str]: current_e = 0.0 to_compensate = 0 # Used when extrusion mode is relative. is_active = False # Whether retract-continue is in effect. From 7e695908cfd5cc2d575138cb9310e409cf5f5fd1 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 25 Jan 2023 11:44:46 +0100 Subject: [PATCH 3/3] Rename confusing parameter. part of CURA-10092 --- plugins/PostProcessingPlugin/scripts/RetractContinue.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/PostProcessingPlugin/scripts/RetractContinue.py b/plugins/PostProcessingPlugin/scripts/RetractContinue.py index fb41c4d593..b5ea4d4eda 100644 --- a/plugins/PostProcessingPlugin/scripts/RetractContinue.py +++ b/plugins/PostProcessingPlugin/scripts/RetractContinue.py @@ -39,12 +39,12 @@ class RetractContinue(Script): f = self.getValue(travel_move, "F", -1.0) return travel, f - def _travelMoveString(self, travel: Vector, f: float, out_e: float) -> str: + def _travelMoveString(self, travel: Vector, f: float, e: float) -> str: # Note that only G1 moves are written, since extrusion is included. if f <= 0.0: - return f"G1 X{travel.x:.5f} Y{travel.y:.5f} Z{travel.z:.5f} E{out_e:.5f}" + return f"G1 X{travel.x:.5f} Y{travel.y:.5f} Z{travel.z:.5f} E{e:.5f}" else: - return f"G1 F{f:.5f} X{travel.x:.5f} Y{travel.y:.5f} Z{travel.z:.5f} E{out_e:.5f}" + return f"G1 F{f:.5f} X{travel.x:.5f} Y{travel.y:.5f} Z{travel.z:.5f} E{e:.5f}" def execute(self, data: List[str]) -> List[str]: current_e = 0.0