offset
This commit is contained in:
parent
8cff75d569
commit
e3e2335754
9 changed files with 71 additions and 7 deletions
|
|
@ -103,6 +103,37 @@ class SLICENODE_WipeTower(SliceNode):
|
|||
"wipe_tower_position": (pos_x, pos_y)
|
||||
}
|
||||
|
||||
@register_class
|
||||
class SLICENODE_OffsetObject(SliceNode):
|
||||
bl_label = "Offset Object"
|
||||
|
||||
def init(self, context):
|
||||
self.add_input("SLICESOCK_Float", "X Offset").value = 0.0
|
||||
self.add_input("SLICESOCK_Float", "Y Offset").value = 0.0
|
||||
self.add_input("SLICESOCK_Float", "Z Offset").value = 0.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 Offset")
|
||||
raw_y = self.resolve("Y Offset")
|
||||
raw_z = self.resolve("Z Offset")
|
||||
|
||||
if isinstance(raw_x, (list, tuple, np.ndarray)) and len(raw_x) >= 2:
|
||||
ox = float(raw_x[0])
|
||||
oy = float(raw_x[1])
|
||||
oz = float(raw_x[2]) if len(raw_x) >= 3 else (float(raw_z) if raw_z is not None else 0.0)
|
||||
else:
|
||||
ox = float(raw_x) if raw_x is not None else 0.0
|
||||
oy = float(raw_y) if raw_y is not None else 0.0
|
||||
oz = float(raw_z) if raw_z is not None else 0.0
|
||||
|
||||
return {
|
||||
"offset_object": (ox, oy, oz)
|
||||
}
|
||||
|
||||
@register_class
|
||||
class SLICENODE_InputSettings(InputNode):
|
||||
input_type = "Settings"
|
||||
|
|
|
|||
|
|
@ -427,6 +427,7 @@ def _copy_slicing_group(sg: SlicingGroup) -> SlicingGroup:
|
|||
new_sg.collections = {k: _copy_slicing_collection(sc) for k, sc in sg.collections.items()}
|
||||
new_sg.wipe_tower_xy = sg.wipe_tower_xy.copy()
|
||||
new_sg.wipe_tower_rotation_deg = sg.wipe_tower_rotation_deg
|
||||
new_sg.object_offset = sg.object_offset.copy()
|
||||
return new_sg
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -230,9 +230,20 @@ class SLICENODE_PrusaslicerSlice(SliceNode):
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
# Prepare overrides for generate_conf_writer (excluding wipe_tower_position)
|
||||
# Check for offset_object override
|
||||
if overrides and "offset_object" in overrides:
|
||||
obj_offset = overrides["offset_object"]
|
||||
try:
|
||||
ox = float(obj_offset[0])
|
||||
oy = float(obj_offset[1])
|
||||
oz = float(obj_offset[2]) if len(obj_offset) > 2 else 0.0
|
||||
slicing_group.object_offset = np.array([ox, oy, oz])
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Prepare overrides for generate_conf_writer (excluding wipe_tower_position & offset_object)
|
||||
writer_overrides = {
|
||||
k: {'value': v} for k, v in (overrides or {}).items() if k != "wipe_tower_position"
|
||||
k: {'value': v} for k, v in (overrides or {}).items() if k not in ("wipe_tower_position", "offset_object")
|
||||
}
|
||||
|
||||
# Generate the ConfigWriter
|
||||
|
|
|
|||
|
|
@ -482,6 +482,7 @@ class SLICENODE_MT_DataInput(Menu):
|
|||
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="Wipe Tower").type = "SLICENODE_WipeTower"
|
||||
layout.operator("node.add_node", text="Offset Object").type = "SLICENODE_OffsetObject"
|
||||
layout.operator("node.add_node", text="PrusaSlicer Config").type = "SLICENODE_PrusaslicerConf"
|
||||
layout.separator()
|
||||
layout.operator("node.add_node", text="Printer").type = "SLICENODE_InputPrinter"
|
||||
|
|
|
|||
|
|
@ -51,6 +51,10 @@ All nodes are found in the **Add (Shift+A)** menu under the **SliceNodes** categ
|
|||
- **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.
|
||||
- **Offset Object**: Configures the object placement transform offset (`offset_object`) applied to `<item transform="...">` inside 3MF model build data (`3D/3dmodel.model`) without injecting into profile INI settings.
|
||||
- **Offset X**: X translation offset for the build item.
|
||||
- **Offset Y**: Y translation offset for the build item.
|
||||
- **Offset Z**: Z translation offset for the build item.
|
||||
- **Printer Intrinsics**: Configures bed size, extrusion/travel rates, retraction, and Z-lift.
|
||||
- **Gcode Block**: Manual text area for entering raw G-code.
|
||||
|
||||
|
|
|
|||
|
|
@ -130,9 +130,11 @@ def write_model_xml(group: SlicingGroup, filename: str):
|
|||
|
||||
# Write build element
|
||||
file.write(f' <build>\n')
|
||||
ox, oy, oz = group.object_offset[0], group.object_offset[1], group.object_offset[2]
|
||||
transform_str = f"1 0 0 0 1 0 0 0 1 {ox} {oy} {oz}"
|
||||
for i, k in enumerate(valid_collections):
|
||||
if not valid_collections[k].unique_verts[0].size: continue
|
||||
file.writelines([f' <item objectid="{str(i+1)}" transform="1 0 0 0 1 0 0 0 1 0 0 0" printable="1" />\n' ])
|
||||
file.writelines([f' <item objectid="{str(i+1)}" transform="{transform_str}" printable="1" />\n' ])
|
||||
file.write(f' </build>\n')
|
||||
|
||||
# Close the model tag
|
||||
|
|
|
|||
|
|
@ -385,8 +385,10 @@ class SlicingGroup():
|
|||
# Metadata
|
||||
wipe_tower_xy: NDArray = np.array([0, 0])
|
||||
wipe_tower_rotation_deg: float = 0
|
||||
object_offset: NDArray = np.array([0.0, 0.0, 0.0])
|
||||
|
||||
def __init__(self, selected_objs: list[Object]) -> None:
|
||||
self.object_offset = np.array([0.0, 0.0, 0.0])
|
||||
|
||||
family = []
|
||||
for obj in selected_objs:
|
||||
|
|
@ -445,6 +447,7 @@ class SlicingGroup():
|
|||
|
||||
buf.extend(struct.pack(">I", crc32_array(self.wipe_tower_xy)))
|
||||
buf.extend(struct.pack(">I", crc32_array(np.array(self.wipe_tower_rotation_deg))))
|
||||
buf.extend(struct.pack(">I", crc32_array(self.object_offset)))
|
||||
|
||||
return zlib.crc32(buf) & 0xFFFFFFFF
|
||||
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class ConfigWriter:
|
|||
def write_ini_3mf(self, config_local_path: Path):
|
||||
with open(config_local_path, 'w') as file:
|
||||
for key, val in dict(sorted(self.config_dict.items())).items():
|
||||
if key == "wipe_tower_position":
|
||||
if key in ("wipe_tower_position", "offset_object"):
|
||||
continue
|
||||
file.write(f"; {key} = {val}\n")
|
||||
|
||||
|
|
|
|||
|
|
@ -8,23 +8,33 @@ 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.core.nodeslicer.nodes.inputs import SLICENODE_WipeTower, SLICENODE_OffsetObject
|
||||
from NodeSlicer.infra.prusaslicer_profiles.models import ConfigWriter
|
||||
from NodeSlicer.infra._3mf_writer._3mf import write_model_xml
|
||||
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
|
||||
node.resolve = lambda key: 180.5 if key == "X Position" 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):
|
||||
def test_offset_object_node_output(self):
|
||||
node = SLICENODE_OffsetObject()
|
||||
node.resolve = lambda key: {"Offset X": 12.5, "Offset Y": -5.0, "Offset Z": 2.0}.get(key, 0.0)
|
||||
|
||||
res = node.evaluate_output("Settings")
|
||||
self.assertIn("offset_object", res)
|
||||
self.assertEqual(res["offset_object"], (12.5, -5.0, 2.0))
|
||||
|
||||
def test_config_writer_excludes_special_keys(self):
|
||||
conf = {
|
||||
"layer_height": "0.2",
|
||||
"wipe_tower_position": "(180.5, 135.2)",
|
||||
"offset_object": "(12.5, -5.0, 2.0)",
|
||||
"nozzle_diameter": "0.4"
|
||||
}
|
||||
writer = ConfigWriter(conf)
|
||||
|
|
@ -39,6 +49,7 @@ class TestWipeTowerNode(unittest.TestCase):
|
|||
self.assertIn("; layer_height = 0.2", content)
|
||||
self.assertIn("; nozzle_diameter = 0.4", content)
|
||||
self.assertNotIn("wipe_tower_position", content)
|
||||
self.assertNotIn("offset_object", content)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue