up
This commit is contained in:
parent
77323d29db
commit
c77412bb50
5 changed files with 216 additions and 9 deletions
|
|
@ -835,3 +835,125 @@ class SLICENODE_AssignPropertyToObject(SliceNode):
|
|||
props.append(("", str(item)))
|
||||
|
||||
return _assign_properties_to_item(geom, props)
|
||||
|
||||
|
||||
OBJECT_TYPE_ITEMS: list[tuple[str, str, str]] = [
|
||||
("NO_CHANGE", "Keep Original", "Do not modify the object/part type"),
|
||||
("ModelPart", "Model Part", "Standard model part"),
|
||||
("ParameterModifier", "Modifier", "Parameter modifier volume"),
|
||||
("NegativeVolume", "Negative Volume", "Negative volume for subtractions"),
|
||||
("SupportBlocker", "Support Blocker", "Support blocker volume"),
|
||||
("SupportEnforcer", "Support Enforcer", "Support enforcer volume"),
|
||||
("WipeTower", "Wipe Tower", "Wipe tower marker"),
|
||||
]
|
||||
|
||||
EXTRUDER_ITEMS: list[tuple[str, str, str]] = [
|
||||
("NO_CHANGE", "Keep Original", "Do not modify extruder assignment"),
|
||||
("0", "Default Extruder", "Default Extruder"),
|
||||
("1", "Extruder 1", "Extruder 1"),
|
||||
("2", "Extruder 2", "Extruder 2"),
|
||||
("3", "Extruder 3", "Extruder 3"),
|
||||
("4", "Extruder 4", "Extruder 4"),
|
||||
("5", "Extruder 5", "Extruder 5"),
|
||||
]
|
||||
|
||||
|
||||
def _apply_modifiers_to_so(
|
||||
so: SlicingObject,
|
||||
part_type: str,
|
||||
extruder: str,
|
||||
modifiers_dict: dict[str, str] | None
|
||||
) -> None:
|
||||
if part_type != "NO_CHANGE":
|
||||
so.object_type = part_type
|
||||
if extruder != "NO_CHANGE":
|
||||
so.extruder = extruder
|
||||
if modifiers_dict:
|
||||
existing = {m['param_id']: str(m['param_value']) for m in getattr(so, 'modifiers', []) if m and 'param_id' in m}
|
||||
for k, v in modifiers_dict.items():
|
||||
existing[str(k)] = str(v)
|
||||
so.modifiers = [{'param_id': k, 'param_value': v} for k, v in existing.items()]
|
||||
|
||||
|
||||
def _assign_modifiers_to_item(
|
||||
geom: Any,
|
||||
part_type: str = "NO_CHANGE",
|
||||
extruder: str = "NO_CHANGE",
|
||||
modifiers_dict: dict[str, str] | None = None
|
||||
) -> Any:
|
||||
if geom is None:
|
||||
return None
|
||||
elif isinstance(geom, (list, tuple)):
|
||||
return [_assign_modifiers_to_item(item, part_type, extruder, modifiers_dict) for item in geom]
|
||||
elif isinstance(geom, SlicingGroup):
|
||||
copied = _copy_slicing_group(geom)
|
||||
for coll in copied.collections.values():
|
||||
for obj in coll.objects:
|
||||
_apply_modifiers_to_so(obj, part_type, extruder, modifiers_dict)
|
||||
return copied
|
||||
elif isinstance(geom, SlicingCollection):
|
||||
copied = _copy_slicing_collection(geom)
|
||||
for obj in copied.objects:
|
||||
_apply_modifiers_to_so(obj, part_type, extruder, modifiers_dict)
|
||||
return copied
|
||||
elif isinstance(geom, SlicingObject):
|
||||
copied = _copy_slicing_object(geom)
|
||||
_apply_modifiers_to_so(copied, part_type, extruder, modifiers_dict)
|
||||
return copied
|
||||
return geom
|
||||
|
||||
|
||||
@register_class
|
||||
class SLICENODE_AssignModifiers(SliceNode):
|
||||
bl_label = "Assign Modifiers"
|
||||
|
||||
part_type: str = NfEnumProperty(
|
||||
name="Part Type",
|
||||
description="PrusaSlicer part/volume type",
|
||||
items=OBJECT_TYPE_ITEMS,
|
||||
default="NO_CHANGE"
|
||||
)
|
||||
extruder: str = NfEnumProperty(
|
||||
name="Extruder",
|
||||
description="Extruder assignment",
|
||||
items=EXTRUDER_ITEMS,
|
||||
default="NO_CHANGE"
|
||||
)
|
||||
|
||||
def init(self, context):
|
||||
self.add_input("SLICESOCK_Mesh", "Geometry")
|
||||
self.add_input("SLICESOCK_Settings", "Modifiers")
|
||||
self.add_output("SLICESOCK_Mesh", "Geometry")
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
assert layout
|
||||
layout.prop(self, "part_type", text="Type")
|
||||
layout.prop(self, "extruder", text="Extruder")
|
||||
|
||||
def evaluate_output(self, socket_name: str, **_kwargs) -> Any:
|
||||
if socket_name != "Geometry":
|
||||
return None
|
||||
|
||||
geom: Any = self.resolve("Geometry")
|
||||
if not geom:
|
||||
return None
|
||||
|
||||
raw_modifiers = self.resolve("Modifiers")
|
||||
if raw_modifiers is None:
|
||||
raw_modifiers = self.resolve("Settings")
|
||||
|
||||
mod_dict: dict[str, str] = {}
|
||||
if raw_modifiers:
|
||||
if isinstance(raw_modifiers, dict):
|
||||
for k, v in raw_modifiers.items():
|
||||
mod_dict[str(k)] = str(v)
|
||||
elif isinstance(raw_modifiers, (list, tuple)):
|
||||
for item in raw_modifiers:
|
||||
if isinstance(item, tuple) and len(item) == 2:
|
||||
mod_dict[str(item[0])] = str(item[1])
|
||||
elif isinstance(item, dict):
|
||||
for k, v in item.items():
|
||||
mod_dict[str(k)] = str(v)
|
||||
|
||||
return _assign_modifiers_to_item(geom, self.part_type, self.extruder, mod_dict if mod_dict else None)
|
||||
|
||||
|
|
|
|||
|
|
@ -517,6 +517,7 @@ class SLICENODE_MT_Geometry(Menu):
|
|||
layout.operator("node.add_node", text="Rename Mesh").type = "SLICENODE_RenameMesh"
|
||||
layout.operator("node.add_node", text="Assign PDF Properties").type = "SLICENODE_AssignPdfProperties"
|
||||
layout.operator("node.add_node", text="Assign Property to Object").type = "SLICENODE_AssignPropertyToObject"
|
||||
layout.operator("node.add_node", text="Assign Modifiers").type = "SLICENODE_AssignModifiers"
|
||||
layout.operator("node.add_node", text="Transform Geometry").type = "SLICENODE_TransformGeometry"
|
||||
layout.operator("node.add_node", text="Drop Geometry").type = "SLICENODE_DropGeometry"
|
||||
layout.operator("node.add_node", text="Separate Mesh").type = "SLICENODE_SeparateMesh"
|
||||
|
|
|
|||
|
|
@ -73,6 +73,11 @@ All nodes are found in the **Add (Shift+A)** menu under the **SliceNodes** categ
|
|||
- **Assign Property to Object**: Assigns custom settings/properties to geometry (`SlicingObject`, `SlicingCollection`, `SlicingGroup`, or `SlicePaths`) for rendering in PDF properties tables.
|
||||
- **Geometry**: Generic geometry input socket (accepts `SLICESOCK_Mesh`, `SLICESOCK_Paths`, or `SLICESOCK_Geometry`).
|
||||
- **Settings**: Input socket accepting configuration settings or property items.
|
||||
- **Assign Modifiers**: Assigns PrusaSlicer part/volume types (`ModelPart`, `ParameterModifier`, `NegativeVolume`, `SupportBlocker`, `SupportEnforcer`, `WipeTower`), extruder assignments (`0` to `5`), and config parameter modifiers directly to geometry (`SlicingObject`, `SlicingCollection`, or `SlicingGroup`) for 3MF export.
|
||||
- **Geometry**: Generic geometry input socket (`SLICESOCK_Mesh`).
|
||||
- **Modifiers**: Optional input socket accepting configuration settings dictionary (`SLICESOCK_Settings`).
|
||||
- **Part Type**: Dropdown property specifying target part/volume type (`Keep Original`, `Model Part`, `Modifier`, `Negative Volume`, `Support Blocker`, `Support Enforcer`, `Wipe Tower`).
|
||||
- **Extruder**: Dropdown property specifying target extruder assignment (`Keep Original`, `Default Extruder`, `Extruder 1-5`).
|
||||
- **Transform Geometry**: Moves, rotates, and scales geometry (`SlicingObject`, `SlicingCollection`, `SlicingGroup`, or `SlicePaths`) relative to the origin.
|
||||
- **Geometry**: Input geometry data.
|
||||
- **Translation**: 3D vector specifying the translation offset.
|
||||
|
|
|
|||
|
|
@ -7,14 +7,7 @@ import xml.etree.ElementTree as ET
|
|||
from datetime import date
|
||||
from typing import Any
|
||||
|
||||
<<<<<<< HEAD
|
||||
from ..blender.blender_mesh_capture import SlicingGroup, SlicingCollection
|
||||
=======
|
||||
try:
|
||||
from ..blender.blender_mesh_capture import SlicingGroup, SlicingCollection
|
||||
except ImportError:
|
||||
from ..infra.blender_mesh_capture import SlicingGroup, SlicingCollection
|
||||
>>>>>>> 9d282dee016bdbc10e4529120ee4e3faedbe6685
|
||||
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
|
|
|||
|
|
@ -35,9 +35,11 @@ sys.modules['bpy.app'] = MagicMock()
|
|||
sys.modules['bpy.app.handlers'] = MagicMock()
|
||||
|
||||
from NodeSlicer.core.nodeslicer.nodes.inputs import SLICENODE_WipeTower, SLICENODE_OffsetObject
|
||||
from NodeSlicer.core.nodeslicer.nodes.mesh import SLICENODE_AssignModifiers
|
||||
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
|
||||
from NodeSlicer.infra._3mf_writer._3mf import write_model_xml, write_metadata_xml
|
||||
from NodeSlicer.infra.blender.blender_mesh_capture import SlicingObject, SlicingCollection, SlicingGroup
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
class TestWipeTowerNode(unittest.TestCase):
|
||||
def test_wipe_tower_node_output(self):
|
||||
|
|
@ -77,5 +79,89 @@ class TestWipeTowerNode(unittest.TestCase):
|
|||
self.assertNotIn("wipe_tower_position", content)
|
||||
self.assertNotIn("offset_object", content)
|
||||
|
||||
|
||||
class TestAssignModifiersNode(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.so = SlicingObject()
|
||||
self.so.name = "TestPart"
|
||||
self.so.object_type = "ModelPart"
|
||||
self.so.extruder = "1"
|
||||
self.so.modifiers = [{"param_id": "initial_param", "param_value": "123"}]
|
||||
self.so.mesh = np.array([[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]], dtype=np.float64)
|
||||
|
||||
self.sc = SlicingCollection([], "TestColl")
|
||||
self.sc.objects = [self.so]
|
||||
self.sc.mesh_start_ids = [0]
|
||||
self.sc.mesh_end_ids = [1]
|
||||
|
||||
self.sg = SlicingGroup([])
|
||||
self.sg.collections = {"TestColl": self.sc}
|
||||
|
||||
def test_assign_modifiers_to_slicing_object(self):
|
||||
node = SLICENODE_AssignModifiers()
|
||||
node.part_type = "ParameterModifier"
|
||||
node.extruder = "3"
|
||||
node.resolve = lambda f: {
|
||||
"Geometry": self.so,
|
||||
"Modifiers": {"infill_density": "20%", "layer_height": "0.15"}
|
||||
}.get(f)
|
||||
|
||||
result_so = node.evaluate_output("Geometry")
|
||||
self.assertIsNotNone(result_so)
|
||||
self.assertEqual(result_so.object_type, "ParameterModifier")
|
||||
self.assertEqual(result_so.extruder, "3")
|
||||
|
||||
mod_dict = {m['param_id']: m['param_value'] for m in result_so.modifiers}
|
||||
self.assertEqual(mod_dict["initial_param"], "123")
|
||||
self.assertEqual(mod_dict["infill_density"], "20%")
|
||||
self.assertEqual(mod_dict["layer_height"], "0.15")
|
||||
|
||||
def test_assign_modifiers_to_slicing_group(self):
|
||||
node = SLICENODE_AssignModifiers()
|
||||
node.part_type = "NegativeVolume"
|
||||
node.extruder = "2"
|
||||
node.resolve = lambda f: {
|
||||
"Geometry": self.sg,
|
||||
"Modifiers": {"perimeters": "4"}
|
||||
}.get(f)
|
||||
|
||||
result_sg = node.evaluate_output("Geometry")
|
||||
self.assertIsNotNone(result_sg)
|
||||
res_so = result_sg.collections["TestColl"].objects[0]
|
||||
self.assertEqual(res_so.object_type, "NegativeVolume")
|
||||
self.assertEqual(res_so.extruder, "2")
|
||||
|
||||
mod_dict = {m['param_id']: m['param_value'] for m in res_so.modifiers}
|
||||
self.assertEqual(mod_dict["perimeters"], "4")
|
||||
|
||||
def test_3mf_metadata_xml_output(self):
|
||||
node = SLICENODE_AssignModifiers()
|
||||
node.part_type = "ParameterModifier"
|
||||
node.extruder = "2"
|
||||
node.resolve = lambda f: {
|
||||
"Geometry": self.sg,
|
||||
"Modifiers": {"fill_density": "30%"}
|
||||
}.get(f)
|
||||
|
||||
modified_sg = node.evaluate_output("Geometry")
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
xml_path = Path(temp_dir) / "Slic3r_PE_model.config"
|
||||
write_metadata_xml(modified_sg, xml_path)
|
||||
|
||||
tree = ET.parse(xml_path)
|
||||
root = tree.getroot()
|
||||
|
||||
volumes = root.findall(".//volume")
|
||||
self.assertEqual(len(volumes), 1)
|
||||
vol = volumes[0]
|
||||
|
||||
meta_entries = {m.get("key"): m.get("value") for m in vol.findall("metadata")}
|
||||
self.assertEqual(meta_entries.get("volume_type"), "ParameterModifier")
|
||||
self.assertEqual(meta_entries.get("modifier"), "1")
|
||||
self.assertEqual(meta_entries.get("extruder"), "2")
|
||||
self.assertEqual(meta_entries.get("fill_density"), "30%")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue