group-list
This commit is contained in:
parent
6520e2b6f9
commit
31a86fbb73
4 changed files with 194 additions and 0 deletions
|
|
@ -100,6 +100,99 @@ class SLICENODE_JoinMeshGroups(SliceNodeJoiner):
|
|||
|
||||
return group
|
||||
|
||||
@register_class
|
||||
class SLICENODE_GroupToList(SliceNode):
|
||||
bl_label = "Group to List"
|
||||
always_list_outputs = ["Collections"]
|
||||
|
||||
def init(self, context):
|
||||
self.add_input("SLICESOCK_Mesh", "Group")
|
||||
self.add_output("SLICESOCK_Mesh", "Collections", display_shape='LIST')
|
||||
|
||||
def evaluate_output(self, socket_name: str, **_kwargs) -> list[SlicingCollection]:
|
||||
val = self.resolve("Group")
|
||||
if val is None:
|
||||
val = self.resolve("Collections")
|
||||
if val is None and hasattr(self, "inputs") and len(self.inputs) > 0:
|
||||
val = self.resolve(self.inputs[0].name)
|
||||
if val is None:
|
||||
return []
|
||||
|
||||
if isinstance(val, SlicingGroup):
|
||||
return list(val.collections.values())
|
||||
|
||||
if isinstance(val, (list, tuple)):
|
||||
res: list[SlicingCollection] = []
|
||||
for item in val:
|
||||
if isinstance(item, SlicingGroup):
|
||||
res.extend(item.collections.values())
|
||||
elif isinstance(item, SlicingCollection):
|
||||
res.append(item)
|
||||
elif isinstance(item, SlicingObject):
|
||||
res.append(SlicingCollection([item], getattr(item, "name", "Collection")))
|
||||
return res
|
||||
|
||||
if isinstance(val, SlicingCollection):
|
||||
return [val]
|
||||
|
||||
if isinstance(val, SlicingObject):
|
||||
return [SlicingCollection([val], getattr(val, "name", "Collection"))]
|
||||
|
||||
return []
|
||||
|
||||
@register_class
|
||||
class SLICENODE_ListToGroup(SliceNode):
|
||||
bl_label = "List to Group"
|
||||
always_list_inputs = ["Collections"]
|
||||
|
||||
def init(self, context):
|
||||
self.add_input("SLICESOCK_Mesh", "Collections", display_shape='LIST')
|
||||
self.add_output("SLICESOCK_Mesh", "Group")
|
||||
|
||||
def evaluate_output(self, socket_name: str, **_kwargs) -> SlicingGroup:
|
||||
val = self.resolve("Collections")
|
||||
if val is None:
|
||||
val = self.resolve("Group")
|
||||
if val is None and hasattr(self, "inputs") and len(self.inputs) > 0:
|
||||
val = self.resolve(self.inputs[0].name)
|
||||
if val is None:
|
||||
return SlicingGroup([])
|
||||
|
||||
if isinstance(val, SlicingGroup):
|
||||
return val
|
||||
|
||||
items = val if isinstance(val, (list, tuple)) else [val]
|
||||
group: SlicingGroup = SlicingGroup([])
|
||||
|
||||
for idx, item in enumerate(items):
|
||||
if not item:
|
||||
continue
|
||||
if isinstance(item, SlicingGroup):
|
||||
if hasattr(item, "transforms") and item.transforms and not group.transforms:
|
||||
group.transforms = list(item.transforms)
|
||||
for k, sc in item.collections.items():
|
||||
name = k
|
||||
if name in group.collections:
|
||||
name = f"{name}_{idx}"
|
||||
group.collections[name] = sc
|
||||
elif isinstance(item, SlicingCollection):
|
||||
if hasattr(item, "transforms") and item.transforms and not group.transforms:
|
||||
group.transforms = list(item.transforms)
|
||||
name = item.name or f"collection_{idx}"
|
||||
if name in group.collections:
|
||||
name = f"{name}_{idx}"
|
||||
group.collections[name] = item
|
||||
elif isinstance(item, SlicingObject):
|
||||
if hasattr(item, "transforms") and item.transforms and not group.transforms:
|
||||
group.transforms = list(item.transforms)
|
||||
name = getattr(item, "name", f"object_{idx}")
|
||||
if name in group.collections:
|
||||
name = f"{name}_{idx}"
|
||||
sc = SlicingCollection([item], name)
|
||||
group.collections[name] = sc
|
||||
|
||||
return group
|
||||
|
||||
@register_class
|
||||
class SLICENODE_SeparateMesh(SliceNode):
|
||||
bl_label = "Separate Mesh"
|
||||
|
|
|
|||
|
|
@ -808,6 +808,8 @@ class SLICENODE_MT_Geometry(Menu):
|
|||
layout.operator("node.add_node", text="Separate Mesh").type = "SLICENODE_SeparateMesh"
|
||||
layout.operator("node.add_node", text="Separate Mesh by Range").type = "SLICENODE_SeparateMeshByRange"
|
||||
layout.operator("node.add_node", text="Arrange Slicing Group").type = "SLICENODE_ArrangeMesh"
|
||||
layout.operator("node.add_node", text="Group to List").type = "SLICENODE_GroupToList"
|
||||
layout.operator("node.add_node", text="List to Group").type = "SLICENODE_ListToGroup"
|
||||
layout.operator("node.add_node", text="Get Paths").type = "SLICENODE_GetPaths"
|
||||
layout.separator()
|
||||
layout.operator("node.add_node", text="Layers").type = "SLICENODE_Layers"
|
||||
|
|
|
|||
|
|
@ -196,6 +196,8 @@ All nodes are found in the **Add (Shift+A)** menu under the **SliceNodes** categ
|
|||
- **Meshes to List**: Multi-input list joiner that bundles meshes into a `list[SlicingCollection]` with LIST socket shape. Flattens incoming lists.
|
||||
- **Paths to List**: Multi-input list joiner that bundles toolpaths into a `list[SlicePaths]` with LIST socket shape. Flattens incoming lists.
|
||||
- **Gcodes to List**: Multi-input list joiner that bundles G-code strings into a `list[str]` with LIST socket shape. Flattens incoming lists.
|
||||
- **Group to List**: Decomposes a `SlicingGroup` into a `list[SlicingCollection]`. Output socket `Collections` displays LIST socket shape.
|
||||
- **List to Group**: Converts a `list[SlicingCollection]` into a single `SlicingGroup`. Input socket `Collections` has LIST socket shape, output socket `Group` outputs a single group.
|
||||
- **Join Mesh Parts**: Merges multiple `SlicingObject` instances into one `SlicingCollection`. Flattens incoming lists.
|
||||
- **Join Mesh Groups**: Combines multiple `SlicingCollection` instances into a `SlicingGroup`. Flattens incoming lists.
|
||||
- **Path Operations**: Multi-input joiner performing Zip, Extend, Maximum coverage, Minimum coverage, Intersection, Subtract, and XOR operations. The first input can be a list, in which case the operation runs per-element against the same secondary inputs.
|
||||
|
|
|
|||
|
|
@ -375,6 +375,103 @@ class TestNodeEvaluationsWithLists(unittest.TestCase):
|
|||
g_node.resolve_multi_input = lambda label: [["G1 X1", "G1 X2"], "G1 X3", ["G1 X4", "G1 X5"]]
|
||||
self.assertEqual(g_node.evaluate_output("Gcode"), ["G1 X1", "G1 X2", "G1 X3", "G1 X4", "G1 X5"])
|
||||
|
||||
def test_group_to_list(self):
|
||||
from NodeSlicer.core.nodeslicer.nodes.mesh import SLICENODE_GroupToList
|
||||
|
||||
sc1 = SlicingCollection([], "PartA")
|
||||
sc2 = SlicingCollection([], "PartB")
|
||||
group = SlicingGroup([])
|
||||
group.collections = {"PartA": sc1, "PartB": sc2}
|
||||
|
||||
node = SLICENODE_GroupToList()
|
||||
self.assertEqual(node.always_list_outputs, ["Collections"])
|
||||
|
||||
# 1. Normal conversion: SlicingGroup -> list[SlicingCollection]
|
||||
node.resolve = lambda label: group
|
||||
res = node.evaluate_output("Collections")
|
||||
self.assertIsInstance(res, list)
|
||||
self.assertEqual(len(res), 2)
|
||||
self.assertEqual(res, [sc1, sc2])
|
||||
|
||||
# 2. None input -> empty list
|
||||
node.resolve = lambda label: None
|
||||
self.assertEqual(node.evaluate_output("Collections"), [])
|
||||
|
||||
# 3. Single collection passed directly -> [sc1]
|
||||
node.resolve = lambda label: sc1
|
||||
self.assertEqual(node.evaluate_output("Collections"), [sc1])
|
||||
|
||||
# 4. List of groups -> flattened list of collections
|
||||
group2 = SlicingGroup([])
|
||||
sc3 = SlicingCollection([], "PartC")
|
||||
group2.collections = {"PartC": sc3}
|
||||
node.resolve = lambda label: [group, group2]
|
||||
res_multi = node.evaluate_output("Collections")
|
||||
self.assertEqual(res_multi, [sc1, sc2, sc3])
|
||||
|
||||
def test_list_to_group(self):
|
||||
from NodeSlicer.core.nodeslicer.nodes.mesh import SLICENODE_ListToGroup
|
||||
|
||||
sc1 = SlicingCollection([], "PartA")
|
||||
sc2 = SlicingCollection([], "PartB")
|
||||
|
||||
node = SLICENODE_ListToGroup()
|
||||
self.assertEqual(node.always_list_inputs, ["Collections"])
|
||||
|
||||
# 1. Normal conversion: list[SlicingCollection] -> SlicingGroup
|
||||
node.resolve = lambda label: [sc1, sc2]
|
||||
res = node.evaluate_output("Group")
|
||||
self.assertIsInstance(res, SlicingGroup)
|
||||
self.assertEqual(len(res.collections), 2)
|
||||
self.assertIs(res.collections.get("PartA"), sc1)
|
||||
self.assertIs(res.collections.get("PartB"), sc2)
|
||||
|
||||
# 2. None input -> empty SlicingGroup
|
||||
node.resolve = lambda label: None
|
||||
res_empty = node.evaluate_output("Group")
|
||||
self.assertIsInstance(res_empty, SlicingGroup)
|
||||
self.assertEqual(len(res_empty.collections), 0)
|
||||
|
||||
# 3. Single collection -> SlicingGroup with 1 collection
|
||||
node.resolve = lambda label: sc1
|
||||
res_single = node.evaluate_output("Group")
|
||||
self.assertIsInstance(res_single, SlicingGroup)
|
||||
self.assertEqual(len(res_single.collections), 1)
|
||||
self.assertIs(res_single.collections.get("PartA"), sc1)
|
||||
|
||||
# 4. Duplicate collection names are handled without overwriting
|
||||
dup1 = SlicingCollection([], "Part")
|
||||
dup2 = SlicingCollection([], "Part")
|
||||
node.resolve = lambda label: [dup1, dup2]
|
||||
res_dup = node.evaluate_output("Group")
|
||||
self.assertEqual(len(res_dup.collections), 2)
|
||||
self.assertIn("Part", res_dup.collections)
|
||||
self.assertIn("Part_1", res_dup.collections)
|
||||
|
||||
def test_group_to_list_and_list_to_group_roundtrip(self):
|
||||
from NodeSlicer.core.nodeslicer.nodes.mesh import SLICENODE_GroupToList, SLICENODE_ListToGroup
|
||||
|
||||
sc1 = SlicingCollection([], "Part1")
|
||||
sc1.transforms = [{"type": "INIT", "translation": (1.0, 2.0, 3.0), "rotation": (0.0, 0.0, 0.0), "scale": (1.0, 1.0, 1.0)}]
|
||||
sc2 = SlicingCollection([], "Part2")
|
||||
orig_group = SlicingGroup([])
|
||||
orig_group.collections = {"Part1": sc1, "Part2": sc2}
|
||||
orig_group.transforms = list(sc1.transforms)
|
||||
|
||||
g2l = SLICENODE_GroupToList()
|
||||
g2l.resolve = lambda label: orig_group
|
||||
colls_list = g2l.evaluate_output("Collections")
|
||||
self.assertIsInstance(colls_list, list)
|
||||
self.assertEqual(len(colls_list), 2)
|
||||
|
||||
l2g = SLICENODE_ListToGroup()
|
||||
l2g.resolve = lambda label: colls_list
|
||||
recovered_group = l2g.evaluate_output("Group")
|
||||
self.assertIsInstance(recovered_group, SlicingGroup)
|
||||
self.assertEqual(len(recovered_group.collections), 2)
|
||||
self.assertEqual(set(recovered_group.collections.keys()), {"Part1", "Part2"})
|
||||
self.assertEqual(recovered_group.transforms, orig_group.transforms)
|
||||
|
||||
def test_join_mesh_flattens_lists(self):
|
||||
from NodeSlicer.core.nodeslicer.nodes.mesh import SLICENODE_JoinMesh
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue