refactor sockets

This commit is contained in:
Nicolas Predella 2026-08-25 13:57:44 +03:00
parent 29f7b71be1
commit 2bfc1ef781
14 changed files with 213 additions and 191 deletions

View file

@ -34,16 +34,29 @@ class SliceIntrinsics:
class InputNode(SliceNode):
input_type: str = ""
socket_type: str = ""
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if "bl_label" not in cls.__dict__: cls.bl_label = cls.input_type
def init(self, context) -> None:
self.add_output(f"SLICESOCK_{self.bl_label}")
stype = self.socket_type or (f"NodeSocket{self.bl_label}" if not self.bl_label.startswith("NodeSocket") else self.bl_label)
self.add_output(stype)
def draw_buttons(self, context, layout):
assert layout
if self.outputs:
sock = self.outputs[0]
if hasattr(sock, "default_value"):
layout.prop(sock, "default_value", text="")
elif hasattr(sock, "value"):
layout.prop(sock, "value", text="")
def evaluate_output(self, socket_name: str, **_kwargs):
return self.outputs.get(socket_name).value
out = self.outputs.get(socket_name) or (self.outputs[0] if self.outputs else None)
if out is None: return None
return getattr(out, "value", getattr(out, "default_value", None))
class MathNode(SliceNode):
bl_label = "Float Math"
@ -72,12 +85,13 @@ class MathNode(SliceNode):
math_type: TypeAlias = float
socket_label: str = "Float"
socket_type: str = "NodeSocketFloat"
def init(self, context):
self.add_output(f"SLICESOCK_{self.socket_label}Ro", "Value")
self.add_output(self.socket_type, "Value")
self.add_input(f"SLICESOCK_{self.socket_label}", "Value")
self.add_input(f"SLICESOCK_{self.socket_label}", "Value")
self.add_input(self.socket_type, "Value")
self.add_input(self.socket_type, "Value")
def draw_buttons(self, context, layout):
assert layout

View file

@ -63,9 +63,9 @@ class SLICENODE_AssignMetadata(SliceNode):
bl_label = "Assign Metadata"
def init(self, context):
self.add_input("SLICESOCK_String", "Name")
self.add_input("SLICESOCK_String", "Filament Type")
self.add_input("SLICESOCK_String", "Printer Model")
self.add_input("NodeSocketString", "Name")
self.add_input("NodeSocketString", "Filament Type")
self.add_input("NodeSocketString", "Printer Model")
self.add_output("SLICESOCK_GcodeRo", "Gcode")
def evaluate_output(self, socket_name: str, **_kwargs) -> str:
@ -87,8 +87,8 @@ class SLICENODE_GcodeRegexReplace(SliceNode):
def init(self, context):
self.add_input("SLICESOCK_Gcode", "Gcode")
self.add_input("SLICESOCK_String", "Find")
self.add_input("SLICESOCK_String", "Replace")
self.add_input("NodeSocketString", "Find")
self.add_input("NodeSocketString", "Replace")
self.add_output("SLICESOCK_GcodeRo", "Gcode")
def evaluate_output(self, socket_name: str, **_kwargs) -> str:

View file

