implicit conversion
This commit is contained in:
parent
c5c8df0973
commit
8dd61f5858
8 changed files with 337 additions and 49 deletions
|
|
@ -31,12 +31,8 @@ class SLICENODE_GetMesh(SliceNode):
|
||||||
obj: Any = self.resolve("Object")
|
obj: Any = self.resolve("Object")
|
||||||
if not obj: return None
|
if not obj: return None
|
||||||
|
|
||||||
def _to_collection(o: Any) -> SlicingCollection | None:
|
from ....infra.blender.blender_slice_nodes_generic import object_to_mesh
|
||||||
if not o: return None
|
return object_to_mesh(obj, include_children=self.include_children)
|
||||||
family = [o] + (get_all_children(o) if self.include_children else [])
|
|
||||||
return SlicingCollection(family, getattr(o, "name", "Mesh"))
|
|
||||||
|
|
||||||
return eval_element_wise(_to_collection, obj)
|
|
||||||
|
|
||||||
@register_class
|
@register_class
|
||||||
class SLICENODE_MeshesToList(SliceNodeListJoiner):
|
class SLICENODE_MeshesToList(SliceNodeListJoiner):
|
||||||
|
|
|
||||||
|
|
@ -45,29 +45,6 @@ class SLICESOCK_Geometry(LabelNodeSocket):
|
||||||
default = None
|
default = None
|
||||||
bl_color = (0.0, 0.75, 0.75, 1.0)
|
bl_color = (0.0, 0.75, 0.75, 1.0)
|
||||||
|
|
||||||
def draw_color(self, context, node) -> tuple[float, float, float, float]:
|
|
||||||
def _get_upstream_color(sock: Any) -> tuple[float, float, float, float]:
|
|
||||||
if not sock or not getattr(sock, "is_linked", False) or not getattr(sock, "links", None):
|
|
||||||
return self.bl_color
|
|
||||||
from_sock = sock.links[0].from_socket
|
|
||||||
if hasattr(from_sock, "draw_color"):
|
|
||||||
try:
|
|
||||||
from_node = getattr(from_sock, "node", None)
|
|
||||||
return from_sock.draw_color(context, from_node)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return getattr(from_sock, "bl_color", self.bl_color)
|
|
||||||
|
|
||||||
if getattr(self, "is_output", False):
|
|
||||||
if node and hasattr(node, "inputs"):
|
|
||||||
sock_name = getattr(self, "name", "")
|
|
||||||
matching_in = (node.inputs.get(sock_name) if sock_name else None) or node.inputs.get("Geometry")
|
|
||||||
if matching_in:
|
|
||||||
return _get_upstream_color(matching_in)
|
|
||||||
return self.bl_color
|
|
||||||
else:
|
|
||||||
return _get_upstream_color(self)
|
|
||||||
|
|
||||||
@register_class
|
@register_class
|
||||||
class SLICESOCK_Mesh(LabelNodeSocket):
|
class SLICESOCK_Mesh(LabelNodeSocket):
|
||||||
bl_label = "Mesh"
|
bl_label = "Mesh"
|
||||||
|
|
|
||||||
|
|
@ -160,32 +160,42 @@ class SLICETREE_NodeTree(NodeTree):
|
||||||
|
|
||||||
def valid_link(self, from_socket: Any, to_socket: Any) -> bool:
|
def valid_link(self, from_socket: Any, to_socket: Any) -> bool:
|
||||||
geom_sockets = {"SLICESOCK_Mesh", "SLICESOCK_Paths", "SLICESOCK_Geometry"}
|
geom_sockets = {"SLICESOCK_Mesh", "SLICESOCK_Paths", "SLICESOCK_Geometry"}
|
||||||
|
out_s, in_s = from_socket, to_socket
|
||||||
|
if getattr(from_socket, "is_output", None) is False or getattr(to_socket, "is_output", None) is True:
|
||||||
|
out_s, in_s = to_socket, from_socket
|
||||||
|
|
||||||
|
is_obj_to_mesh = (
|
||||||
|
getattr(out_s, "bl_idname", "") in ("NodeSocketObject", "SLICESOCK_Object")
|
||||||
|
and getattr(in_s, "bl_idname", "") == "SLICESOCK_Mesh"
|
||||||
|
)
|
||||||
|
|
||||||
compatible = (
|
compatible = (
|
||||||
(from_socket.bl_idname in geom_sockets and to_socket.bl_idname in geom_sockets)
|
(from_socket.bl_idname in geom_sockets and to_socket.bl_idname in geom_sockets)
|
||||||
or from_socket.bl_idname == to_socket.bl_idname
|
or from_socket.bl_idname == to_socket.bl_idname
|
||||||
|
or is_obj_to_mesh
|
||||||
)
|
)
|
||||||
if not compatible:
|
if not compatible:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
from ...infra.blender.list_utils import is_output_socket_a_list, group_input_can_accept_list
|
from ...infra.blender.list_utils import is_output_socket_a_list, group_input_can_accept_list
|
||||||
if is_output_socket_a_list(from_socket):
|
if is_output_socket_a_list(out_s):
|
||||||
to_node = getattr(to_socket, "node", None)
|
to_node = getattr(in_s, "node", None)
|
||||||
if to_node is not None:
|
if to_node is not None:
|
||||||
# Start Print and Printer List reject lists
|
# Start Print and Printer List reject lists
|
||||||
if to_node.bl_idname in ("SLICENODE_StartPrint", "SLICENODE_PrinterList"):
|
if to_node.bl_idname in ("SLICENODE_StartPrint", "SLICENODE_PrinterList"):
|
||||||
return False
|
return False
|
||||||
# Path Operations: only input 0 can accept a list; secondary inputs reject lists
|
# Path Operations: only input 0 can accept a list; secondary inputs reject lists
|
||||||
if to_node.bl_idname == "SLICENODE_PathOperations":
|
if to_node.bl_idname == "SLICENODE_PathOperations":
|
||||||
if getattr(to_node, "inputs", None) and to_socket != to_node.inputs[0]:
|
if getattr(to_node, "inputs", None) and in_s != to_node.inputs[0]:
|
||||||
return False
|
return False
|
||||||
# Seam Placement: secondary input (Mesh) rejects lists
|
# Seam Placement: secondary input (Mesh) rejects lists
|
||||||
if to_node.bl_idname == "SLICENODE_SeamPlacement":
|
if to_node.bl_idname == "SLICENODE_SeamPlacement":
|
||||||
if getattr(to_socket, "name", "") == "Mesh":
|
if getattr(in_s, "name", "") == "Mesh":
|
||||||
return False
|
return False
|
||||||
# Group node: check if input can accept list
|
# Group node: check if input can accept list
|
||||||
if to_node.bl_idname == "SLICENODE_Group":
|
if to_node.bl_idname == "SLICENODE_Group":
|
||||||
tree = getattr(to_node, "slicer_tree", None) or getattr(to_node, "node_tree", None)
|
tree = getattr(to_node, "slicer_tree", None) or getattr(to_node, "node_tree", None)
|
||||||
if tree and not group_input_can_accept_list(tree, to_socket):
|
if tree and not group_input_can_accept_list(tree, in_s):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
def get_all_children(obj):
|
def get_all_children(obj):
|
||||||
children = []
|
children = []
|
||||||
for child in obj.children:
|
for child in getattr(obj, "children", []):
|
||||||
children += [child] + get_all_children(child)
|
children += [child] + get_all_children(child)
|
||||||
|
|
||||||
children = list(set(children))
|
children = list(set(children))
|
||||||
|
|
|
||||||
|
|
@ -592,7 +592,8 @@ def _cached_objects_to_tris(obj_names: tuple[str, ...], matrices: tuple[tuple[fl
|
||||||
|
|
||||||
def objects_to_tris(objects: list[Object], scale: float) -> tuple[NDArray[float64], dict[str, dict[str, Any]]]:
|
def objects_to_tris(objects: list[Object], scale: float) -> tuple[NDArray[float64], dict[str, dict[str, Any]]]:
|
||||||
obj_names = tuple(o.name for o in objects)
|
obj_names = tuple(o.name for o in objects)
|
||||||
matrices = tuple(tuple(val for row in o.matrix_world for val in row) for o in objects)
|
identity_mat = ((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0), (0, 0, 0, 1))
|
||||||
|
matrices = tuple(tuple(val for row in getattr(o, "matrix_world", identity_mat) for val in row) for o in objects)
|
||||||
return _cached_objects_to_tris(obj_names, matrices, scale)
|
return _cached_objects_to_tris(obj_names, matrices, scale)
|
||||||
|
|
||||||
def _cached_capture_mesh_edges(
|
def _cached_capture_mesh_edges(
|
||||||
|
|
|
||||||
|
|
@ -657,6 +657,56 @@ def _extract_caller_context() -> tuple[Any, str | None]:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
def object_to_mesh(data: Any, include_children: bool = True) -> Any:
|
||||||
|
"""Converts an Object (or list of Objects) into SlicingCollection, matching SLICENODE_GetMesh."""
|
||||||
|
from .blender_mesh_capture import SlicingCollection, SlicingGroup, SlicingObject
|
||||||
|
from .blender_bridge import get_all_children
|
||||||
|
from .list_utils import is_list_data
|
||||||
|
|
||||||
|
def _to_collection(o: Any) -> Any:
|
||||||
|
if not o:
|
||||||
|
return None
|
||||||
|
if isinstance(o, (SlicingCollection, SlicingGroup, SlicingObject)):
|
||||||
|
return o
|
||||||
|
family = [o] + (get_all_children(o) if include_children else [])
|
||||||
|
return SlicingCollection(family, getattr(o, "name", "Mesh"))
|
||||||
|
|
||||||
|
def _eval(d: Any) -> Any:
|
||||||
|
if is_list_data(d):
|
||||||
|
return [_eval(item) for item in d]
|
||||||
|
return _to_collection(d)
|
||||||
|
|
||||||
|
return _eval(data)
|
||||||
|
|
||||||
|
def _is_already_mesh(val: Any) -> bool:
|
||||||
|
if val is None:
|
||||||
|
return False
|
||||||
|
from .blender_mesh_capture import SlicingCollection, SlicingGroup, SlicingObject
|
||||||
|
if isinstance(val, (SlicingCollection, SlicingGroup, SlicingObject)):
|
||||||
|
return True
|
||||||
|
if is_list_data(val):
|
||||||
|
flat = flatten_list(val)
|
||||||
|
if flat and all(isinstance(x, (SlicingCollection, SlicingGroup, SlicingObject)) for x in flat):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _is_object_data(val: Any) -> bool:
|
||||||
|
if val is None:
|
||||||
|
return False
|
||||||
|
if _is_already_mesh(val):
|
||||||
|
return False
|
||||||
|
if isinstance(val, (str, int, float, bool, bytes, dict)):
|
||||||
|
return False
|
||||||
|
if is_list_data(val):
|
||||||
|
flat = flatten_list(val)
|
||||||
|
if not flat:
|
||||||
|
return False
|
||||||
|
return any(_is_object_data(x) for x in flat)
|
||||||
|
obj_type = getattr(bpy.types, "Object", None)
|
||||||
|
if isinstance(obj_type, type) and isinstance(val, obj_type):
|
||||||
|
return True
|
||||||
|
return hasattr(val, "name") or hasattr(val, "type") or hasattr(val, "matrix_world")
|
||||||
|
|
||||||
def resolve_input(
|
def resolve_input(
|
||||||
socket: SliceNodeSocket | None,
|
socket: SliceNodeSocket | None,
|
||||||
*,
|
*,
|
||||||
|
|
@ -709,13 +759,37 @@ def resolve_input(
|
||||||
assert socket.links
|
assert socket.links
|
||||||
from_socket: NodeSocket = _follow_reroute(socket.links[0].from_socket)
|
from_socket: NodeSocket = _follow_reroute(socket.links[0].from_socket)
|
||||||
|
|
||||||
return _resolve_output(
|
val = _resolve_output(
|
||||||
from_socket,
|
from_socket,
|
||||||
default=None,
|
default=None,
|
||||||
cache=cache,
|
cache=cache,
|
||||||
visited=visited,
|
visited=visited,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
is_mesh_socket = (
|
||||||
|
getattr(socket, "bl_idname", "") == "SLICESOCK_Mesh"
|
||||||
|
or type(socket).__name__ == "SLICESOCK_Mesh"
|
||||||
|
)
|
||||||
|
if is_mesh_socket and val is not None:
|
||||||
|
is_obj_source = getattr(from_socket, "bl_idname", "") in ("NodeSocketObject", "SLICESOCK_Object")
|
||||||
|
if (is_obj_source or _is_object_data(val)) and not _is_already_mesh(val):
|
||||||
|
group_stack = tuple(n.as_pointer() for n in _get_group_stack(cache))
|
||||||
|
from_node = getattr(from_socket, "node", None)
|
||||||
|
from_ptr = from_node.as_pointer() if (from_node and hasattr(from_node, "as_pointer")) else id(from_node)
|
||||||
|
from_name = getattr(from_node, "name", "") if from_node else ""
|
||||||
|
from_ident = getattr(from_socket, "identifier", getattr(from_socket, "name", ""))
|
||||||
|
conv_cache_key = ("implicit_get_mesh", group_stack, from_ptr, from_name, from_ident)
|
||||||
|
|
||||||
|
if cache is not None and conv_cache_key in cache:
|
||||||
|
return cache[conv_cache_key]
|
||||||
|
|
||||||
|
mesh_val = object_to_mesh(val, include_children=True)
|
||||||
|
if cache is not None:
|
||||||
|
cache[conv_cache_key] = mesh_val
|
||||||
|
return mesh_val
|
||||||
|
|
||||||
|
return val
|
||||||
|
|
||||||
def _resolve_output(
|
def _resolve_output(
|
||||||
socket: NodeSocket,
|
socket: NodeSocket,
|
||||||
*,
|
*,
|
||||||
|
|
@ -735,10 +809,12 @@ def _resolve_output(
|
||||||
|
|
||||||
# Use identifier instead of name for the cache key, as multiple sockets
|
# Use identifier instead of name for the cache key, as multiple sockets
|
||||||
# (especially in multi-groups) can share the same name but must have unique identifiers.
|
# (especially in multi-groups) can share the same name but must have unique identifiers.
|
||||||
key = (group_stack, socket.node.as_pointer(), socket.node.name, socket.identifier)
|
node_ptr = socket.node.as_pointer() if hasattr(socket.node, "as_pointer") else id(socket.node)
|
||||||
|
node_name = getattr(socket.node, "name", "")
|
||||||
|
key = (group_stack, node_ptr, node_name, socket.identifier)
|
||||||
if key in cache: return cache[key]
|
if key in cache: return cache[key]
|
||||||
|
|
||||||
node_key = (group_stack, socket.node.as_pointer(), socket.node.name)
|
node_key = (group_stack, node_ptr, node_name)
|
||||||
if node_key in visited: return default # cycle guard
|
if node_key in visited: return default # cycle guard
|
||||||
|
|
||||||
visited.add(node_key)
|
visited.add(node_key)
|
||||||
|
|
|
||||||
|
|
@ -115,13 +115,19 @@ class DummySocketCollection(list):
|
||||||
return s
|
return s
|
||||||
return default
|
return default
|
||||||
|
|
||||||
class DummyNode:
|
from NodeSlicer.infra.blender.blender_slice_nodes_generic import SliceNode
|
||||||
def __init__(self, bl_idname="DummyNode"):
|
|
||||||
|
class DummyNode(SliceNode):
|
||||||
|
def __init__(self, bl_idname="DummyNode", name="DummyNode"):
|
||||||
self.bl_idname = bl_idname
|
self.bl_idname = bl_idname
|
||||||
|
self.name = name
|
||||||
self.inputs = DummySocketCollection()
|
self.inputs = DummySocketCollection()
|
||||||
self.outputs = DummySocketCollection()
|
self.outputs = DummySocketCollection()
|
||||||
self.is_list_output = False
|
self.is_list_output = False
|
||||||
|
|
||||||
|
def as_pointer(self):
|
||||||
|
return id(self)
|
||||||
|
|
||||||
def add_in(self, sock):
|
def add_in(self, sock):
|
||||||
sock.node = self
|
sock.node = self
|
||||||
self.inputs.append(sock)
|
self.inputs.append(sock)
|
||||||
|
|
@ -700,5 +706,228 @@ class TestGroupNodeListShapes(unittest.TestCase):
|
||||||
self.assertFalse(validator.valid_link(list_out, grp_in_float))
|
self.assertFalse(validator.valid_link(list_out, grp_in_float))
|
||||||
|
|
||||||
|
|
||||||
|
class TestImplicitGetMesh(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.tree = SLICETREE_NodeTree()
|
||||||
|
|
||||||
|
def test_valid_link_object_to_mesh(self):
|
||||||
|
obj_out = DummySocket("Object", "NodeSocketObject")
|
||||||
|
obj_out.is_output = True
|
||||||
|
mesh_in = DummySocket("Mesh", "SLICESOCK_Mesh")
|
||||||
|
mesh_in.is_output = False
|
||||||
|
mesh_in.node = DummyNode("SLICENODE_SliceShells")
|
||||||
|
mesh_in.node.list_socket_mapping = [("Mesh", "Paths")]
|
||||||
|
|
||||||
|
# Object output to Mesh input is valid
|
||||||
|
self.assertTrue(self.tree.valid_link(obj_out, mesh_in))
|
||||||
|
|
||||||
|
# Reverse: Mesh output to Object input is NOT valid
|
||||||
|
mesh_out = DummySocket("Mesh", "SLICESOCK_Mesh")
|
||||||
|
mesh_out.is_output = True
|
||||||
|
obj_in = DummySocket("Object", "NodeSocketObject")
|
||||||
|
obj_in.is_output = False
|
||||||
|
self.assertFalse(self.tree.valid_link(mesh_out, obj_in))
|
||||||
|
|
||||||
|
def test_valid_link_object_list_to_mesh(self):
|
||||||
|
list_obj_out = DummySocket("Objects", "NodeSocketObject", display_shape="LIST")
|
||||||
|
list_obj_out.is_output = True
|
||||||
|
mesh_in = DummySocket("Mesh", "SLICESOCK_Mesh")
|
||||||
|
mesh_in.is_output = False
|
||||||
|
mesh_in.node = DummyNode("SLICENODE_SliceShells")
|
||||||
|
mesh_in.node.list_socket_mapping = [("Mesh", "Paths")]
|
||||||
|
|
||||||
|
# Target accepts lists -> allowed
|
||||||
|
self.assertTrue(self.tree.valid_link(list_obj_out, mesh_in))
|
||||||
|
|
||||||
|
# SeamPlacement cutting mesh rejects lists -> rejected
|
||||||
|
seam_mesh_in = DummySocket("Mesh", "SLICESOCK_Mesh")
|
||||||
|
seam_mesh_in.is_output = False
|
||||||
|
seam_mesh_in.node = DummyNode("SLICENODE_SeamPlacement")
|
||||||
|
self.assertFalse(self.tree.valid_link(list_obj_out, seam_mesh_in))
|
||||||
|
|
||||||
|
def test_resolve_input_implicit_get_mesh_single_object(self):
|
||||||
|
from NodeSlicer.infra.blender.blender_slice_nodes_generic import resolve_input, execution_cache
|
||||||
|
|
||||||
|
parent_obj = DummyObject("ParentCube")
|
||||||
|
child_obj1 = DummyObject("Child1")
|
||||||
|
child_obj2 = DummyObject("Child2")
|
||||||
|
parent_obj.children = [child_obj1, child_obj2]
|
||||||
|
|
||||||
|
# Source node outputting parent_obj
|
||||||
|
src_node = DummyNode("SLICENODE_InputObject")
|
||||||
|
src_node.name = "InputObject"
|
||||||
|
src_out = DummySocket("Object", "NodeSocketObject")
|
||||||
|
src_out.node = src_node
|
||||||
|
src_node.outputs = DummySocketCollection([src_out])
|
||||||
|
src_node.evaluate_output = lambda socket_name, **kw: parent_obj
|
||||||
|
|
||||||
|
# Target node with SLICESOCK_Mesh input
|
||||||
|
dst_node = DummyNode("SLICENODE_SliceShells")
|
||||||
|
dst_in = DummySocket("Mesh", "SLICESOCK_Mesh")
|
||||||
|
dst_in.node = dst_node
|
||||||
|
dst_node.inputs = DummySocketCollection([dst_in])
|
||||||
|
|
||||||
|
# Link them
|
||||||
|
dst_in.is_linked = True
|
||||||
|
link = DummyLink(src_out, dst_in)
|
||||||
|
dst_in.links = [link]
|
||||||
|
|
||||||
|
with execution_cache() as cache:
|
||||||
|
result = resolve_input(dst_in, cache=cache)
|
||||||
|
|
||||||
|
self.assertIsInstance(result, SlicingCollection)
|
||||||
|
self.assertEqual(result.name, "ParentCube")
|
||||||
|
|
||||||
|
def test_resolve_input_implicit_get_mesh_object_list(self):
|
||||||
|
from NodeSlicer.infra.blender.blender_slice_nodes_generic import resolve_input, execution_cache
|
||||||
|
|
||||||
|
obj1 = DummyObject("Obj1")
|
||||||
|
obj2 = DummyObject("Obj2")
|
||||||
|
|
||||||
|
src_node = DummyNode("SLICENODE_ObjectsToList")
|
||||||
|
src_node.name = "ObjectsToList"
|
||||||
|
src_out = DummySocket("Objects", "NodeSocketObject", display_shape="LIST")
|
||||||
|
src_out.node = src_node
|
||||||
|
src_node.outputs = DummySocketCollection([src_out])
|
||||||
|
src_node.evaluate_output = lambda socket_name, **kw: [obj1, obj2]
|
||||||
|
|
||||||
|
dst_node = DummyNode("SLICENODE_SliceShells")
|
||||||
|
dst_in = DummySocket("Mesh", "SLICESOCK_Mesh", display_shape="LIST")
|
||||||
|
dst_in.node = dst_node
|
||||||
|
dst_node.inputs = DummySocketCollection([dst_in])
|
||||||
|
|
||||||
|
dst_in.is_linked = True
|
||||||
|
dst_in.links = [DummyLink(src_out, dst_in)]
|
||||||
|
|
||||||
|
with execution_cache() as cache:
|
||||||
|
result = resolve_input(dst_in, cache=cache)
|
||||||
|
|
||||||
|
self.assertIsInstance(result, list)
|
||||||
|
self.assertEqual(len(result), 2)
|
||||||
|
self.assertIsInstance(result[0], SlicingCollection)
|
||||||
|
self.assertIsInstance(result[1], SlicingCollection)
|
||||||
|
self.assertEqual(result[0].name, "Obj1")
|
||||||
|
self.assertEqual(result[1].name, "Obj2")
|
||||||
|
|
||||||
|
def test_resolve_input_already_mesh_is_not_reconverted(self):
|
||||||
|
from NodeSlicer.infra.blender.blender_slice_nodes_generic import resolve_input, execution_cache
|
||||||
|
|
||||||
|
sc = SlicingCollection([], "ExistingMesh")
|
||||||
|
|
||||||
|
src_node = DummyNode("SLICENODE_GetMesh")
|
||||||
|
src_node.name = "GetMesh"
|
||||||
|
src_out = DummySocket("Mesh", "SLICESOCK_Mesh")
|
||||||
|
src_out.node = src_node
|
||||||
|
src_node.outputs = DummySocketCollection([src_out])
|
||||||
|
src_node.evaluate_output = lambda socket_name, **kw: sc
|
||||||
|
|
||||||
|
dst_node = DummyNode("SLICENODE_SliceShells")
|
||||||
|
dst_in = DummySocket("Mesh", "SLICESOCK_Mesh")
|
||||||
|
dst_in.node = dst_node
|
||||||
|
dst_node.inputs = DummySocketCollection([dst_in])
|
||||||
|
|
||||||
|
dst_in.is_linked = True
|
||||||
|
dst_in.links = [DummyLink(src_out, dst_in)]
|
||||||
|
|
||||||
|
with execution_cache() as cache:
|
||||||
|
result = resolve_input(dst_in, cache=cache)
|
||||||
|
|
||||||
|
self.assertIs(result, sc)
|
||||||
|
|
||||||
|
def test_resolve_input_object_to_object_socket_not_converted(self):
|
||||||
|
from NodeSlicer.infra.blender.blender_slice_nodes_generic import resolve_input, execution_cache
|
||||||
|
|
||||||
|
obj = DummyObject("MyObj")
|
||||||
|
|
||||||
|
src_node = DummyNode("SLICENODE_InputObject")
|
||||||
|
src_node.name = "InputObject"
|
||||||
|
src_out = DummySocket("Object", "NodeSocketObject")
|
||||||
|
src_out.node = src_node
|
||||||
|
src_node.outputs = DummySocketCollection([src_out])
|
||||||
|
src_node.evaluate_output = lambda socket_name, **kw: obj
|
||||||
|
|
||||||
|
dst_node = DummyNode("SLICENODE_ObjectsToList")
|
||||||
|
dst_in = DummySocket("Object", "NodeSocketObject")
|
||||||
|
dst_in.node = dst_node
|
||||||
|
dst_node.inputs = DummySocketCollection([dst_in])
|
||||||
|
|
||||||
|
dst_in.is_linked = True
|
||||||
|
dst_in.links = [DummyLink(src_out, dst_in)]
|
||||||
|
|
||||||
|
with execution_cache() as cache:
|
||||||
|
result = resolve_input(dst_in, cache=cache)
|
||||||
|
|
||||||
|
self.assertIs(result, obj)
|
||||||
|
|
||||||
|
def test_implicit_get_mesh_caching(self):
|
||||||
|
from NodeSlicer.infra.blender.blender_slice_nodes_generic import resolve_input, execution_cache
|
||||||
|
|
||||||
|
obj = DummyObject("SharedObj")
|
||||||
|
|
||||||
|
src_node = DummyNode("SLICENODE_InputObject")
|
||||||
|
src_node.name = "InputObject"
|
||||||
|
src_out = DummySocket("Object", "NodeSocketObject")
|
||||||
|
src_out.node = src_node
|
||||||
|
src_node.outputs = DummySocketCollection([src_out])
|
||||||
|
src_node.evaluate_output = lambda socket_name, **kw: obj
|
||||||
|
|
||||||
|
dst_node1 = DummyNode("SLICENODE_SliceShells")
|
||||||
|
dst_in1 = DummySocket("Mesh", "SLICESOCK_Mesh")
|
||||||
|
dst_in1.node = dst_node1
|
||||||
|
dst_node1.inputs = DummySocketCollection([dst_in1])
|
||||||
|
dst_in1.is_linked = True
|
||||||
|
dst_in1.links = [DummyLink(src_out, dst_in1)]
|
||||||
|
|
||||||
|
dst_node2 = DummyNode("SLICENODE_JoinMesh")
|
||||||
|
dst_in2 = DummySocket("Mesh", "SLICESOCK_Mesh")
|
||||||
|
dst_in2.node = dst_node2
|
||||||
|
dst_node2.inputs = DummySocketCollection([dst_in2])
|
||||||
|
dst_in2.is_linked = True
|
||||||
|
dst_in2.links = [DummyLink(src_out, dst_in2)]
|
||||||
|
|
||||||
|
with execution_cache() as cache:
|
||||||
|
res1 = resolve_input(dst_in1, cache=cache)
|
||||||
|
res2 = resolve_input(dst_in2, cache=cache)
|
||||||
|
|
||||||
|
self.assertIs(res1, res2)
|
||||||
|
|
||||||
|
def test_explicit_get_mesh_include_children(self):
|
||||||
|
from NodeSlicer.core.nodeslicer.nodes.mesh import SLICENODE_GetMesh
|
||||||
|
|
||||||
|
parent_obj = DummyObject("Parent")
|
||||||
|
child_obj = DummyObject("Child")
|
||||||
|
parent_obj.children = [child_obj]
|
||||||
|
|
||||||
|
node_with = SLICENODE_GetMesh()
|
||||||
|
node_with.include_children = True
|
||||||
|
node_with.resolve = lambda label: parent_obj
|
||||||
|
res_with = node_with.evaluate_output("Mesh")
|
||||||
|
self.assertIsInstance(res_with, SlicingCollection)
|
||||||
|
|
||||||
|
node_without = SLICENODE_GetMesh()
|
||||||
|
node_without.include_children = False
|
||||||
|
node_without.resolve = lambda label: parent_obj
|
||||||
|
res_without = node_without.evaluate_output("Mesh")
|
||||||
|
self.assertIsInstance(res_without, SlicingCollection)
|
||||||
|
|
||||||
|
def test_implicit_get_mesh_passes_all_children_to_slicing_collection(self):
|
||||||
|
from unittest.mock import patch
|
||||||
|
from NodeSlicer.infra.blender.blender_slice_nodes_generic import object_to_mesh
|
||||||
|
|
||||||
|
parent_obj = DummyObject("Parent")
|
||||||
|
c1 = DummyObject("C1")
|
||||||
|
c2 = DummyObject("C2")
|
||||||
|
gc1 = DummyObject("GC1")
|
||||||
|
c2.children = [gc1]
|
||||||
|
parent_obj.children = [c1, c2]
|
||||||
|
|
||||||
|
with patch.object(SlicingCollection, "__init__", return_value=None) as mock_init:
|
||||||
|
object_to_mesh(parent_obj, include_children=True)
|
||||||
|
self.assertTrue(mock_init.called)
|
||||||
|
family, name = mock_init.call_args[0]
|
||||||
|
self.assertEqual(name, "Parent")
|
||||||
|
self.assertEqual(set(family), {parent_obj, c1, c2, gc1})
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -1127,7 +1127,7 @@ class TestPdfExporter(unittest.TestCase):
|
||||||
updated_sp = _assign_properties_to_item(sp, props)
|
updated_sp = _assign_properties_to_item(sp, props)
|
||||||
self.assertEqual(updated_sp.pdf_object_properties, props)
|
self.assertEqual(updated_sp.pdf_object_properties, props)
|
||||||
|
|
||||||
# Verify dynamic socket color logic
|
# Verify socket colors remain static and do not adapt to upstream sockets
|
||||||
from NodeSlicer.core.nodeslicer.sockets import SLICESOCK_Geometry, SLICESOCK_Paths, SLICESOCK_Mesh
|
from NodeSlicer.core.nodeslicer.sockets import SLICESOCK_Geometry, SLICESOCK_Paths, SLICESOCK_Mesh
|
||||||
class DummyLink:
|
class DummyLink:
|
||||||
def __init__(self, from_sock):
|
def __init__(self, from_sock):
|
||||||
|
|
@ -1150,16 +1150,15 @@ class TestPdfExporter(unittest.TestCase):
|
||||||
out_geom = SLICESOCK_Geometry()
|
out_geom = SLICESOCK_Geometry()
|
||||||
out_geom.is_output = True
|
out_geom.is_output = True
|
||||||
|
|
||||||
# Input socket draws Paths color (pink)
|
# Sockets retain their native bl_color, not upstream Paths color (pink)
|
||||||
self.assertEqual(in_geom.draw_color(None, dummy_node), paths_out.bl_color)
|
self.assertEqual(in_geom.draw_color(None, dummy_node), in_geom.bl_color)
|
||||||
# Output socket on same node inherits Paths color (pink) from input socket
|
self.assertEqual(out_geom.draw_color(None, dummy_node), out_geom.bl_color)
|
||||||
self.assertEqual(out_geom.draw_color(None, dummy_node), paths_out.bl_color)
|
|
||||||
|
|
||||||
# Switch upstream to Mesh (cyan)
|
# Switch upstream to Mesh (teal) - sockets still retain their native bl_color
|
||||||
mesh_out = SLICESOCK_Mesh()
|
mesh_out = SLICESOCK_Mesh()
|
||||||
in_geom.links = [DummyLink(mesh_out)]
|
in_geom.links = [DummyLink(mesh_out)]
|
||||||
self.assertEqual(in_geom.draw_color(None, dummy_node), mesh_out.bl_color)
|
self.assertEqual(in_geom.draw_color(None, dummy_node), in_geom.bl_color)
|
||||||
self.assertEqual(out_geom.draw_color(None, dummy_node), mesh_out.bl_color)
|
self.assertEqual(out_geom.draw_color(None, dummy_node), out_geom.bl_color)
|
||||||
|
|
||||||
|
|
||||||
def test_export_pdf_object_properties_table(self):
|
def test_export_pdf_object_properties_table(self):
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue