From f9329395347c2c1da862fe34cba1af64b3863a2b Mon Sep 17 00:00:00 2001 From: Nicolas Predella Date: Wed, 29 Jul 2026 19:56:41 +0300 Subject: [PATCH] set name --- core/nodeslicer/nodes/mesh.py | 66 ++++++++++++++++++++++++++++++++++- core/nodeslicer/tree.py | 1 + doc/NODES.md | 3 ++ tests/test_rename_mesh.py | 65 ++++++++++++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 tests/test_rename_mesh.py diff --git a/core/nodeslicer/nodes/mesh.py b/core/nodeslicer/nodes/mesh.py index 2937b13..7637acb 100644 --- a/core/nodeslicer/nodes/mesh.py +++ b/core/nodeslicer/nodes/mesh.py @@ -2,7 +2,7 @@ from __future__ import annotations from typing import Any import numpy as np -from ..bpy_ns_types import NfBoolProperty, NfEnumProperty, NfIntProperty, NfFloatProperty, notify_tree_changed +from ..bpy_ns_types import NfBoolProperty, NfEnumProperty, NfIntProperty, NfFloatProperty, NfStringProperty, notify_tree_changed from ....registry import register_class from .base import SliceNode, SlicingCollection, get_all_children, SliceNodeJoiner, SlicingGroup, SlicingObject @@ -549,3 +549,67 @@ class SLICENODE_DropGeometry(SliceNode): copied_data.transforms.append(drop_rec) return copied_data + + +def _rename_slicing_item(geom: Any, new_name: str) -> Any: + if geom is None or not new_name: + return geom + elif isinstance(geom, (list, tuple)): + return [_rename_slicing_item(item, f"{new_name}_{i}" if len(geom) > 1 else new_name) for i, item in enumerate(geom)] + elif isinstance(geom, SlicingGroup): + copied = _copy_slicing_group(geom) + old_colls = dict(copied.collections) + new_colls = {} + for i, (old_key, coll) in enumerate(old_colls.items()): + coll_name = f"{new_name}_{i}" if len(old_colls) > 1 else new_name + coll.name = coll_name + for obj in coll.objects: + obj.parent = coll_name + obj.name = coll_name + new_colls[coll_name] = coll + copied.collections = new_colls + return copied + elif isinstance(geom, SlicingCollection): + copied = _copy_slicing_collection(geom) + copied.name = new_name + for obj in copied.objects: + obj.parent = new_name + obj.name = new_name + return copied + elif isinstance(geom, SlicingObject): + copied = _copy_slicing_object(geom) + copied.name = new_name + return copied + return geom + + +@register_class +class SLICENODE_RenameMesh(SliceNode): + bl_label = "Rename Mesh" + + new_name: NfStringProperty(name="Name", default="RenamedMesh") # pyright: ignore[reportInvalidTypeForm] + + def init(self, context): + self.add_input("SLICESOCK_Mesh", "Geometry") + self.add_input("SLICESOCK_String", "Name") + self.add_output("SLICESOCK_Mesh", "Geometry") + + def draw_buttons(self, context, layout): + super().draw_buttons(context, layout) + layout.prop(self, "new_name") + + 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 + + name_socket: str | None = self.resolve("Name") + target_name = name_socket if name_socket else self.new_name + if not target_name: + return geom + + return _rename_slicing_item(geom, target_name) + diff --git a/core/nodeslicer/tree.py b/core/nodeslicer/tree.py index e80db27..47c329f 100644 --- a/core/nodeslicer/tree.py +++ b/core/nodeslicer/tree.py @@ -498,6 +498,7 @@ class SLICENODE_MT_Geometry(Menu): layout = self.layout assert layout layout.operator("node.add_node", text="Get Mesh").type = "SLICENODE_GetMesh" + layout.operator("node.add_node", text="Rename Mesh").type = "SLICENODE_RenameMesh" 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" diff --git a/doc/NODES.md b/doc/NODES.md index 6bc9c6c..f437210 100644 --- a/doc/NODES.md +++ b/doc/NODES.md @@ -60,6 +60,9 @@ All nodes are found in the **Add (Shift+A)** menu under the **SliceNodes** categ ### Geometry Processing - **Get Mesh**: Converts a Blender Object into internal `SlicingObject` format. +- **Rename Mesh**: Overrides the internal metadata name of a `SlicingObject`, `SlicingCollection`, or `SlicingGroup`. + - **Geometry**: Input geometry data. + - **Name**: String input socket or property for the new name. - **Transform Geometry**: Moves, rotates, and scales a `SlicingCollection` or `SlicingGroup` relative to the origin. - **Mesh**: Input mesh data. - **Translation**: 3D vector specifying the translation offset. diff --git a/tests/test_rename_mesh.py b/tests/test_rename_mesh.py new file mode 100644 index 0000000..792e905 --- /dev/null +++ b/tests/test_rename_mesh.py @@ -0,0 +1,65 @@ +import sys +import unittest +from unittest.mock import MagicMock +from pathlib import Path + +PROJECT_ROOT = Path(__file__).resolve().parent.parent +sys.path.append(str(PROJECT_ROOT.parent)) + +# Mock bpy and dependencies for unit testing +class DummyNodeSocket: pass +class DummyNode: pass +class DummyObject: + def __init__(self, name="TestObj"): + self.name = name + +mock_types = MagicMock() +mock_types.NodeSocket = DummyNodeSocket +mock_types.Node = DummyNode +mock_types.Object = DummyObject + +mock_bpy = MagicMock() +mock_bpy.context.scene = MagicMock() +mock_bpy.context.scene.unit_settings.scale_length = 0.001 +mock_bpy.types = mock_types + +sys.modules['bpy'] = mock_bpy +sys.modules['bpy.types'] = mock_types +sys.modules['bpy.props'] = MagicMock() +sys.modules['bpy.utils'] = MagicMock() + +from NodeSlicer.infra.blender.blender_mesh_capture import SlicingObject, SlicingCollection, SlicingGroup +from NodeSlicer.core.nodeslicer.nodes.mesh import _rename_slicing_item + + +class TestRenameMesh(unittest.TestCase): + + def test_rename_slicing_object(self): + so = SlicingObject() + so.name = "OldName" + renamed = _rename_slicing_item(so, "NewName") + self.assertEqual(renamed.name, "NewName") + + def test_rename_slicing_collection(self): + so1 = SlicingObject() + so1.name = "Part1" + sc = SlicingCollection([], "OldCollection") + sc.objects = [so1] + + renamed = _rename_slicing_item(sc, "NewColl") + self.assertEqual(renamed.name, "NewColl") + self.assertEqual(renamed.objects[0].name, "NewColl") + self.assertEqual(renamed.objects[0].parent, "NewColl") + + def test_rename_slicing_group(self): + sc = SlicingCollection([], "Coll1") + sg = SlicingGroup([]) + sg.collections = {"Coll1": sc} + + renamed = _rename_slicing_item(sg, "NewGroup") + self.assertIn("NewGroup", renamed.collections) + self.assertEqual(renamed.collections["NewGroup"].name, "NewGroup") + + +if __name__ == '__main__': + unittest.main()