@ -10,27 +10,33 @@ from .base import InputNode, SliceNode, SliceIntrinsics
@register_class
class SLICENODE_InputString(InputNode):
input_type = "String"
bl_label = "String"
socket_type = "NodeSocketString"
@register_class
class SLICENODE_InputFloat(InputNode):
input_type = "Float"
bl_label = "Float"
socket_type = "NodeSocketFloat"
@register_class
class SLICENODE_InputInt(InputNode):
input_type = "Int"
bl_label = "Integer"
socket_type = "NodeSocketInt"
@register_class
class SLICENODE_InputVectorRotation(InputNode):
input_type = "VectorRotation"
bl_label = "Vector Rotation"
socket_type = "NodeSocketRotation"
@register_class
class SLICENODE_InputVector(InputNode):
input_type = "Vector"
bl_label = "Vector"
socket_type = "NodeSocketVector"
@register_class
class SLICENODE_InputObject(InputNode):
input_type = "Object"
bl_label = "Object"
socket_type = "NodeSocketObject"
bl_icon = "OBJECT_DATA"
@register_class
@ -70,7 +76,7 @@ class SLICENODE_CollectionObjects(SliceNode):
# Add new outputs
for name in expected_names:
if name not in self.outputs:
self.add_output("SLICESOCK_ObjectRo", name, identifier=name)
self.add_output("NodeSocketObject", name, identifier=name)
def evaluate_output(self, socket_name: str, **kwargs) -> Any:
if self.collection and socket_name in bpy.data.objects:
@ -82,8 +88,8 @@ class SLICENODE_WipeTower(SliceNode):
bl_label = "Wipe Tower"
def init(self, context):
self.add_input("SLICESOCK_Float", "X Position").value = 170.0
self.add_input("SLICESOCK_Float", "Y Position").value = 140.0
self.add_input("NodeSocketFloat", "X Position").value = 170.0
self.add_input("NodeSocketFloat", "Y Position").value = 140.0
self.add_output("SLICESOCK_SettingsRo", "Settings")
def evaluate_output(self, socket_name: str, **_kwargs) -> Any:
@ -108,9 +114,9 @@ class SLICENODE_OffsetObject(SliceNode):
bl_label = "Offset Object"
def init(self, context):
self.add_input("SLICESOCK_Float", "X Offset").value = 0.0
self.add_input("SLICESOCK_Float", "Y Offset").value = 0.0
self.add_input("SLICESOCK_Float", "Z Offset").value = 0.0
self.add_input("NodeSocketFloat", "X Offset").value = 0.0
self.add_input("NodeSocketFloat", "Y Offset").value = 0.0
self.add_input("NodeSocketFloat", "Z Offset").value = 0.0
self.add_output("SLICESOCK_SettingsRo", "Settings")
def evaluate_output(self, socket_name: str, **_kwargs) -> Any:
@ -250,11 +256,11 @@ class SLICENODE_InputPrinter(SliceNode):
def init(self, context):
self.add_output("SLICESOCK_Printer")
self.add_input("SLICESOCK_String", "Host").value = "localhost"
self.add_input("SLICESOCK_Int", "Port").value = 80
self.add_input("SLICESOCK_String", "Prefix")
self.add_input("SLICESOCK_String", "Username")
self.add_input("SLICESOCK_String", "Password")
self.add_input("NodeSocketString", "Host").value = "localhost"
self.add_input("NodeSocketInt", "Port").value = 80
self.add_input("NodeSocketString", "Prefix")
self.add_input("NodeSocketString", "Username")
self.add_input("NodeSocketString", "Password")
def draw_buttons(self, context, layout):
layout.prop(self, "printer_label") # pyright: ignore[reportOptionalMemberAccess]
@ -280,14 +286,14 @@ class SLICENODE_InputIntrinsics(SliceNode):
def init(self, context):
self.add_output("SLICESOCK_Intrinsics")
self.add_input("SLICESOCK_Float", "Bed X").value = 250.0
self.add_input("SLICESOCK_Float", "Bed Y").value = 210.0
self.add_input("SLICESOCK_Float", "Feedrate").value = 1200.0
self.add_input("SLICESOCK_Float", "Travel Feedrate").value = 2400.0
self.add_input("SLICESOCK_Float", "Extrusion Rate").value = 0.035
self.add_input("SLICESOCK_Float", "Retraction").value = 0.0
self.add_input("SLICESOCK_Float", "Retraction Feedrate").value = 0.0
self.add_input("SLICESOCK_Float", "Lift").value = 0.0
self.add_input("NodeSocketFloat", "Bed X").value = 250.0
self.add_input("NodeSocketFloat", "Bed Y").value = 210.0
self.add_input("NodeSocketFloat", "Feedrate").value = 1200.0
self.add_input("NodeSocketFloat", "Travel Feedrate").value = 2400.0
self.add_input("NodeSocketFloat", "Extrusion Rate").value = 0.035
self.add_input("NodeSocketFloat", "Retraction").value = 0.0
self.add_input("NodeSocketFloat", "Retraction Feedrate").value = 0.0
self.add_input("NodeSocketFloat", "Lift").value = 0.0
def evaluate_output(self, socket_name: str, **_kwargs) -> SliceIntrinsics:
ret_fr = 0.0

