From e1a3e0c746ce42a6b5e4be2c2335f3cf59d0f1ca Mon Sep 17 00:00:00 2001 From: Nicolas Predella Date: Sat, 16 May 2026 15:28:29 +0300 Subject: [PATCH] refactor --- core/nodeslicer/bpy_ns_types.py | 32 +++++++++ core/nodeslicer/nodes.py | 117 ++++++++++++++++++++------------ core/nodeslicer/sockets.py | 32 ++++----- core/nodeslicer/tree.py | 17 ----- 4 files changed, 122 insertions(+), 76 deletions(-) create mode 100644 core/nodeslicer/bpy_ns_types.py diff --git a/core/nodeslicer/bpy_ns_types.py b/core/nodeslicer/bpy_ns_types.py new file mode 100644 index 0000000..10e3231 --- /dev/null +++ b/core/nodeslicer/bpy_ns_types.py @@ -0,0 +1,32 @@ +import bpy +from bpy.props import PointerProperty, StringProperty, FloatProperty, IntProperty, EnumProperty, BoolProperty + +from . import tree + +def notify_tree_changed(self, context): + tree._tree_changed = True + + node = None + if isinstance(self, bpy.types.Node): + node = self + elif isinstance(self, bpy.types.NodeSocket): + node = self.node + + if node and hasattr(node, "update"): + try: + node.update() + except Exception as e: + print(f"Error updating node {node.name}: {e}") + +def NfProperty(prop_func): + def wrapper(**kwargs): + kwargs.setdefault("update", notify_tree_changed) + return prop_func(**kwargs) + return wrapper + +NfStringProperty = NfProperty(StringProperty) +NfIntProperty = NfProperty(IntProperty) +NfFloatProperty = NfProperty(FloatProperty) +NfEnumProperty = NfProperty(EnumProperty) +NfBoolProperty = NfProperty(BoolProperty) +NfPointerProperty = NfProperty(PointerProperty) \ No newline at end of file diff --git a/core/nodeslicer/nodes.py b/core/nodeslicer/nodes.py index e9c95cb..653c5c7 100644 --- a/core/nodeslicer/nodes.py +++ b/core/nodeslicer/nodes.py @@ -10,14 +10,14 @@ from typing import Any, TypeAlias import bpy import numpy as np from mathutils import Euler -from bpy.props import StringProperty, FloatProperty, IntProperty, EnumProperty, BoolProperty +from .bpy_ns_types import NfStringProperty, NfEnumProperty, NfBoolProperty from ...registry import register_class from .. import slice from ..slice import Pather, slice_at_range, SlicePaths from ...infra.blender_mesh_capture import SlicingObject, capture_mesh_edges, SlicingGroup from ...infra.blender_slice_nodes_generic import SliceNode, resolve_input -from .tree import notify_tree_changed +from .bpy_ns_types import notify_tree_changed from ...infra._3mf_writer._3mf import prepare_3mf class SliceIntrinsics: @@ -89,23 +89,23 @@ class SLICENODE_PrusaslicerConf(SliceNode): if os.path.isdir(abs_path): cache.load([abs_path]) - if self.printer_enum != 'NONE': - cache.evaluate_compatibility({self.printer_enum}, set(cache.vendors)) + if self.printer_id != 'NONE': + cache.evaluate_compatibility({self.printer_id}, set(cache.vendors)) notify_tree_changed(self, context) def get_printers(self, context): cache = self.get_cache() - items = [('NONE', "None", "No printer selected")] - for k, p in cache.printer_profiles.items(): - items.append((k, p.id, p.path.name)) + items = [('NONE', "None", "No printer selected", 'NONE', 0)] + for i, (k, p) in enumerate(cache.printer_profiles.items(), 1): + items.append((k, p.id, p.path.name, 'PRINTER', i)) return items def get_filaments(self, context): cache = self.get_cache() - items = [('NONE', "None", "No filament selected")] + items = [('NONE', "None", "No filament selected", 'NONE', 0)] - printer_id = self.printer_enum + printer_id = self.printer_id if printer_id == 'NONE': return items @@ -113,17 +113,19 @@ class SLICENODE_PrusaslicerConf(SliceNode): if not printer: return items + idx = 1 for k in printer.compatible_profiles: if k.startswith("filament:"): p = cache.profiles[k] - items.append((k, p.id, p.path.name)) + items.append((k, p.id, p.path.name, 'FILAMENT', idx)) + idx += 1 return items def get_prints(self, context): cache = self.get_cache() - items = [('NONE', "None", "No print selected")] + items = [('NONE', "None", "No print selected", 'NONE', 0)] - printer_id = self.printer_enum + printer_id = self.printer_id if printer_id == 'NONE': return items @@ -131,34 +133,64 @@ class SLICENODE_PrusaslicerConf(SliceNode): if not printer: return items + idx = 1 for k in printer.compatible_profiles: if k.startswith("print:"): p = cache.profiles[k] - items.append((k, p.id, p.path.name)) + items.append((k, p.id, p.path.name, 'PRINT', idx)) + idx += 1 return items - config_path: StringProperty( + printer_id: NfStringProperty(default='NONE', update=update_profiles) # pyright: ignore[reportInvalidTypeForm] + filament_id: NfStringProperty(default='NONE') # pyright: ignore[reportInvalidTypeForm] + print_id: NfStringProperty(default='NONE') # pyright: ignore[reportInvalidTypeForm] + + def _get_enum_idx(self, items, value): + for item in items: + if item[0] == value: + return item[4] + return 0 + + def _set_enum_idx(self, items, index, prop_name): + for item in items: + if item[4] == index: + setattr(self, prop_name, item[0]) + break + + def get_printer_enum(self): return self._get_enum_idx(self.get_printers(None), self.printer_id) + def set_printer_enum(self, value): self._set_enum_idx(self.get_printers(None), value, 'printer_id') + + def get_filament_enum(self): return self._get_enum_idx(self.get_filaments(None), self.filament_id) + def set_filament_enum(self, value): self._set_enum_idx(self.get_filaments(None), value, 'filament_id') + + def get_print_enum(self): return self._get_enum_idx(self.get_prints(None), self.print_id) + def set_print_enum(self, value): self._set_enum_idx(self.get_prints(None), value, 'print_id') + + config_path: NfStringProperty( name="Profiles", subtype='DIR_PATH', update=update_profiles ) # pyright: ignore[reportInvalidTypeForm] - printer_enum: EnumProperty( + printer_enum: NfEnumProperty( name="Printer", items=get_printers, - update=update_profiles + get=get_printer_enum, + set=set_printer_enum ) # pyright: ignore[reportInvalidTypeForm] - filament_enum: EnumProperty( + filament_enum: NfEnumProperty( name="Filament", items=get_filaments, - update=notify_tree_changed + get=get_filament_enum, + set=set_filament_enum ) # pyright: ignore[reportInvalidTypeForm] - print_enum: EnumProperty( + print_enum: NfEnumProperty( name="Print", items=get_prints, - update=notify_tree_changed + get=get_print_enum, + set=set_print_enum ) # pyright: ignore[reportInvalidTypeForm] def init(self, context): @@ -168,19 +200,19 @@ class SLICENODE_PrusaslicerConf(SliceNode): assert layout layout.prop(self, "config_path") layout.prop(self, "printer_enum") - if self.printer_enum != 'NONE': + if self.printer_id != 'NONE': layout.prop(self, "filament_enum") layout.prop(self, "print_enum") def evaluate_output(self, socket_name: str, **_kwargs) -> Any: - if self.printer_enum == 'NONE' or self.filament_enum == 'NONE' or self.print_enum == 'NONE': + if self.printer_id == 'NONE' or self.filament_id == 'NONE' or self.print_id == 'NONE': return None cache = self.get_cache() return { - 'printer': self.printer_enum, - 'filament': [self.filament_enum], - 'print': self.print_enum, + 'printer': self.printer_id, + 'filament': [self.filament_id], + 'print': self.print_id, 'cache': cache } @@ -193,17 +225,16 @@ class SLICENODE_InputPrinter(SliceNode): self.label = self.printer_label notify_tree_changed(self, context) - printer_label: StringProperty(name="Label", default="Printer", update=update_label) # pyright: ignore[reportInvalidTypeForm] + printer_label: NfStringProperty(name="Label", default="Printer", update=update_label) # pyright: ignore[reportInvalidTypeForm] - host_type: EnumProperty( + host_type: NfEnumProperty( name="Type", items=[ ('PRUSALINK', "PrusaLink", "PrusaLink API"), ('CREALITY', "Creality", "Creality Cloud API"), ('MOONRAKER', "Moonraker", "Moonraker (Klipper) API"), ], - default='PRUSALINK', - update=notify_tree_changed + default='PRUSALINK' ) # pyright: ignore[reportInvalidTypeForm] def init(self, context): @@ -256,7 +287,7 @@ class SLICENODE_InputIntrinsics(SliceNode): class MathNode(SliceNode): bl_label = "Float Math" - operation: bpy.props.EnumProperty( + operation: NfEnumProperty( name="Operation", items=[ ('ADD', "Add", "Add the two values"), @@ -395,7 +426,7 @@ class SLICENODE_SliceShells(SliceNode): class SLICENODE_BufferShells(SliceNode): bl_label = "Buffer Shells" - shape_option: bpy.props.EnumProperty( + shape_option: NfEnumProperty( name="Miter", items=[ ('round', "Round", ""), @@ -448,7 +479,7 @@ class SLICENODE_BufferShells(SliceNode): class SLICENODE_BufferShellsMultiple(SliceNode): bl_label = "Buffer Shells Multiple" - shape_option: bpy.props.EnumProperty( + shape_option: NfEnumProperty( name="Miter", items=[ ('round', "Round", ""), @@ -509,7 +540,7 @@ class SLICENODE_BufferShellsMultiple(SliceNode): class SLICENODE_Infill(SliceNode): bl_label = "Infill" - infill_type: EnumProperty( + infill_type: NfEnumProperty( name="Type", items=[ ('GRID', "Grid", "Cross-hatch grid"), @@ -571,7 +602,7 @@ class SLICENODE_Infill(SliceNode): class SLICENODE_PathsToGcode(SliceNode): bl_label = "Paths to Gcode" - use_header: BoolProperty(name="Header", default=True, update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] + use_header: NfBoolProperty(name="Header", default=True) # pyright: ignore[reportInvalidTypeForm] def init(self, context): self.add_input("SLICESOCK_Paths") @@ -612,7 +643,7 @@ class SLICENODE_GcodeBlock(SliceNode): class SLICENODE_GcodeHelper(SliceNode): bl_label = "Gcode Helper" - command: bpy.props.EnumProperty( + command: NfEnumProperty( name="Command", items=[ ('G0', "G0: Rapid Move", "Fast movement without extrusion"), @@ -644,7 +675,7 @@ class SLICENODE_GcodeHelper(SliceNode): default='G1' ) # pyright: ignore[reportInvalidTypeForm] - parameters: StringProperty(name="Params", default="") # pyright: ignore[reportInvalidTypeForm] + parameters: NfStringProperty(name="Params", default="") # pyright: ignore[reportInvalidTypeForm] def init(self, context): self.add_output("SLICESOCK_GcodeRo") @@ -714,7 +745,7 @@ class SLICENODE_JoinSlices(SliceNodeJoiner): bl_label = "Join Slices" socket_type = "SLICESOCK_Paths" - operation: bpy.props.EnumProperty( + operation: NfEnumProperty( name="Operation", items=[ ('UNION', "Union", "Union of the slices"), @@ -771,7 +802,7 @@ class SLICENODE_JoinPaths(SliceNodeJoiner): bl_label = "Join Paths" socket_type = "SLICESOCK_Paths" - mode: bpy.props.EnumProperty( + mode: NfEnumProperty( name="Mode", items=[ ('EXTEND', "Extend", "Append paths sequentially"), @@ -919,7 +950,7 @@ class SLICENODE_PrinterList(SliceNode): def update_config(self, context): self._refresh() - config_path: StringProperty(name="Config File", default="", subtype='FILE_PATH', update=update_config) # pyright: ignore[reportInvalidTypeForm] + config_path: NfStringProperty(name="Config File", default="", subtype='FILE_PATH', update=update_config) # pyright: ignore[reportInvalidTypeForm] def init(self, context): pass @@ -961,7 +992,7 @@ class SLICENODE_GcodeToTextBlock(ExecSliceNode): bl_label = "Gcode to Text Block" button_label = "Update Text Block" - text_block_name: bpy.props.StringProperty(name="Text Block Name", default="NodeSlicer_Gcode.gcode") # pyright: ignore[reportInvalidTypeForm] + text_block_name: NfStringProperty(name="Text Block Name", default="NodeSlicer_Gcode.gcode") # pyright: ignore[reportInvalidTypeForm] def init(self, context): self._init_exec() @@ -1059,8 +1090,8 @@ class SLICENODE_PrusaslicerSlice(ExecSliceNode): bl_label = "PrusaSlicer Slice" button_label = "Slice with PrusaSlicer" - prusaslicer_executable: StringProperty(name="PrusaSlicer Path", subtype='FILE_PATH', update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] - gcode_result: StringProperty(name="Gcode Result", default="") # pyright: ignore[reportInvalidTypeForm] + prusaslicer_executable: NfStringProperty(name="PrusaSlicer Path", subtype='FILE_PATH') # pyright: ignore[reportInvalidTypeForm] + gcode_result: NfStringProperty(name="Gcode Result", default="") # pyright: ignore[reportInvalidTypeForm] def init(self, context): self._init_exec() @@ -1298,7 +1329,7 @@ class SLICENODE_Executor(ExecSliceNode, SliceNodeJoiner): button_label = "Execute All" socket_type = "SLICESOCK_ExecutionLink" - auto_refresh: bpy.props.BoolProperty(name="Auto-refresh", default=False, update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] + auto_refresh: NfBoolProperty(name="Auto-refresh", default=False) # pyright: ignore[reportInvalidTypeForm] def init(self, context): self._init_exec() diff --git a/core/nodeslicer/sockets.py b/core/nodeslicer/sockets.py index 34993bb..ebe2cd7 100644 --- a/core/nodeslicer/sockets.py +++ b/core/nodeslicer/sockets.py @@ -1,13 +1,13 @@ from __future__ import annotations from typing import Any import bpy -from bpy.props import PointerProperty, StringProperty, FloatProperty, IntProperty, EnumProperty +from .bpy_ns_types import NfStringProperty, NfFloatProperty, NfIntProperty, NfEnumProperty, NfPointerProperty from ...registry import register_class from ...infra.blender_slice_nodes_generic import SliceNodeSocket from ..slice import SlicePaths -from .tree import notify_tree_changed +from .bpy_ns_types import notify_tree_changed class LabelNodeSocket(SliceNodeSocket): default = "" @@ -37,7 +37,7 @@ class ReadonlySocket(): class SLICESOCK_Object(InputNodeSocket): bl_label = "Object" - value: PointerProperty( + value: NfPointerProperty( name="Object", type=bpy.types.Object, update=notify_tree_changed @@ -48,7 +48,7 @@ class SLICESOCK_Object(InputNodeSocket): class SLICESOCK_Float_Common(InputNodeSocket): bl_label = "Float" - value: FloatProperty(name="Float", default=0, update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] + value: NfFloatProperty(name="Float", default=0) # pyright: ignore[reportInvalidTypeForm] bl_color = (161/255, 161/255, 161/255, 1.0) @register_class @@ -60,7 +60,7 @@ class SLICESOCK_Float(SLICESOCK_Float_Common): ... class SLICESOCK_Int_Common(InputNodeSocket): bl_label = "Integer" - value: IntProperty(name="Int", default=0, update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] + value: NfIntProperty(name="Int", default=0) # pyright: ignore[reportInvalidTypeForm] bl_color = (161/255, 161/255, 161/255, 1.0) @register_class @@ -73,21 +73,21 @@ class SLICESOCK_IntRo(ReadonlySocket, SLICESOCK_Int_Common): ... class SLICESOCK_String(InputNodeSocket): bl_label = "String" - value: StringProperty(name="String", default="", update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] + 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: StringProperty(name="Filepath", default="", subtype='FILE_PATH', update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] + 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_VectorRotation_Common(InputNodeSocket): bl_label = "VectorRotation" - value: bpy.props.FloatVectorProperty(name="Vector", size=3, default=(0.0, 0.0, 0.0), subtype='EULER', update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] + value: bpy.props.FloatVectorProperty(name="Vector", size=3, default=(0.0, 0.0, 0.0), subtype='EULER') # pyright: ignore[reportInvalidTypeForm] bl_color = (0.39, 0.39, 0.78, 1.0) @register_class @@ -109,7 +109,7 @@ class SLICESOCK_Paths(LabelNodeSocket): class SLICESOCK_Gcode_Common(InputNodeSocket): bl_label = "Gcode" - value: StringProperty(name="Gcode", default="", update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] + value: NfStringProperty(name="Gcode", default="") # pyright: ignore[reportInvalidTypeForm] bl_color = (1., 1., 1., 1.) @register_class @@ -139,14 +139,14 @@ class SLICESOCK_ExecutionLink(LabelNodeSocket): class SLICESOCK_Printer(InputNodeSocket): bl_label = "Printer" - host: StringProperty(name="Host", default="localhost", update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] - port: IntProperty(name="Port", default=80, min=1, max=65535, update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] - prefix: StringProperty(name="Prefix", default="", update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] - label: StringProperty(name="Label", default="Printer", update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] - username: StringProperty(name="Username", default="", update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] - password: StringProperty(name="Password", default="", subtype='PASSWORD', update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] + host: NfStringProperty(name="Host", default="localhost") # pyright: ignore[reportInvalidTypeForm] + port: NfIntProperty(name="Port", default=80, min=1, max=65535) # pyright: ignore[reportInvalidTypeForm] + prefix: NfStringProperty(name="Prefix", default="") # pyright: ignore[reportInvalidTypeForm] + label: NfStringProperty(name="Label", default="Printer") # pyright: ignore[reportInvalidTypeForm] + username: NfStringProperty(name="Username", default="") # pyright: ignore[reportInvalidTypeForm] + password: NfStringProperty(name="Password", default="", subtype='PASSWORD') # pyright: ignore[reportInvalidTypeForm] - host_type: EnumProperty( + host_type: NfEnumProperty( name="Type", items=[ ('PRUSALINK', "PrusaLink", "PrusaLink API"), diff --git a/core/nodeslicer/tree.py b/core/nodeslicer/tree.py index b30daed..86f883c 100644 --- a/core/nodeslicer/tree.py +++ b/core/nodeslicer/tree.py @@ -41,23 +41,6 @@ def get_referenced_objects(node, visited=None): _is_updating = False _tree_changed = False -def notify_tree_changed(self, context): - global _tree_changed - _tree_changed = True - - # Trigger node update if it exists - node = None - if isinstance(self, bpy.types.Node): - node = self - elif isinstance(self, bpy.types.NodeSocket): - node = self.node - - if node and hasattr(node, "update"): - try: - node.update() - except Exception as e: - print(f"Error updating node {node.name}: {e}") - @persistent def depsgraph_update_handler(scene, depsgraph): global _is_updating, _tree_changed