This commit is contained in:
Nicolas Predella 2026-05-16 15:28:29 +03:00
parent 1769909436
commit e1a3e0c746
4 changed files with 122 additions and 76 deletions

View file

@ -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)

View file

@ -10,14 +10,14 @@ from typing import Any, TypeAlias
import bpy import bpy
import numpy as np import numpy as np
from mathutils import Euler 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 ...registry import register_class
from .. import slice from .. import slice
from ..slice import Pather, slice_at_range, SlicePaths from ..slice import Pather, slice_at_range, SlicePaths
from ...infra.blender_mesh_capture import SlicingObject, capture_mesh_edges, SlicingGroup from ...infra.blender_mesh_capture import SlicingObject, capture_mesh_edges, SlicingGroup
from ...infra.blender_slice_nodes_generic import SliceNode, resolve_input 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 from ...infra._3mf_writer._3mf import prepare_3mf
class SliceIntrinsics: class SliceIntrinsics:
@ -89,23 +89,23 @@ class SLICENODE_PrusaslicerConf(SliceNode):
if os.path.isdir(abs_path): if os.path.isdir(abs_path):
cache.load([abs_path]) cache.load([abs_path])
if self.printer_enum != 'NONE': if self.printer_id != 'NONE':
cache.evaluate_compatibility({self.printer_enum}, set(cache.vendors)) cache.evaluate_compatibility({self.printer_id}, set(cache.vendors))
notify_tree_changed(self, context) notify_tree_changed(self, context)
def get_printers(self, context): def get_printers(self, context):
cache = self.get_cache() cache = self.get_cache()
items = [('NONE', "None", "No printer selected")] items = [('NONE', "None", "No printer selected", 'NONE', 0)]
for k, p in cache.printer_profiles.items(): for i, (k, p) in enumerate(cache.printer_profiles.items(), 1):
items.append((k, p.id, p.path.name)) items.append((k, p.id, p.path.name, 'PRINTER', i))
return items return items
def get_filaments(self, context): def get_filaments(self, context):
cache = self.get_cache() 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': if printer_id == 'NONE':
return items return items
@ -113,17 +113,19 @@ class SLICENODE_PrusaslicerConf(SliceNode):
if not printer: if not printer:
return items return items
idx = 1
for k in printer.compatible_profiles: for k in printer.compatible_profiles:
if k.startswith("filament:"): if k.startswith("filament:"):
p = cache.profiles[k] 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 return items
def get_prints(self, context): def get_prints(self, context):
cache = self.get_cache() 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': if printer_id == 'NONE':
return items return items
@ -131,34 +133,64 @@ class SLICENODE_PrusaslicerConf(SliceNode):
if not printer: if not printer:
return items return items
idx = 1
for k in printer.compatible_profiles: for k in printer.compatible_profiles:
if k.startswith("print:"): if k.startswith("print:"):
p = cache.profiles[k] 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 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", name="Profiles",
subtype='DIR_PATH', subtype='DIR_PATH',
update=update_profiles update=update_profiles
) # pyright: ignore[reportInvalidTypeForm] ) # pyright: ignore[reportInvalidTypeForm]
printer_enum: EnumProperty( printer_enum: NfEnumProperty(
name="Printer", name="Printer",
items=get_printers, items=get_printers,
update=update_profiles get=get_printer_enum,
set=set_printer_enum
) # pyright: ignore[reportInvalidTypeForm] ) # pyright: ignore[reportInvalidTypeForm]
filament_enum: EnumProperty( filament_enum: NfEnumProperty(
name="Filament", name="Filament",
items=get_filaments, items=get_filaments,
update=notify_tree_changed get=get_filament_enum,
set=set_filament_enum
) # pyright: ignore[reportInvalidTypeForm] ) # pyright: ignore[reportInvalidTypeForm]
print_enum: EnumProperty( print_enum: NfEnumProperty(
name="Print", name="Print",
items=get_prints, items=get_prints,
update=notify_tree_changed get=get_print_enum,
set=set_print_enum
) # pyright: ignore[reportInvalidTypeForm] ) # pyright: ignore[reportInvalidTypeForm]
def init(self, context): def init(self, context):
@ -168,19 +200,19 @@ class SLICENODE_PrusaslicerConf(SliceNode):
assert layout assert layout
layout.prop(self, "config_path") layout.prop(self, "config_path")
layout.prop(self, "printer_enum") layout.prop(self, "printer_enum")
if self.printer_enum != 'NONE': if self.printer_id != 'NONE':
layout.prop(self, "filament_enum") layout.prop(self, "filament_enum")
layout.prop(self, "print_enum") layout.prop(self, "print_enum")
def evaluate_output(self, socket_name: str, **_kwargs) -> Any: 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 return None
cache = self.get_cache() cache = self.get_cache()
return { return {
'printer': self.printer_enum, 'printer': self.printer_id,
'filament': [self.filament_enum], 'filament': [self.filament_id],
'print': self.print_enum, 'print': self.print_id,
'cache': cache 'cache': cache
} }
@ -193,17 +225,16 @@ class SLICENODE_InputPrinter(SliceNode):
self.label = self.printer_label self.label = self.printer_label
notify_tree_changed(self, context) 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", name="Type",
items=[ items=[
('PRUSALINK', "PrusaLink", "PrusaLink API"), ('PRUSALINK', "PrusaLink", "PrusaLink API"),
('CREALITY', "Creality", "Creality Cloud API"), ('CREALITY', "Creality", "Creality Cloud API"),
('MOONRAKER', "Moonraker", "Moonraker (Klipper) API"), ('MOONRAKER', "Moonraker", "Moonraker (Klipper) API"),
], ],
default='PRUSALINK', default='PRUSALINK'
update=notify_tree_changed
) # pyright: ignore[reportInvalidTypeForm] ) # pyright: ignore[reportInvalidTypeForm]
def init(self, context): def init(self, context):
@ -256,7 +287,7 @@ class SLICENODE_InputIntrinsics(SliceNode):
class MathNode(SliceNode): class MathNode(SliceNode):
bl_label = "Float Math" bl_label = "Float Math"
operation: bpy.props.EnumProperty( operation: NfEnumProperty(
name="Operation", name="Operation",
items=[ items=[
('ADD', "Add", "Add the two values"), ('ADD', "Add", "Add the two values"),
@ -395,7 +426,7 @@ class SLICENODE_SliceShells(SliceNode):
class SLICENODE_BufferShells(SliceNode): class SLICENODE_BufferShells(SliceNode):
bl_label = "Buffer Shells" bl_label = "Buffer Shells"
shape_option: bpy.props.EnumProperty( shape_option: NfEnumProperty(
name="Miter", name="Miter",
items=[ items=[
('round', "Round", ""), ('round', "Round", ""),
@ -448,7 +479,7 @@ class SLICENODE_BufferShells(SliceNode):
class SLICENODE_BufferShellsMultiple(SliceNode): class SLICENODE_BufferShellsMultiple(SliceNode):
bl_label = "Buffer Shells Multiple" bl_label = "Buffer Shells Multiple"
shape_option: bpy.props.EnumProperty( shape_option: NfEnumProperty(
name="Miter", name="Miter",
items=[ items=[
('round', "Round", ""), ('round', "Round", ""),
@ -509,7 +540,7 @@ class SLICENODE_BufferShellsMultiple(SliceNode):
class SLICENODE_Infill(SliceNode): class SLICENODE_Infill(SliceNode):
bl_label = "Infill" bl_label = "Infill"
infill_type: EnumProperty( infill_type: NfEnumProperty(
name="Type", name="Type",
items=[ items=[
('GRID', "Grid", "Cross-hatch grid"), ('GRID', "Grid", "Cross-hatch grid"),
@ -571,7 +602,7 @@ class SLICENODE_Infill(SliceNode):
class SLICENODE_PathsToGcode(SliceNode): class SLICENODE_PathsToGcode(SliceNode):
bl_label = "Paths to Gcode" 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): def init(self, context):
self.add_input("SLICESOCK_Paths") self.add_input("SLICESOCK_Paths")
@ -612,7 +643,7 @@ class SLICENODE_GcodeBlock(SliceNode):
class SLICENODE_GcodeHelper(SliceNode): class SLICENODE_GcodeHelper(SliceNode):
bl_label = "Gcode Helper" bl_label = "Gcode Helper"
command: bpy.props.EnumProperty( command: NfEnumProperty(
name="Command", name="Command",
items=[ items=[
('G0', "G0: Rapid Move", "Fast movement without extrusion"), ('G0', "G0: Rapid Move", "Fast movement without extrusion"),
@ -644,7 +675,7 @@ class SLICENODE_GcodeHelper(SliceNode):
default='G1' default='G1'
) # pyright: ignore[reportInvalidTypeForm] ) # pyright: ignore[reportInvalidTypeForm]
parameters: StringProperty(name="Params", default="") # pyright: ignore[reportInvalidTypeForm] parameters: NfStringProperty(name="Params", default="") # pyright: ignore[reportInvalidTypeForm]
def init(self, context): def init(self, context):
self.add_output("SLICESOCK_GcodeRo") self.add_output("SLICESOCK_GcodeRo")
@ -714,7 +745,7 @@ class SLICENODE_JoinSlices(SliceNodeJoiner):
bl_label = "Join Slices" bl_label = "Join Slices"
socket_type = "SLICESOCK_Paths" socket_type = "SLICESOCK_Paths"
operation: bpy.props.EnumProperty( operation: NfEnumProperty(
name="Operation", name="Operation",
items=[ items=[
('UNION', "Union", "Union of the slices"), ('UNION', "Union", "Union of the slices"),
@ -771,7 +802,7 @@ class SLICENODE_JoinPaths(SliceNodeJoiner):
bl_label = "Join Paths" bl_label = "Join Paths"
socket_type = "SLICESOCK_Paths" socket_type = "SLICESOCK_Paths"
mode: bpy.props.EnumProperty( mode: NfEnumProperty(
name="Mode", name="Mode",
items=[ items=[
('EXTEND', "Extend", "Append paths sequentially"), ('EXTEND', "Extend", "Append paths sequentially"),
@ -919,7 +950,7 @@ class SLICENODE_PrinterList(SliceNode):
def update_config(self, context): def update_config(self, context):
self._refresh() 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): def init(self, context):
pass pass
@ -961,7 +992,7 @@ class SLICENODE_GcodeToTextBlock(ExecSliceNode):
bl_label = "Gcode to Text Block" bl_label = "Gcode to Text Block"
button_label = "Update 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): def init(self, context):
self._init_exec() self._init_exec()
@ -1059,8 +1090,8 @@ class SLICENODE_PrusaslicerSlice(ExecSliceNode):
bl_label = "PrusaSlicer Slice" bl_label = "PrusaSlicer Slice"
button_label = "Slice with PrusaSlicer" button_label = "Slice with PrusaSlicer"
prusaslicer_executable: StringProperty(name="PrusaSlicer Path", subtype='FILE_PATH', update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] prusaslicer_executable: NfStringProperty(name="PrusaSlicer Path", subtype='FILE_PATH') # pyright: ignore[reportInvalidTypeForm]
gcode_result: StringProperty(name="Gcode Result", default="") # pyright: ignore[reportInvalidTypeForm] gcode_result: NfStringProperty(name="Gcode Result", default="") # pyright: ignore[reportInvalidTypeForm]
def init(self, context): def init(self, context):
self._init_exec() self._init_exec()
@ -1298,7 +1329,7 @@ class SLICENODE_Executor(ExecSliceNode, SliceNodeJoiner):
button_label = "Execute All" button_label = "Execute All"
socket_type = "SLICESOCK_ExecutionLink" 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): def init(self, context):
self._init_exec() self._init_exec()

View file

@ -1,13 +1,13 @@
from __future__ import annotations from __future__ import annotations
from typing import Any from typing import Any
import bpy 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 ...registry import register_class
from ...infra.blender_slice_nodes_generic import SliceNodeSocket from ...infra.blender_slice_nodes_generic import SliceNodeSocket
from ..slice import SlicePaths from ..slice import SlicePaths
from .tree import notify_tree_changed from .bpy_ns_types import notify_tree_changed
class LabelNodeSocket(SliceNodeSocket): class LabelNodeSocket(SliceNodeSocket):
default = "" default = ""
@ -37,7 +37,7 @@ class ReadonlySocket():
class SLICESOCK_Object(InputNodeSocket): class SLICESOCK_Object(InputNodeSocket):
bl_label = "Object" bl_label = "Object"
value: PointerProperty( value: NfPointerProperty(
name="Object", name="Object",
type=bpy.types.Object, type=bpy.types.Object,
update=notify_tree_changed update=notify_tree_changed
@ -48,7 +48,7 @@ class SLICESOCK_Object(InputNodeSocket):
class SLICESOCK_Float_Common(InputNodeSocket): class SLICESOCK_Float_Common(InputNodeSocket):
bl_label = "Float" 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) bl_color = (161/255, 161/255, 161/255, 1.0)
@register_class @register_class
@ -60,7 +60,7 @@ class SLICESOCK_Float(SLICESOCK_Float_Common): ...
class SLICESOCK_Int_Common(InputNodeSocket): class SLICESOCK_Int_Common(InputNodeSocket):
bl_label = "Integer" 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) bl_color = (161/255, 161/255, 161/255, 1.0)
@register_class @register_class
@ -73,21 +73,21 @@ class SLICESOCK_IntRo(ReadonlySocket, SLICESOCK_Int_Common): ...
class SLICESOCK_String(InputNodeSocket): class SLICESOCK_String(InputNodeSocket):
bl_label = "String" 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) bl_color = (0.4, 0.7, 1.0, 1.0)
@register_class @register_class
class SLICESOCK_FilePath(InputNodeSocket): class SLICESOCK_FilePath(InputNodeSocket):
bl_label = "Filepath" 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) bl_color = (0.4, 0.7, 1.0, 1.0)
@register_class @register_class
class SLICESOCK_VectorRotation_Common(InputNodeSocket): class SLICESOCK_VectorRotation_Common(InputNodeSocket):
bl_label = "VectorRotation" 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) bl_color = (0.39, 0.39, 0.78, 1.0)
@register_class @register_class
@ -109,7 +109,7 @@ class SLICESOCK_Paths(LabelNodeSocket):
class SLICESOCK_Gcode_Common(InputNodeSocket): class SLICESOCK_Gcode_Common(InputNodeSocket):
bl_label = "Gcode" 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.) bl_color = (1., 1., 1., 1.)
@register_class @register_class
@ -139,14 +139,14 @@ class SLICESOCK_ExecutionLink(LabelNodeSocket):
class SLICESOCK_Printer(InputNodeSocket): class SLICESOCK_Printer(InputNodeSocket):
bl_label = "Printer" bl_label = "Printer"
host: StringProperty(name="Host", default="localhost", update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] host: NfStringProperty(name="Host", default="localhost") # pyright: ignore[reportInvalidTypeForm]
port: IntProperty(name="Port", default=80, min=1, max=65535, update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] port: NfIntProperty(name="Port", default=80, min=1, max=65535) # pyright: ignore[reportInvalidTypeForm]
prefix: StringProperty(name="Prefix", default="", update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] prefix: NfStringProperty(name="Prefix", default="") # pyright: ignore[reportInvalidTypeForm]
label: StringProperty(name="Label", default="Printer", update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] label: NfStringProperty(name="Label", default="Printer") # pyright: ignore[reportInvalidTypeForm]
username: StringProperty(name="Username", default="", update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] username: NfStringProperty(name="Username", default="") # pyright: ignore[reportInvalidTypeForm]
password: StringProperty(name="Password", default="", subtype='PASSWORD', update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm] password: NfStringProperty(name="Password", default="", subtype='PASSWORD') # pyright: ignore[reportInvalidTypeForm]
host_type: EnumProperty( host_type: NfEnumProperty(
name="Type", name="Type",
items=[ items=[
('PRUSALINK', "PrusaLink", "PrusaLink API"), ('PRUSALINK', "PrusaLink", "PrusaLink API"),

View file

@ -41,23 +41,6 @@ def get_referenced_objects(node, visited=None):
_is_updating = False _is_updating = False
_tree_changed = 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 @persistent
def depsgraph_update_handler(scene, depsgraph): def depsgraph_update_handler(scene, depsgraph):
global _is_updating, _tree_changed global _is_updating, _tree_changed