View file

@ -7,9 +7,11 @@ class SLICENODE_FloatMath(MathNode):
bl_label = "Math"
math_type = float
socket_label: str = "Float"
socket_type: str = "NodeSocketFloat"
@register_class
class SLICENODE_IntMath(MathNode):
bl_label = "Integer Math"
math_type = int
socket_label: str = "Int"
socket_type: str = "NodeSocketInt"

View file

@ -14,7 +14,7 @@ class SLICENODE_GetMesh(SliceNode):
include_children: NfBoolProperty(name="Include Children", default=True) # pyright: ignore[reportInvalidTypeForm]
def init(self, context):
self.add_input("SLICESOCK_Object")
self.add_input("NodeSocketObject")
self.add_output("SLICESOCK_Mesh")
def draw_buttons(self, context, layout):
@ -89,8 +89,8 @@ class SLICENODE_SeparateMesh(SliceNode):
def init(self, context):
self.add_input("SLICESOCK_Mesh")
self.add_input("SLICESOCK_String", "Attribute")
self.add_input("SLICESOCK_Float", "Value")
self.add_input("NodeSocketString", "Attribute")
self.add_input("NodeSocketFloat", "Value")
self.add_output("SLICESOCK_Mesh", "Selection")
self.add_output("SLICESOCK_Mesh", "Inverted")
@ -172,7 +172,7 @@ class SLICENODE_SeparateMeshByRange(SliceNode):
def init(self, context):
self.add_input("SLICESOCK_Mesh")
self.add_input("SLICESOCK_String", "Attribute")
self.add_input("NodeSocketString", "Attribute")
self._update_outputs()
def update(self):
@ -277,9 +277,9 @@ class SLICENODE_ArrangeMesh(SliceNode):
def init(self, context):
self.add_input("SLICESOCK_Mesh", "Slicing Group", display_shape='LIST')
self.add_input("SLICESOCK_Float", "Bed X").value = 250.0
self.add_input("SLICESOCK_Float", "Bed Y").value = 250.0
self.add_input("SLICESOCK_Float", "Spacing").value = 2.0
self.add_input("NodeSocketFloat", "Bed X").value = 250.0
self.add_input("NodeSocketFloat", "Bed Y").value = 250.0
self.add_input("NodeSocketFloat", "Spacing").value = 2.0
self.add_output("SLICESOCK_Mesh", "Slicing Group", display_shape='LIST')
def draw_buttons(self, context, layout):
@ -584,18 +584,16 @@ class SLICENODE_TransformGeometry(SliceNode):
def init(self, context):
self.add_input("SLICESOCK_Geometry", "Geometry")
self.add_input("SLICESOCK_Vector", "Translation")
self.add_input("SLICESOCK_VectorRotation", "Rotation")
self.add_input("SLICESOCK_Vector", "Scale").value = (1.0, 1.0, 1.0)
self.add_input("NodeSocketVector", "Translation")
self.add_input("NodeSocketRotation", "Rotation")
self.add_input("NodeSocketVector", "Scale").value = (1.0, 1.0, 1.0)
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
@ -623,9 +621,9 @@ class SLICENODE_DropGeometry(SliceNode):
def init(self, context):
self.add_input("SLICESOCK_Mesh", "Mesh")
self.add_input("SLICESOCK_Float", "Z")
self.add_input("NodeSocketFloat", "Z")
self.add_output("SLICESOCK_Mesh", "Mesh")
self.add_output("SLICESOCK_VectorRo", "Translation")
self.add_output("NodeSocketVector", "Translation")
def evaluate_output(self, socket_name: str, **_kwargs) -> SlicingCollection | SlicingGroup | tuple[float, float, float] | None:
if socket_name == "Translation":
@ -754,7 +752,7 @@ class SLICENODE_RenameMesh(SliceNode):
def init(self, context):
self.add_input("SLICESOCK_Mesh", "Geometry")
self.add_input("SLICESOCK_String", "Name")
self.add_input("NodeSocketString", "Name")
self.add_output("SLICESOCK_Mesh", "Geometry")
def draw_buttons(self, context, layout):
@ -821,7 +819,7 @@ class SLICENODE_AssignPdfProperties(SliceNode):
def init(self, context):
self.add_input("SLICESOCK_Mesh", "Geometry")
self.add_input("SLICESOCK_Float", "Line Width")
self.add_input("NodeSocketFloat", "Line Width")
self.add_output("SLICESOCK_Mesh", "Geometry")
def draw_buttons(self, context, layout):

