wipe tower
This commit is contained in:
parent
0e14fa19f7
commit
8cff75d569
6 changed files with 92 additions and 3 deletions
|
|
@ -2,6 +2,7 @@ from __future__ import annotations
|
||||||
from typing import Any
|
from typing import Any
|
||||||
import bpy
|
import bpy
|
||||||
import os
|
import os
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
from ..bpy_ns_types import NfStringProperty, notify_tree_changed, NfEnumProperty
|
from ..bpy_ns_types import NfStringProperty, notify_tree_changed, NfEnumProperty
|
||||||
from ....registry import register_class
|
from ....registry import register_class
|
||||||
|
|
@ -76,6 +77,32 @@ class SLICENODE_CollectionObjects(SliceNode):
|
||||||
return bpy.data.objects[socket_name]
|
return bpy.data.objects[socket_name]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@register_class
|
||||||
|
class SLICENODE_WipeTower(SliceNode):
|
||||||
|
bl_label = "Wipe Tower"
|
||||||
|
|
||||||
|
def init(self, context):
|
||||||
|
self.add_input("SLICESOCK_Float", "X Position").value = 170.0
|
||||||
|
self.add_input("SLICESOCK_Float", "Y Position").value = 140.0
|
||||||
|
self.add_output("SLICESOCK_SettingsRo", "Settings")
|
||||||
|
|
||||||
|
def evaluate_output(self, socket_name: str, **_kwargs) -> Any:
|
||||||
|
if socket_name != "Settings":
|
||||||
|
return {}
|
||||||
|
|
||||||
|
raw_x = self.resolve("X Position")
|
||||||
|
raw_y = self.resolve("Y Position")
|
||||||
|
|
||||||
|
if isinstance(raw_x, (list, tuple, np.ndarray)) and len(raw_x) >= 2:
|
||||||
|
pos_x, pos_y = float(raw_x[0]), float(raw_x[1])
|
||||||
|
else:
|
||||||
|
pos_x = float(raw_x) if raw_x is not None else 0.0
|
||||||
|
pos_y = float(raw_y) if raw_y is not None else 0.0
|
||||||
|
|
||||||
|
return {
|
||||||
|
"wipe_tower_position": (pos_x, pos_y)
|
||||||
|
}
|
||||||
|
|
||||||
@register_class
|
@register_class
|
||||||
class SLICENODE_InputSettings(InputNode):
|
class SLICENODE_InputSettings(InputNode):
|
||||||
input_type = "Settings"
|
input_type = "Settings"
|
||||||
|
|
|
||||||
|
|
@ -220,8 +220,20 @@ class SLICENODE_PrusaslicerSlice(SliceNode):
|
||||||
offset = np.array([bed_center[0], bed_center[1], a_z]) - np.array([slicing_group.center_xy[0], slicing_group.center_xy[1], 0])
|
offset = np.array([bed_center[0], bed_center[1], a_z]) - np.array([slicing_group.center_xy[0], slicing_group.center_xy[1], 0])
|
||||||
slicing_group.offset(offset)
|
slicing_group.offset(offset)
|
||||||
|
|
||||||
# Prepare overrides for generate_conf_writer
|
# Check for wipe_tower_position override
|
||||||
writer_overrides = {k: {'value': v} for k, v in overrides.items()}
|
if overrides and "wipe_tower_position" in overrides:
|
||||||
|
wipe_tower_pos = overrides["wipe_tower_position"]
|
||||||
|
try:
|
||||||
|
wx = float(wipe_tower_pos[0])
|
||||||
|
wy = float(wipe_tower_pos[1])
|
||||||
|
slicing_group.wipe_tower_xy = np.array([wx, wy])
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Prepare overrides for generate_conf_writer (excluding wipe_tower_position)
|
||||||
|
writer_overrides = {
|
||||||
|
k: {'value': v} for k, v in (overrides or {}).items() if k != "wipe_tower_position"
|
||||||
|
}
|
||||||
|
|
||||||
# Generate the ConfigWriter
|
# Generate the ConfigWriter
|
||||||
from ....infra.prusaslicer_profiles import ConfigWriter
|
from ....infra.prusaslicer_profiles import ConfigWriter
|
||||||
|
|
|
||||||
|
|
@ -481,6 +481,7 @@ class SLICENODE_MT_DataInput(Menu):
|
||||||
layout.operator("node.add_node", text="Object").type = "SLICENODE_InputObject"
|
layout.operator("node.add_node", text="Object").type = "SLICENODE_InputObject"
|
||||||
layout.operator("node.add_node", text="Collection Objects").type = "SLICENODE_CollectionObjects"
|
layout.operator("node.add_node", text="Collection Objects").type = "SLICENODE_CollectionObjects"
|
||||||
layout.operator("node.add_node", text="Settings").type = "SLICENODE_InputSettings"
|
layout.operator("node.add_node", text="Settings").type = "SLICENODE_InputSettings"
|
||||||
|
layout.operator("node.add_node", text="Wipe Tower").type = "SLICENODE_WipeTower"
|
||||||
layout.operator("node.add_node", text="PrusaSlicer Config").type = "SLICENODE_PrusaslicerConf"
|
layout.operator("node.add_node", text="PrusaSlicer Config").type = "SLICENODE_PrusaslicerConf"
|
||||||
layout.separator()
|
layout.separator()
|
||||||
layout.operator("node.add_node", text="Printer").type = "SLICENODE_InputPrinter"
|
layout.operator("node.add_node", text="Printer").type = "SLICENODE_InputPrinter"
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,10 @@ All nodes are found in the **Add (Shift+A)** menu under the **SliceNodes** categ
|
||||||
- **Float**: Single float input.
|
- **Float**: Single float input.
|
||||||
- **Integer**: Single integer input.
|
- **Integer**: Single integer input.
|
||||||
- **String**: Single string/path input.
|
- **String**: Single string/path input.
|
||||||
- **Rotation**: Input for Euler rotation vectors.
|
- **Settings**: Read-only/write configuration settings dictionary.
|
||||||
|
- **Wipe Tower**: Configures the Wipe Tower printbed position (`wipe_tower_position`) passed to 3MF metadata (`Prusa_Slicer_wipe_tower_information.xml`) without injecting into profile INI settings.
|
||||||
|
- **Position X**: X coordinate of the wipe tower on the printbed.
|
||||||
|
- **Position Y**: Y coordinate of the wipe tower on the printbed.
|
||||||
- **Printer Intrinsics**: Configures bed size, extrusion/travel rates, retraction, and Z-lift.
|
- **Printer Intrinsics**: Configures bed size, extrusion/travel 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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,8 @@ class ConfigWriter:
|
||||||
def write_ini_3mf(self, config_local_path: Path):
|
def write_ini_3mf(self, config_local_path: Path):
|
||||||
with open(config_local_path, 'w') as file:
|
with open(config_local_path, 'w') as file:
|
||||||
for key, val in dict(sorted(self.config_dict.items())).items():
|
for key, val in dict(sorted(self.config_dict.items())).items():
|
||||||
|
if key == "wipe_tower_position":
|
||||||
|
continue
|
||||||
file.write(f"; {key} = {val}\n")
|
file.write(f"; {key} = {val}\n")
|
||||||
|
|
||||||
def get(self, key: str, default: str = "") -> str | list[str]:
|
def get(self, key: str, default: str = "") -> str | list[str]:
|
||||||
|
|
|
||||||
44
tests/test_wipe_tower.py
Normal file
44
tests/test_wipe_tower.py
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import unittest
|
||||||
|
import numpy as np
|
||||||
|
from pathlib import Path
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
sys.path.append(str(PROJECT_ROOT))
|
||||||
|
|
||||||
|
from NodeSlicer.core.nodeslicer.nodes.inputs import SLICENODE_WipeTower
|
||||||
|
from NodeSlicer.infra.prusaslicer_profiles.models import ConfigWriter
|
||||||
|
from NodeSlicer.infra.blender.blender_mesh_capture import SlicingGroup
|
||||||
|
|
||||||
|
class TestWipeTowerNode(unittest.TestCase):
|
||||||
|
def test_wipe_tower_node_output(self):
|
||||||
|
node = SLICENODE_WipeTower()
|
||||||
|
node.resolve = lambda key: 180.5 if key == "Position X" else 135.2
|
||||||
|
|
||||||
|
res = node.evaluate_output("Settings")
|
||||||
|
self.assertIn("wipe_tower_position", res)
|
||||||
|
self.assertEqual(res["wipe_tower_position"], (180.5, 135.2))
|
||||||
|
|
||||||
|
def test_config_writer_excludes_wipe_tower_position(self):
|
||||||
|
conf = {
|
||||||
|
"layer_height": "0.2",
|
||||||
|
"wipe_tower_position": "(180.5, 135.2)",
|
||||||
|
"nozzle_diameter": "0.4"
|
||||||
|
}
|
||||||
|
writer = ConfigWriter(conf)
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
ini_path = Path(temp_dir) / "Slic3r_PE.config"
|
||||||
|
writer.write_ini_3mf(ini_path)
|
||||||
|
|
||||||
|
with open(ini_path, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
self.assertIn("; layer_height = 0.2", content)
|
||||||
|
self.assertIn("; nozzle_diameter = 0.4", content)
|
||||||
|
self.assertNotIn("wipe_tower_position", content)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Loading…
Add table
Reference in a new issue