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

View file

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

View file

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