View file

@ -11,9 +11,9 @@ class SLICENODE_GetPaths(SliceNode):
bl_label = "Get Paths"
def init(self, context):
self.add_input("SLICESOCK_Object")
self.add_input("SLICESOCK_String", "Extrusion Multiplier Attribute").value = 'e_mult'
self.add_input("SLICESOCK_String", "Feedrate Attribute").value = 'f_rate'
self.add_input("NodeSocketObject")
self.add_input("NodeSocketString", "Extrusion Multiplier Attribute").value = 'e_mult'
self.add_input("NodeSocketString", "Feedrate Attribute").value = 'f_rate'
self.add_input("SLICESOCK_Settings", "Extra Attributes")
self.add_output("SLICESOCK_Paths")
@ -44,9 +44,9 @@ class SLICENODE_SliceShells(SliceNode):
def init(self, context):
self.add_input("SLICESOCK_Mesh")
self.add_input("SLICESOCK_Float", "Layer Height").value = 0.2
self.add_input("SLICESOCK_Float", "First Layer Height").value = 0.2
self.add_input("SLICESOCK_VectorRotation", "Rotation")
self.add_input("NodeSocketFloat", "Layer Height").value = 0.2
self.add_input("NodeSocketFloat", "First Layer Height").value = 0.2
self.add_input("NodeSocketRotation", "Rotation")
self.add_output("SLICESOCK_Paths")
def evaluate_output(self, socket_name: str, **_kwargs) -> SlicePaths:
@ -211,8 +211,8 @@ class SLICENODE_BufferCurve(SliceNode):
def init(self, context):
self.add_input("SLICESOCK_Paths")
self.add_input("SLICESOCK_Float", "Distance").value = 0.0
self.add_input("SLICESOCK_VectorRotation", "Rotation")
self.add_input("NodeSocketFloat", "Distance").value = 0.0
self.add_input("NodeSocketRotation", "Rotation")
self.add_output("SLICESOCK_Paths")
def draw_buttons(self, context, layout):
@ -254,8 +254,8 @@ class SLICENODE_OffsetCurve(SliceNode):
def init(self, context):
self.add_input("SLICESOCK_Paths")
self.add_input("SLICESOCK_Float", "Distance").value = 0.0
self.add_input("SLICESOCK_VectorRotation", "Rotation")
self.add_input("NodeSocketFloat", "Distance").value = 0.0
self.add_input("NodeSocketRotation", "Rotation")
self.add_output("SLICESOCK_Paths")
def draw_buttons(self, context, layout):
@ -283,7 +283,7 @@ class SLICENODE_PathOffset(SliceNode):
def init(self, context):
self.add_input("SLICESOCK_Paths", "Paths")
self.add_input("SLICESOCK_Vector", "Offset")
self.add_input("NodeSocketVector", "Offset")
self.add_output("SLICESOCK_Paths", "Paths")
def evaluate_output(self, socket_name: str, **_kwargs) -> Any:
@ -300,8 +300,8 @@ class SLICENODE_RotatePath(SliceNode):
def init(self, context):
self.add_input("SLICESOCK_Paths", "Paths")
self.add_input("SLICESOCK_Float", "Angle").value = 0.0
self.add_input("SLICESOCK_Vector", "Center")
self.add_input("NodeSocketFloat", "Angle").value = 0.0
self.add_input("NodeSocketVector", "Center")
self.add_output("SLICESOCK_Paths", "Paths")
def evaluate_output(self, socket_name: str, **_kwargs) -> Any:
@ -330,10 +330,10 @@ class SLICENODE_BufferShellsMultiple(SliceNode):
def init(self, context):
self.add_input("SLICESOCK_Paths")
self.add_input("SLICESOCK_Float", "Distance").value = 0
self.add_input("SLICESOCK_Float", "Width Step").value = 0.4
self.add_input("SLICESOCK_Int", "Count").value = 2
self.add_input("SLICESOCK_VectorRotation", "Rotation")
self.add_input("NodeSocketFloat", "Distance").value = 0
self.add_input("NodeSocketFloat", "Width Step").value = 0.4
self.add_input("NodeSocketInt", "Count").value = 2
self.add_input("NodeSocketRotation", "Rotation")
self.add_output("SLICESOCK_Paths")
def draw_buttons(self, context, layout):
@ -370,9 +370,9 @@ class SLICENODE_Infill(SliceNode):
def init(self, context):
self.add_input("SLICESOCK_Paths")
self.add_input("SLICESOCK_Float", "Density").value = 2.0
self.add_input("SLICESOCK_Float", "Infill Rotation").value = 0.0
self.add_input("SLICESOCK_VectorRotation", "Layer Rotation")
self.add_input("NodeSocketFloat", "Density").value = 2.0
self.add_input("NodeSocketFloat", "Infill Rotation").value = 0.0
self.add_input("NodeSocketRotation", "Layer Rotation")
self.add_output("SLICESOCK_Paths")
def draw_buttons(self, context, layout):
@ -422,7 +422,7 @@ class SLICENODE_OverhangMask(SliceNode):
def init(self, context):
self.add_input("SLICESOCK_Paths")
self.add_input("SLICESOCK_Float", "Tolerance").value = 0.05
self.add_input("NodeSocketFloat", "Tolerance").value = 0.05
self.add_output("SLICESOCK_Paths")
def draw_buttons(self, context, layout):
@ -534,7 +534,7 @@ class SLICENODE_SimplifyPaths(SliceNode):
def init(self, context):
self.add_input("SLICESOCK_Paths")
self.add_input("SLICESOCK_Float", "Tolerance").value = 0.1
self.add_input("NodeSocketFloat", "Tolerance").value = 0.1
self.add_output("SLICESOCK_Paths")
def draw_buttons(self, context, layout):

