up
This commit is contained in:
parent
7228613ae4
commit
b67a8d9755
6 changed files with 195 additions and 17 deletions
|
|
@ -184,8 +184,11 @@ class SLICENODE_JoinGcode(SliceNodeJoiner):
|
||||||
from ....infra.blender.blender_slice_nodes_generic import resolve_input
|
from ....infra.blender.blender_slice_nodes_generic import resolve_input
|
||||||
gcode_input: list[str] = []
|
gcode_input: list[str] = []
|
||||||
for input in self.inputs:
|
for input in self.inputs:
|
||||||
gc: str = str(resolve_input(input))
|
val = resolve_input(input)
|
||||||
gcode_input += [gc] or [""]
|
if val is not None:
|
||||||
|
gc = str(val)
|
||||||
|
if gc:
|
||||||
|
gcode_input.append(gc)
|
||||||
return "\n".join(gcode_input)
|
return "\n".join(gcode_input)
|
||||||
|
|
||||||
@register_class
|
@register_class
|
||||||
|
|
|
||||||
|
|
@ -590,12 +590,10 @@ class SLICENODE_TransformGeometry(SliceNode):
|
||||||
self.add_output("SLICESOCK_Geometry", "Geometry")
|
self.add_output("SLICESOCK_Geometry", "Geometry")
|
||||||
|
|
||||||
def evaluate_output(self, socket_name: str, **_kwargs) -> Any:
|
def evaluate_output(self, socket_name: str, **_kwargs) -> Any:
|
||||||
if socket_name not in ("Geometry", "Mesh"):
|
if socket_name not in ("Geometry"):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
geom_data: Any = self.resolve("Geometry")
|
geom_data: Any = self.resolve("Geometry")
|
||||||
if geom_data is None:
|
|
||||||
geom_data = self.resolve("Mesh")
|
|
||||||
if geom_data is None:
|
if geom_data is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,17 +19,10 @@ class LabelNodeSocket(SliceNodeSocket):
|
||||||
class InputNodeSocket(SliceNodeSocket):
|
class InputNodeSocket(SliceNodeSocket):
|
||||||
def draw(self, context: Any, layout: Any, node: Any, text: str | None):
|
def draw(self, context: Any, layout: Any, node: Any, text: str | None):
|
||||||
assert layout
|
assert layout
|
||||||
if node and isinstance(node, bpy.types.NodeCustomGroup):
|
|
||||||
if self.is_output or self.is_linked:
|
if self.is_output or self.is_linked:
|
||||||
layout.label(text=text or self.name)
|
layout.label(text=text or self.name)
|
||||||
else:
|
else:
|
||||||
self._draw_prop(text, layout)
|
self._draw_prop(text, layout)
|
||||||
return
|
|
||||||
|
|
||||||
if not self.is_output and self.is_linked:
|
|
||||||
layout.label(text=text or self.name)
|
|
||||||
else:
|
|
||||||
self._draw_prop(text, layout)
|
|
||||||
|
|
||||||
def _draw_prop(self, text, layout):
|
def _draw_prop(self, text, layout):
|
||||||
layout.prop(self, "value", text=text or self.name)
|
layout.prop(self, "value", text=text or self.name)
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,11 @@ def depsgraph_update_handler(scene, depsgraph):
|
||||||
node._update_outputs()
|
node._update_outputs()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
if hasattr(node, "_update_multi_groups"):
|
||||||
|
try:
|
||||||
|
node._update_multi_groups()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@register_class
|
@register_class
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,39 @@ class SliceNodeSocket(NodeSocket, AutoBlIdName):
|
||||||
|
|
||||||
from ...registry import get_label_by_name, register_class
|
from ...registry import get_label_by_name, register_class
|
||||||
class SliceNode(Node, AutoBlIdName):
|
class SliceNode(Node, AutoBlIdName):
|
||||||
|
def get(self, key: str, default: Any = None) -> Any:
|
||||||
|
try:
|
||||||
|
return super().get(key, default) # type: ignore
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
pass
|
||||||
|
if hasattr(self, "_data") and isinstance(self._data, dict):
|
||||||
|
return self._data.get(key, default)
|
||||||
|
return getattr(self, key, default)
|
||||||
|
|
||||||
|
def __setitem__(self, key: str, value: Any) -> None:
|
||||||
|
try:
|
||||||
|
super().__setitem__(key, value) # type: ignore
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
if not hasattr(self, "_data") or self._data is None:
|
||||||
|
self._data = {}
|
||||||
|
self._data[key] = value
|
||||||
|
|
||||||
|
def __getitem__(self, key: str) -> Any:
|
||||||
|
try:
|
||||||
|
return super().__getitem__(key) # type: ignore
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
if hasattr(self, "_data") and isinstance(self._data, dict):
|
||||||
|
return self._data[key]
|
||||||
|
raise KeyError(key)
|
||||||
|
|
||||||
|
def __contains__(self, key: str) -> bool:
|
||||||
|
try:
|
||||||
|
return super().__contains__(key) # type: ignore
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
if hasattr(self, "_data") and isinstance(self._data, dict):
|
||||||
|
return key in self._data
|
||||||
|
return False
|
||||||
|
|
||||||
def update(self) -> None:
|
def update(self) -> None:
|
||||||
self._update_multi_groups()
|
self._update_multi_groups()
|
||||||
|
|
||||||
|
|
@ -128,7 +161,13 @@ class SliceNode(Node, AutoBlIdName):
|
||||||
|
|
||||||
def get_ins(): return [i for i in self.inputs if i.identifier.startswith(f"{group_id}_ms_in_")]
|
def get_ins(): return [i for i in self.inputs if i.identifier.startswith(f"{group_id}_ms_in_")]
|
||||||
def get_outs(): return [o for o in self.outputs if o.identifier.startswith(f"{group_id}_ms_out_")]
|
def get_outs(): return [o for o in self.outputs if o.identifier.startswith(f"{group_id}_ms_out_")]
|
||||||
def is_active(s): return s.is_linked or bool(getattr(s, 'value', False))
|
def is_active(s):
|
||||||
|
if getattr(s, 'is_linked', False):
|
||||||
|
return True
|
||||||
|
val = getattr(s, 'value', None)
|
||||||
|
if val is None or val == "":
|
||||||
|
val = getattr(s, 'default_value', None)
|
||||||
|
return bool(val)
|
||||||
|
|
||||||
ins = get_ins()
|
ins = get_ins()
|
||||||
|
|
||||||
|
|
@ -426,13 +465,13 @@ def resolve_input(
|
||||||
|
|
||||||
if not socket.is_linked:
|
if not socket.is_linked:
|
||||||
try:
|
try:
|
||||||
val = getattr(socket, "default_value", None)
|
val = getattr(socket, "value", None)
|
||||||
if val is not None: return val
|
if val is not None and val != "": return val
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
val = getattr(socket, "value", None)
|
val = getattr(socket, "default_value", None)
|
||||||
if val is not None: return val
|
if val is not None: return val
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
140
tests/test_multi_groups.py
Normal file
140
tests/test_multi_groups.py
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
sys.path.append(str(PROJECT_ROOT.parent))
|
||||||
|
|
||||||
|
class DummyBase:
|
||||||
|
def _get_data_dict(self):
|
||||||
|
if not hasattr(self, "_data") or self._data is None:
|
||||||
|
self._data = {}
|
||||||
|
return self._data
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self._get_data_dict()[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self._get_data_dict()[key] = value
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return key in self._get_data_dict()
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
return self._get_data_dict().get(key, default)
|
||||||
|
|
||||||
|
mock_types = MagicMock()
|
||||||
|
mock_types.NodeSocket = type("NodeSocket", (DummyBase,), {})
|
||||||
|
mock_types.Node = DummyBase
|
||||||
|
mock_types.NodeCustomGroup = type("NodeCustomGroup", (DummyBase,), {})
|
||||||
|
mock_types.NodeTree = type("NodeTree", (DummyBase,), {})
|
||||||
|
mock_types.PropertyGroup = type("PropertyGroup", (DummyBase,), {})
|
||||||
|
mock_types.Operator = type("Operator", (DummyBase,), {})
|
||||||
|
mock_types.Panel = type("Panel", (DummyBase,), {})
|
||||||
|
mock_types.Menu = type("Menu", (DummyBase,), {})
|
||||||
|
mock_types.Object = type("Object", (DummyBase,), {})
|
||||||
|
mock_types.Mesh = type("Mesh", (DummyBase,), {})
|
||||||
|
|
||||||
|
mock_bpy = MagicMock()
|
||||||
|
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()
|
||||||
|
sys.modules['bpy.utils.previews'] = MagicMock()
|
||||||
|
sys.modules['bpy.app'] = MagicMock()
|
||||||
|
sys.modules['bpy.app.handlers'] = MagicMock()
|
||||||
|
|
||||||
|
# Socket and node testing mocks
|
||||||
|
class TestNodeSocket:
|
||||||
|
def __init__(self, name="", identifier="", node=None):
|
||||||
|
self.name = name
|
||||||
|
self.identifier = identifier or name
|
||||||
|
self.is_linked = False
|
||||||
|
self.links = []
|
||||||
|
self.default = None
|
||||||
|
self.default_value = ""
|
||||||
|
self.value = ""
|
||||||
|
self.hide = False
|
||||||
|
self.display_shape = ""
|
||||||
|
self.node = node
|
||||||
|
|
||||||
|
class TestSocketCollection(list):
|
||||||
|
def __init__(self, node=None):
|
||||||
|
super().__init__()
|
||||||
|
self.node = node
|
||||||
|
|
||||||
|
def new(self, type_name, name, identifier=""):
|
||||||
|
sock = TestNodeSocket(name, identifier or name, node=self.node)
|
||||||
|
self.append(sock)
|
||||||
|
return sock
|
||||||
|
|
||||||
|
def remove(self, sock):
|
||||||
|
if sock in self:
|
||||||
|
super().remove(sock)
|
||||||
|
|
||||||
|
def get(self, name, default=None):
|
||||||
|
for item in self:
|
||||||
|
if item.name == name or item.identifier == name:
|
||||||
|
return item
|
||||||
|
return default
|
||||||
|
|
||||||
|
from NodeSlicer.infra.blender.blender_slice_nodes_generic import SliceNode, resolve_input
|
||||||
|
from NodeSlicer.core.nodeslicer.nodes.gcode import SLICENODE_JoinGcode
|
||||||
|
|
||||||
|
class TestMultiGroupsManualValue(unittest.TestCase):
|
||||||
|
def test_join_gcode_multi_groups_manual_value(self):
|
||||||
|
node = SLICENODE_JoinGcode()
|
||||||
|
node.inputs = TestSocketCollection(node)
|
||||||
|
node.outputs = TestSocketCollection(node)
|
||||||
|
|
||||||
|
node.add_output("NodeSocketString", "Gcode")
|
||||||
|
node.add_multi_socket_group("NodeSocketString", name="Gcode")
|
||||||
|
|
||||||
|
# Initial state should have 1 input socket
|
||||||
|
self.assertEqual(len(node.inputs), 1)
|
||||||
|
|
||||||
|
# Entering a manual value in socket 0
|
||||||
|
node.inputs[0].value = "G1 X10 Y10 F1200"
|
||||||
|
node._update_multi_groups()
|
||||||
|
|
||||||
|
# Should trigger adding socket 1
|
||||||
|
self.assertEqual(len(node.inputs), 2)
|
||||||
|
|
||||||
|
# Entering a manual value in socket 1
|
||||||
|
node.inputs[1].value = "G1 X20 Y20"
|
||||||
|
node._update_multi_groups()
|
||||||
|
|
||||||
|
# Should trigger adding socket 2
|
||||||
|
self.assertEqual(len(node.inputs), 3)
|
||||||
|
|
||||||
|
# Evaluate output should join manual values
|
||||||
|
joined = node.evaluate_output("Gcode")
|
||||||
|
self.assertEqual(joined, "G1 X10 Y10 F1200\nG1 X20 Y20")
|
||||||
|
|
||||||
|
# Clearing socket 1 manual value
|
||||||
|
node.inputs[1].value = ""
|
||||||
|
node._update_multi_groups()
|
||||||
|
|
||||||
|
# Trailing inactive sockets cleaned up
|
||||||
|
self.assertEqual(len(node.inputs), 2)
|
||||||
|
|
||||||
|
def test_join_gcode_default_value_trigger(self):
|
||||||
|
node = SLICENODE_JoinGcode()
|
||||||
|
node.inputs = TestSocketCollection(node)
|
||||||
|
node.outputs = TestSocketCollection(node)
|
||||||
|
|
||||||
|
node.add_output("NodeSocketString", "Gcode")
|
||||||
|
node.add_multi_socket_group("NodeSocketString", name="Gcode")
|
||||||
|
|
||||||
|
# Set default_value on native socket
|
||||||
|
node.inputs[0].default_value = "M104 S200"
|
||||||
|
node._update_multi_groups()
|
||||||
|
|
||||||
|
self.assertEqual(len(node.inputs), 2)
|
||||||
|
self.assertEqual(node.evaluate_output("Gcode"), "M104 S200")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Loading…
Add table
Reference in a new issue