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
This commit is contained in:
Remco Burema 2023-01-20 17:34:39 +01:00
parent 7ccb361b8f
commit 9861e450a0

View file

@ -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
return data