View file

@ -15,7 +15,7 @@ class SLICENODE_GcodeToMesh(ExecSliceNode):
def init(self, context):
self._init_exec()
self.add_input("SLICESOCK_String", "Name")
self.add_input("NodeSocketString", "Name")
self.add_multi_socket_group("SLICESOCK_Gcode", name="Gcode")
def draw_buttons(self, context, layout):
@ -242,7 +242,7 @@ class SLICENODE_GcodeToMesh(ExecSliceNode):
for i, gcode in enumerate(gcodes):
if total_parts > 1:
mesh_name = f"{base_mesh_name}{i+1:0{num_digits}d}"
mesh_name = f"{base_mesh_name}{i:0{num_digits}d}"
else:
mesh_name = base_mesh_name

View file

@ -44,101 +44,18 @@ class ReadonlySocket():
def draw(self, context: Any, layout: Any, node: Any, text: str | None):
return self._draw_as_ro(context, layout, node, text) # pyright: ignore[reportAttributeAccessIssue]
class SLICESOCK_ObjectCommon(InputNodeSocket):
bl_label = "Object"
value: NfPointerProperty(
name="Object",
type=bpy.types.Object,
update=notify_tree_changed
) # pyright: ignore[reportInvalidTypeForm]
bl_color = (0.9, 0.7, 0.2, 1.0)
@register_class
class SLICESOCK_ObjectRo(ReadonlySocket, SLICESOCK_ObjectCommon): ...
@register_class
class SLICESOCK_Object(SLICESOCK_ObjectCommon): ...
class SLICESOCK_Float_Common(InputNodeSocket):
bl_label = "Float"
value: NfFloatProperty(name="Float", default=0) # pyright: ignore[reportInvalidTypeForm]
bl_color = (161/255, 161/255, 161/255, 1.0)
@register_class
class SLICESOCK_FloatRo(ReadonlySocket, SLICESOCK_Float_Common): ...
@register_class
class SLICESOCK_Float(SLICESOCK_Float_Common): ...
class SLICESOCK_Int_Common(InputNodeSocket):
bl_label = "Integer"
value: NfIntProperty(name="Int", default=0) # pyright: ignore[reportInvalidTypeForm]
bl_color = (161/255, 161/255, 161/255, 1.0)
@register_class
class SLICESOCK_Int(SLICESOCK_Int_Common): ...
@register_class
class SLICESOCK_IntRo(ReadonlySocket, SLICESOCK_Int_Common): ...
@register_class
class SLICESOCK_String(InputNodeSocket):
bl_label = "String"
value: NfStringProperty(name="String", default="") # pyright: ignore[reportInvalidTypeForm]
bl_color = (0.4, 0.7, 1.0, 1.0)
@register_class
class SLICESOCK_FilePath(InputNodeSocket):
bl_label = "Filepath"
value: NfStringProperty(name="Filepath", default="", subtype='FILE_PATH') # pyright: ignore[reportInvalidTypeForm]
bl_color = (0.4, 0.7, 1.0, 1.0)
@register_class
class SLICESOCK_DirPath(InputNodeSocket):
bl_label = "Filepath"
value: NfStringProperty(name="Folder", default="", subtype='DIR_PATH') # pyright: ignore[reportInvalidTypeForm]
bl_color = (0.4, 0.7, 1.0, 1.0)
@register_class
class SLICESOCK_VectorRotation_Common(InputNodeSocket):
bl_label = "VectorRotation"
value: NfFloatVectorProperty(name="Vector", size=3, default=(0.0, 0.0, 0.0), subtype='EULER') # pyright: ignore[reportInvalidTypeForm]
bl_color = (166/255, 99/255, 199/255, 1.0)
@register_class
class SLICESOCK_VectorRotation(SLICESOCK_VectorRotation_Common): ...
@register_class
class SLICESOCK_VectorRotationRo(ReadonlySocket, SLICESOCK_VectorRotation_Common): ...
@register_class
class SLICESOCK_Vector_Common(InputNodeSocket):
bl_label = "Vector"
value: NfFloatVectorProperty(name="Vector", size=3, default=(0.0, 0.0, 0.0)) # pyright: ignore[reportInvalidTypeForm]
bl_color = (0.39, 0.39, 0.78, 1.0)
@register_class
class SLICESOCK_Vector(SLICESOCK_Vector_Common): ...
@register_class
class SLICESOCK_VectorRo(ReadonlySocket, SLICESOCK_Vector_Common): ...
@register_class
class SLICESOCK_Mesh(LabelNodeSocket):
bl_label = "Mesh"
default = None
bl_color = (0, 214/255, 163/255, 1.0)
@register_class
class SLICESOCK_Paths(LabelNodeSocket):
bl_label = "Paths"
default = SlicePaths()
bl_color = (214/255, 0, 120/255, 1.0)
# Native Blender socket type aliases
SLICESOCK_Object = "NodeSocketObject"
SLICESOCK_ObjectRo = "NodeSocketObject"
SLICESOCK_Float = "NodeSocketFloat"
SLICESOCK_FloatRo = "NodeSocketFloat"
SLICESOCK_Int = "NodeSocketInt"
SLICESOCK_IntRo = "NodeSocketInt"
SLICESOCK_String = "NodeSocketString"
SLICESOCK_VectorRotation = "NodeSocketRotation"
SLICESOCK_VectorRotationRo = "NodeSocketRotation"
SLICESOCK_Vector = "NodeSocketVector"
SLICESOCK_VectorRo = "NodeSocketVector"
@register_class
class SLICESOCK_Geometry(LabelNodeSocket):
@ -159,7 +76,7 @@ class SLICESOCK_Geometry(LabelNodeSocket):
pass
return getattr(from_sock, "bl_color", self.bl_color)
if self.is_output:
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")
@ -169,6 +86,32 @@ class SLICESOCK_Geometry(LabelNodeSocket):
else:
return _get_upstream_color(self)
@register_class
class SLICESOCK_FilePath(InputNodeSocket):
bl_label = "Filepath"
value: NfStringProperty(name="Filepath", default="", subtype='FILE_PATH') # pyright: ignore[reportInvalidTypeForm]
bl_color = (0.4, 0.7, 1.0, 1.0)
@register_class
class SLICESOCK_DirPath(InputNodeSocket):
bl_label = "Filepath"
value: NfStringProperty(name="Folder", default="", subtype='DIR_PATH') # pyright: ignore[reportInvalidTypeForm]
bl_color = (0.4, 0.7, 1.0, 1.0)
@register_class
class SLICESOCK_Mesh(LabelNodeSocket):
bl_label = "Mesh"
default = None
bl_color = (0, 214/255, 163/255, 1.0)
@register_class
class SLICESOCK_Paths(LabelNodeSocket):
bl_label = "Paths"
default = SlicePaths()
bl_color = (214/255, 0, 120/255, 1.0)
class SLICESOCK_Gcode_Common(InputNodeSocket):

