feat: add support for retraction and Z-lift in Pather and Printer Intrinsics
This commit is contained in:
parent
76cf856155
commit
21dde0c4a5
6 changed files with 129 additions and 10 deletions
|
|
@ -17,12 +17,16 @@ class SliceIntrinsics:
|
||||||
bed_y: float
|
bed_y: float
|
||||||
feedrate: float
|
feedrate: float
|
||||||
extrusion_rate: float
|
extrusion_rate: float
|
||||||
|
retraction: float
|
||||||
|
lift: float
|
||||||
|
|
||||||
def __init__(self, bed_x=250.0, bed_y=250.0, feedrate=1200.0, extrusion_rate=0.017):
|
def __init__(self, bed_x: float = 250.0, bed_y: float = 250.0, feedrate: float = 1200.0, extrusion_rate: float = 0.017, retraction: float = 0.0, lift: float = 0.0):
|
||||||
self.bed_x = bed_x
|
self.bed_x = bed_x
|
||||||
self.bed_y = bed_y
|
self.bed_y = bed_y
|
||||||
self.feedrate = feedrate
|
self.feedrate = feedrate
|
||||||
self.extrusion_rate = extrusion_rate
|
self.extrusion_rate = extrusion_rate
|
||||||
|
self.retraction = retraction
|
||||||
|
self.lift = lift
|
||||||
|
|
||||||
class InputNode(SliceNode):
|
class InputNode(SliceNode):
|
||||||
input_type: str = ""
|
input_type: str = ""
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,8 @@ class SLICENODE_PathsToGcode(SliceNode):
|
||||||
pather.bed_center_y = intrinsics.bed_y / 2.0
|
pather.bed_center_y = intrinsics.bed_y / 2.0
|
||||||
pather.feedrate = intrinsics.feedrate
|
pather.feedrate = intrinsics.feedrate
|
||||||
pather.extrusion_rate = intrinsics.extrusion_rate
|
pather.extrusion_rate = intrinsics.extrusion_rate
|
||||||
|
pather.retraction = intrinsics.retraction
|
||||||
|
pather.lift = intrinsics.lift
|
||||||
|
|
||||||
gcode: str = pather.to_gcode(include_header=self.use_header)
|
gcode: str = pather.to_gcode(include_header=self.use_header)
|
||||||
return gcode
|
return gcode
|
||||||
|
|
|
||||||
|
|
@ -226,11 +226,15 @@ class SLICENODE_InputIntrinsics(SliceNode):
|
||||||
self.add_input("SLICESOCK_Float", "Bed Y").value = 210.0
|
self.add_input("SLICESOCK_Float", "Bed Y").value = 210.0
|
||||||
self.add_input("SLICESOCK_Float", "Feedrate").value = 1200.0
|
self.add_input("SLICESOCK_Float", "Feedrate").value = 1200.0
|
||||||
self.add_input("SLICESOCK_Float", "Extrusion Rate").value = 0.035
|
self.add_input("SLICESOCK_Float", "Extrusion Rate").value = 0.035
|
||||||
|
self.add_input("SLICESOCK_Float", "Retraction").value = 0.0
|
||||||
|
self.add_input("SLICESOCK_Float", "Lift").value = 0.0
|
||||||
|
|
||||||
def evaluate_output(self, socket_name: str, **_kwargs) -> SliceIntrinsics:
|
def evaluate_output(self, socket_name: str, **_kwargs) -> SliceIntrinsics:
|
||||||
return SliceIntrinsics(
|
return SliceIntrinsics(
|
||||||
bed_x=self.resolve("Bed X"),
|
bed_x=self.resolve("Bed X"),
|
||||||
bed_y=self.resolve("Bed Y"),
|
bed_y=self.resolve("Bed Y"),
|
||||||
feedrate=self.resolve("Feedrate"),
|
feedrate=self.resolve("Feedrate"),
|
||||||
extrusion_rate=self.resolve("Extrusion Rate")
|
extrusion_rate=self.resolve("Extrusion Rate"),
|
||||||
|
retraction=self.resolve("Retraction"),
|
||||||
|
lift=self.resolve("Lift")
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1108,6 +1108,8 @@ class Pather(object):
|
||||||
self.bed_center_y: float = 250.
|
self.bed_center_y: float = 250.
|
||||||
self.feedrate: float = 1200.
|
self.feedrate: float = 1200.
|
||||||
self.extrusion_rate: float = 0.017
|
self.extrusion_rate: float = 0.017
|
||||||
|
self.retraction: float = 0.0
|
||||||
|
self.lift: float = 0.0
|
||||||
|
|
||||||
def to_gcode(self, include_header: bool = True) -> str:
|
def to_gcode(self, include_header: bool = True) -> str:
|
||||||
if not self.paths:
|
if not self.paths:
|
||||||
|
|
@ -1153,8 +1155,21 @@ class Pather(object):
|
||||||
]
|
]
|
||||||
gcode += header
|
gcode += header
|
||||||
|
|
||||||
|
# Safely parse retraction and lift fields.
|
||||||
|
# If they cannot be parsed or value is zero, we skip them.
|
||||||
|
try:
|
||||||
|
retraction_val = float(self.retraction) if self.retraction is not None else 0.0
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
retraction_val = 0.0
|
||||||
|
|
||||||
|
try:
|
||||||
|
lift_val = float(self.lift) if self.lift is not None else 0.0
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
lift_val = 0.0
|
||||||
|
|
||||||
last_z = -1.0
|
last_z = -1.0
|
||||||
max_z = 0
|
max_z = 0
|
||||||
|
last_nozzle_z = 0.0
|
||||||
|
|
||||||
for i, line in enumerate(centered_paths):
|
for i, line in enumerate(centered_paths):
|
||||||
coords = line.tolist()
|
coords = line.tolist()
|
||||||
|
|
@ -1167,15 +1182,51 @@ class Pather(object):
|
||||||
|
|
||||||
x0, y0, z0 = coords[0]
|
x0, y0, z0 = coords[0]
|
||||||
|
|
||||||
|
active_feedrate = self.feedrate
|
||||||
|
if i > 0:
|
||||||
|
# 1. Retract if retraction is enabled
|
||||||
|
if retraction_val > 0.0:
|
||||||
|
gcode.append(f"G1 E-{retraction_val:.5f} F1800")
|
||||||
|
active_feedrate = 1800.0
|
||||||
|
|
||||||
|
# 2. Lift Z if lift is enabled
|
||||||
|
if lift_val > 0.0:
|
||||||
|
gcode.append(f"G0 Z{last_nozzle_z + lift_val:.3f} F720")
|
||||||
|
active_feedrate = 720.0
|
||||||
|
|
||||||
|
# 3. Travel to the start of path `i` (x0, y0) in XY
|
||||||
|
travel_fr = f_rate[0] if f_rate[0] > 0 else self.feedrate
|
||||||
|
gcode.append(f"G0 X{x0:.3f} Y{y0:.3f} F{travel_fr}")
|
||||||
|
active_feedrate = travel_fr
|
||||||
|
|
||||||
|
# 4. Handle Z alignment (layer change and/or un-lift)
|
||||||
if abs(z0 - last_z) > 0.001:
|
if abs(z0 - last_z) > 0.001:
|
||||||
gcode.append(f";BEFORE_LAYER_CHANGE")
|
gcode.append(f";BEFORE_LAYER_CHANGE")
|
||||||
gcode.append(f"G0 Z{z0:.3f} F720")
|
gcode.append(f"G0 Z{z0:.3f} F720")
|
||||||
gcode.append(f";AFTER_LAYER_CHANGE")
|
gcode.append(f";AFTER_LAYER_CHANGE")
|
||||||
last_z = z0
|
last_z = z0
|
||||||
|
active_feedrate = 720.0
|
||||||
|
elif lift_val > 0.0:
|
||||||
|
gcode.append(f"G0 Z{z0:.3f} F720")
|
||||||
|
active_feedrate = 720.0
|
||||||
|
|
||||||
|
# 5. Un-retract if retraction was enabled
|
||||||
|
if retraction_val > 0.0:
|
||||||
|
gcode.append(f"G1 E{retraction_val:.5f} F1800")
|
||||||
|
active_feedrate = 1800.0
|
||||||
|
else:
|
||||||
|
if abs(z0 - last_z) > 0.001:
|
||||||
|
gcode.append(f";BEFORE_LAYER_CHANGE")
|
||||||
|
gcode.append(f"G0 Z{z0:.3f} F720")
|
||||||
|
gcode.append(f";AFTER_LAYER_CHANGE")
|
||||||
|
last_z = z0
|
||||||
|
active_feedrate = 720.0
|
||||||
|
|
||||||
old_fr = f_rate[0] if f_rate[0] > 0 else self.feedrate
|
old_fr = f_rate[0] if f_rate[0] > 0 else self.feedrate
|
||||||
gcode.append(f"G0 X{x0:.3f} Y{y0:.3f} Z{z0:.3f} F{old_fr}")
|
gcode.append(f"G0 X{x0:.3f} Y{y0:.3f} Z{z0:.3f} F{old_fr}")
|
||||||
|
active_feedrate = old_fr
|
||||||
|
|
||||||
|
old_fr = active_feedrate
|
||||||
old_x, old_y, old_z = x0, y0, z0
|
old_x, old_y, old_z = x0, y0, z0
|
||||||
old_extras = {prefix: None for prefix in extras}
|
old_extras = {prefix: None for prefix in extras}
|
||||||
for j, (p1, p2) in enumerate(zip(coords, coords[1:])):
|
for j, (p1, p2) in enumerate(zip(coords, coords[1:])):
|
||||||
|
|
@ -1207,4 +1258,6 @@ class Pather(object):
|
||||||
max_z = max(max_z, z)
|
max_z = max(max_z, z)
|
||||||
old_fr = fr
|
old_fr = fr
|
||||||
|
|
||||||
|
last_nozzle_z = coords[-1][2]
|
||||||
|
|
||||||
return "\n".join(gcode)
|
return "\n".join(gcode)
|
||||||
|
|
@ -48,7 +48,7 @@ All nodes are found in the **Add (Shift+A)** menu under the **SliceNodes** categ
|
||||||
- **Integer**: Single integer input.
|
- **Integer**: Single integer input.
|
||||||
- **String**: Single string/path input.
|
- **String**: Single string/path input.
|
||||||
- **Rotation**: Input for Euler rotation vectors.
|
- **Rotation**: Input for Euler rotation vectors.
|
||||||
- **Printer Intrinsics**: Configures bed size and extrusion rates.
|
- **Printer Intrinsics**: Configures bed size, extrusion rates, retraction, and Z-lift.
|
||||||
- **Gcode Block**: Manual text area for entering raw G-code.
|
- **Gcode Block**: Manual text area for entering raw G-code.
|
||||||
|
|
||||||
### Geometry Processing
|
### Geometry Processing
|
||||||
|
|
|
||||||
|
|
@ -244,5 +244,61 @@ class TestSlice(unittest.TestCase):
|
||||||
self.assertAlmostEqual(area, 25.0, places=3)
|
self.assertAlmostEqual(area, 25.0, places=3)
|
||||||
self.assertTrue(np.allclose(mask_path[:, 2], 3.0))
|
self.assertTrue(np.allclose(mask_path[:, 2], 3.0))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_pather_retraction_lift(self):
|
||||||
|
from NodeSlicer.core.slice import Pather
|
||||||
|
|
||||||
|
path1 = np.array([
|
||||||
|
[10.0, 10.0, 0.2],
|
||||||
|
[20.0, 10.0, 0.2]
|
||||||
|
])
|
||||||
|
path2 = np.array([
|
||||||
|
[30.0, 30.0, 0.2],
|
||||||
|
[40.0, 30.0, 0.2]
|
||||||
|
])
|
||||||
|
|
||||||
|
# Test default (no retraction or lift)
|
||||||
|
pather = Pather([path1, path2])
|
||||||
|
pather.retraction = 0.0
|
||||||
|
pather.lift = 0.0
|
||||||
|
gcode = pather.to_gcode(include_header=False)
|
||||||
|
self.assertNotIn("E-", gcode)
|
||||||
|
self.assertNotIn("Z0.700", gcode) # no lift
|
||||||
|
|
||||||
|
# Test with retraction and lift
|
||||||
|
pather = Pather([path1, path2])
|
||||||
|
pather.retraction = 1.2
|
||||||
|
pather.lift = 0.5
|
||||||
|
gcode = pather.to_gcode(include_header=False)
|
||||||
|
|
||||||
|
# Retraction should be present before travel
|
||||||
|
self.assertIn("G1 E-1.20000 F1800", gcode)
|
||||||
|
# Lift should be present: 0.2 (last_nozzle_z) + 0.5 (lift) = 0.7
|
||||||
|
self.assertIn("G0 Z0.700 F720", gcode)
|
||||||
|
# Un-lift back to 0.2 should be present
|
||||||
|
self.assertIn("G0 Z0.200 F720", gcode)
|
||||||
|
# Un-retraction should be present
|
||||||
|
self.assertIn("G1 E1.20000 F1800", gcode)
|
||||||
|
|
||||||
|
# Test with lift only (no retraction)
|
||||||
|
pather = Pather([path1, path2])
|
||||||
|
pather.retraction = 0.0
|
||||||
|
pather.lift = 0.5
|
||||||
|
gcode = pather.to_gcode(include_header=False)
|
||||||
|
self.assertNotIn("E-", gcode)
|
||||||
|
# Lift should be present: 0.2 (last_nozzle_z) + 0.5 (lift) = 0.7
|
||||||
|
self.assertIn("G0 Z0.700 F720", gcode)
|
||||||
|
# Un-lift back to 0.2 should be present
|
||||||
|
self.assertIn("G0 Z0.200 F720", gcode)
|
||||||
|
|
||||||
|
# Test with unparsable retraction and lift fields
|
||||||
|
pather = Pather([path1, path2])
|
||||||
|
pather.retraction = "invalid"
|
||||||
|
pather.lift = None
|
||||||
|
gcode = pather.to_gcode(include_header=False)
|
||||||
|
self.assertNotIn("E-", gcode)
|
||||||
|
self.assertNotIn("Z0.700", gcode)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue