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
|
||||
gcode_input: list[str] = []
|
||||
for input in self.inputs:
|
||||
gc: str = str(resolve_input(input))
|
||||
gcode_input += [gc] or [""]
|
||||
val = resolve_input(input)
|
||||
if val is not None:
|
||||
gc = str(val)
|
||||
if gc:
|
||||
gcode_input.append(gc)
|
||||
return "\n".join(gcode_input)
|
||||
|
||||
@register_class
|
||||
|
|
|
|||
|
|
@ -590,12 +590,10 @@ class SLICENODE_TransformGeometry(SliceNode):
|
|||
self.add_output("SLICESOCK_Geometry", "Geometry")
|
||||
|
||||
def evaluate_output(self, socket_name: str, **_kwargs) -> Any:
|
||||
if socket_name not in ("Geometry", "Mesh"):
|
||||
if socket_name not in ("Geometry"):
|
||||
return None
|
||||
|
||||
geom_data: Any = self.resolve("Geometry")
|
||||
if geom_data is None:
|
||||
geom_data = self.resolve("Mesh")
|
||||
if geom_data is None:
|
||||
return None
|
||||
|
||||
|
|
|
|||
|
|
@ -19,17 +19,10 @@ class LabelNodeSocket(SliceNodeSocket):
|
|||
class InputNodeSocket(SliceNodeSocket):
|
||||
def draw(self, context: Any, layout: Any, node: Any, text: str | None):
|
||||
assert layout
|
||||
if node and isinstance(node, bpy.types.NodeCustomGroup):
|
||||
if self.is_output or self.is_linked:
|
||||
layout.label(text=text or self.name)
|
||||
else:
|
||||
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):
|
||||
layout.prop(self, "value", text=text or self.name)
|
||||
|
|
|
|||
|
|
@ -150,6 +150,11 @@ def depsgraph_update_handler(scene, depsgraph):
|
|||
node._update_outputs()
|
||||
except Exception:
|
||||
pass
|
||||
if hasattr(node, "_update_multi_groups"):
|
||||
try:
|
||||
node._update_multi_groups()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@register_class
|
||||
|
|
|
|||
|
|
@ -64,6 +64,39 @@ class SliceNodeSocket(NodeSocket, AutoBlIdName):
|
|||
|
||||
from ...registry import get_label_by_name, register_class
|
||||
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:
|
||||
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_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()
|
||||
|
||||
|
|
@ -426,13 +465,13 @@ def resolve_input(
|
|||
|
||||
if not socket.is_linked:
|
||||
try:
|
||||
val = getattr(socket, "default_value", None)
|
||||
if val is not None: return val
|
||||
val = getattr(socket, "value", None)
|
||||
if val is not None and val != "": return val
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
val = getattr(socket, "value", None)
|
||||
val = getattr(socket, "default_value", None)
|
||||
if val is not None: return val
|
||||
except Exception:
|
||||
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