View file

@ -30,9 +30,11 @@ def get_referenced_objects(node, visited=None):
# Check all input sockets for objects
for inp in node.inputs:
# If it's an object socket, it might have a value if not linked
if inp.bl_idname in {"SLICESOCK_Object", "SLICESOCK_ObjectRo"}:
if not inp.is_linked and getattr(inp, "value", None):
objs.add(inp.value)
if inp.bl_idname == "NodeSocketObject":
if not inp.is_linked and getattr(inp, "value", getattr(inp, "default_value", None)):
val = getattr(inp, "value", getattr(inp, "default_value", None))
if isinstance(val, bpy.types.Object):
objs.add(val)
# Always follow links to find more objects upstream
if inp.is_linked:
@ -48,9 +50,10 @@ def get_referenced_objects(node, visited=None):
# Also check output sockets of the node itself,
# as some nodes might "hold" an object (like InputObject)
for outp in node.outputs:
if outp.bl_idname in {"SLICESOCK_Object", "SLICESOCK_ObjectRo"}:
if getattr(outp, "value", None):
objs.add(outp.value)
if outp.bl_idname == "NodeSocketObject":
val = getattr(outp, "value", getattr(outp, "default_value", None))
if isinstance(val, bpy.types.Object):
objs.add(val)
elif node.bl_idname == "SLICENODE_CollectionObjects" and outp.is_linked:
obj = bpy.data.objects.get(outp.identifier)
if obj:

View file

@ -752,7 +752,7 @@ def segments_to_slice(segments: np.ndarray) -> list[np.ndarray]:
open_paths = []
for p in linked:
path_pts = p[:, :2]
if len(path_pts) > 2 and np.allclose(path_pts[0], path_pts[-1]):
if len(path_pts) > 3 and np.allclose(path_pts[0], path_pts[-1]):
closed_paths.append(p)
else:
open_paths.append(p)

View file

@ -57,4 +57,5 @@ This file provides a mapping of modules to their primary responsibilities and ke
- `blender_slice_nodes_generic.py`:
- Base classes for NodeSlicer nodes (`SliceNode`) and sockets (`SliceNodeSocket`).
- Native Blender socket type mappings (`NodeSocketFloat`, `NodeSocketInt`, `NodeSocketVector`, `NodeSocketRotation`, `NodeSocketString`, `NodeSocketObject`, `NodeSocketGeometry`).
- Core evaluation logic (`resolve_input`).

View file

@ -4,11 +4,13 @@
| Socket ID | Type | Underlying Data Structure | Purpose |
|-----------|------|---------------------------|---------|
| `SLICESOCK_Object` | `bpy.types.Object` | Blender RNA Object | Reference to a Blender Object |
| `SLICESOCK_Float` | `float` | Python float | Numeric value |
| `SLICESOCK_Int` | `int` | Python int | Integer value |
| `SLICESOCK_String` | `str` | Python string | Text/Path value |
| `SLICESOCK_Vector` | `tuple` | Python tuple (3) | XYZ or Euler rotation value |
| `NodeSocketObject` | `bpy.types.Object` | Blender RNA Object | Reference to a Blender Object |
| `NodeSocketFloat` | `float` | Python float | Numeric value |
| `NodeSocketInt` | `int` | Python int | Integer value |
| `NodeSocketString` | `str` | Python string | Text value |
| `NodeSocketVector` | `tuple` | Python tuple (3) | XYZ vector value |
| `NodeSocketRotation` | `tuple` | Python tuple (3) | Euler rotation value |
| `NodeSocketGeometry` | `Geometry` | Geometry Data | Generic geometry reference |
| `SLICESOCK_Mesh` | `SlicingObject` | NumPy NDArray (Triangles) | Internal mesh data (Transformed/Scaled) |
| `SLICESOCK_Paths` | `SlicePaths` (list) | List of paths (N, 3) + groups | Generic toolpaths or slice boundaries |
| `SLICESOCK_Gcode` | `str` | Multi-line string | Generated G-code text |

View file

@ -11,9 +11,47 @@ class NodeException(Exception): ...
_EvalCache = dict[tuple[int, str, str], Any]
NATIVE_SOCKET_MAP: dict[str, str] = {}
NATIVE_SOCKET_LABELS: dict[str, str] = {
"NodeSocketFloat": "Float",
"NodeSocketInt": "Integer",
"NodeSocketVector": "Vector",
"NodeSocketRotation": "Rotation",
"NodeSocketString": "String",
"NodeSocketObject": "Object",
}
if hasattr(bpy.types, "NodeSocket") and not hasattr(bpy.types.NodeSocket, "value"):
def _get_socket_value(self):
try:
return self.default_value
except (AttributeError, TypeError):
pass
return getattr(self, "_value", getattr(self, "default", None))
def _set_socket_value(self, val):
try:
self.default_value = val
return
except (AttributeError, TypeError):
pass
self._value = val
try:
bpy.types.NodeSocket.value = property(_get_socket_value, _set_socket_value)
except Exception:
pass
class AutoBlIdName:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if kwargs:
try:
super().__init_subclass__(**kwargs)
except TypeError:
super().__init_subclass__()
else:
super().__init_subclass__()
if "bl_idname" not in cls.__dict__:
cls.bl_idname = cls.__name__
@ -40,7 +78,8 @@ class SliceNode(Node, AutoBlIdName):
return resolve_input(inputs.get(fieldname), cache=cache, visited=visited)
def add_output(self, bl_idname: str, name: str = "", identifier: str = "", hide: bool = False, display_shape: str = ""):
eval_name = name or get_label_by_name(bl_idname)
bl_idname = NATIVE_SOCKET_MAP.get(bl_idname, bl_idname)
eval_name = name or NATIVE_SOCKET_LABELS.get(bl_idname) or get_label_by_name(bl_idname) or bl_idname
sock = self.outputs.new(bl_idname, eval_name, identifier=identifier or eval_name)
sock.hide = hide
if display_shape:
@ -48,7 +87,8 @@ class SliceNode(Node, AutoBlIdName):
return sock
def add_input(self, bl_idname: str, name: str = "", identifier: str = "", hide: bool = False, display_shape: str = ""):
eval_name = name or get_label_by_name(bl_idname)
bl_idname = NATIVE_SOCKET_MAP.get(bl_idname, bl_idname)
eval_name = name or NATIVE_SOCKET_LABELS.get(bl_idname) or get_label_by_name(bl_idname) or bl_idname
sock = self.inputs.new(bl_idname, eval_name, identifier=identifier or eval_name)
sock.hide = hide
if display_shape:
@ -268,6 +308,7 @@ class SLICENODE_Group(bpy.types.NodeCustomGroup, AutoBlIdName):
# Add or update sockets
sock_indexes = {s.identifier: i for i, s in enumerate(collection)}
for ident, name, stype in expected:
stype = NATIVE_SOCKET_MAP.get(stype, stype)
if ident not in sock_indexes:
try:
# Explicitly pass identifier to keep it perfectly synced
@ -384,10 +425,19 @@ def resolve_input(
visited = getattr(socket.node, "_current_visited", None)
if not socket.is_linked:
val = getattr(socket, "value", None)
if val is not None:
return val
return socket.default
try:
val = getattr(socket, "default_value", None)
if val is not None: return val
except Exception:
pass
try:
val = getattr(socket, "value", None)
if val is not None: return val
except Exception:
pass
return getattr(socket, "default", None)
assert socket.is_linked
assert socket.links

View file

@ -63,6 +63,9 @@ 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()
from NodeSlicer.infra.blender.blender_slice_nodes_generic import (
_resolve_group_output,