realtime also when changing parameters

This commit is contained in:
Nicolas Predella 2026-05-15 17:48:08 +03:00
parent e69a92c9a3
commit 3b6f996724
3 changed files with 80 additions and 45 deletions

View file

@ -17,6 +17,7 @@ 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 ...infra._3mf_writer._3mf import prepare_3mf
class SliceIntrinsics:
@ -91,6 +92,8 @@ class SLICENODE_PrusaslicerConf(SliceNode):
if self.printer_enum != 'NONE':
cache.evaluate_compatibility({self.printer_enum}, set(cache.vendors))
notify_tree_changed(self, context)
def get_printers(self, context):
cache = self.get_cache()
items = [('NONE', "None", "No printer selected")]
@ -148,12 +151,14 @@ class SLICENODE_PrusaslicerConf(SliceNode):
filament_enum: EnumProperty(
name="Filament",
items=get_filaments
items=get_filaments,
update=notify_tree_changed
) # pyright: ignore[reportInvalidTypeForm]
print_enum: EnumProperty(
name="Print",
items=get_prints
items=get_prints,
update=notify_tree_changed
) # pyright: ignore[reportInvalidTypeForm]
def init(self, context):
@ -186,6 +191,7 @@ class SLICENODE_InputPrinter(SliceNode):
def update_label(self, context):
self.label = self.printer_label
notify_tree_changed(self, context)
printer_label: StringProperty(name="Label", default="Printer", update=update_label) # pyright: ignore[reportInvalidTypeForm]
@ -196,7 +202,8 @@ class SLICENODE_InputPrinter(SliceNode):
('CREALITY', "Creality", "Creality Cloud API"),
('MOONRAKER', "Moonraker", "Moonraker (Klipper) API"),
],
default='PRUSALINK'
default='PRUSALINK',
update=notify_tree_changed
) # pyright: ignore[reportInvalidTypeForm]
def init(self, context):
@ -503,14 +510,16 @@ class SLICENODE_Infill(SliceNode):
bl_label = "Infill"
infill_type: EnumProperty(
name="Infill Type",
name="Type",
items=[
('GRID', "Grid", "Cross-hatch grid infill"),
('GRID', "Grid", "Cross-hatch grid"),
('RECTILINEAR', "Rectilinear", "Alternating parallel lines"),
],
default='GRID'
default='GRID',
update=notify_tree_changed
) # pyright: ignore[reportInvalidTypeForm]
def init(self, context):
self.add_input("SLICESOCK_Paths")
self.add_input("SLICESOCK_Float", "Density").value = 2.0
@ -562,12 +571,12 @@ class SLICENODE_Infill(SliceNode):
class SLICENODE_PathsToGcode(SliceNode):
bl_label = "Paths to Gcode"
use_header: BoolProperty(name="Header", default=True) # pyright: ignore[reportInvalidTypeForm]
use_header: BoolProperty(name="Header", default=True, update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm]
def init(self, context):
self.add_input("SLICESOCK_Paths")
self.add_input("SLICESOCK_Intrinsics")
self.add_output("SLICESOCK_Gcode")
self.add_output("SLICESOCK_GcodeRo")
def draw_buttons(self, context, layout):
assert layout
@ -593,17 +602,11 @@ class SLICENODE_PathsToGcode(SliceNode):
class SLICENODE_GcodeBlock(SliceNode):
bl_label = "Gcode Block"
gcode: StringProperty(name="Gcode", default="") # pyright: ignore[reportInvalidTypeForm]
def init(self, context):
self.add_output("SLICESOCK_Gcode")
def draw_buttons(self, context, layout):
assert layout
layout.prop(self, "gcode", text="")
self.add_output("SLICESOCK_Gcode", "Gcode")
def evaluate_output(self, socket_name: str, **_kwargs) -> str:
return self.gcode
return self.outputs['Gcode'].value
@register_class
class SLICENODE_GcodeHelper(SliceNode):
@ -644,7 +647,7 @@ class SLICENODE_GcodeHelper(SliceNode):
parameters: StringProperty(name="Params", default="") # pyright: ignore[reportInvalidTypeForm]
def init(self, context):
self.add_output("SLICESOCK_Gcode")
self.add_output("SLICESOCK_GcodeRo")
def draw_buttons(self, context, layout):
assert layout
@ -675,7 +678,9 @@ class SliceNodeJoiner(SliceNode):
def update(self) -> None:
def is_active(input) -> bool:
return input.is_linked or getattr(input, 'value', False)
a: bool = input.is_linked or getattr(input, 'value', False)
b: bool = getattr(self.inputs[-1], 'value', False)
return a or b
if len(self.inputs) == 0:
self.add_input(self.socket_type)
@ -695,6 +700,7 @@ class SliceNodeJoiner(SliceNode):
class SLICENODE_JoinGcode(SliceNodeJoiner):
bl_label = "Join Gcode"
socket_type = "SLICESOCK_Gcode"
output_socket_type = "SLICESOCK_GcodeRo"
def evaluate_output(self, socket_name: str, **_kwargs) -> str:
gcode_input: list[str] = []
@ -841,7 +847,7 @@ class SLICENODE_LoadGcode(SliceNode):
def init(self, context):
self.add_input("SLICESOCK_FilePath", "Filepath")
self.add_output("SLICESOCK_Gcode")
self.add_output("SLICESOCK_GcodeRo")
def evaluate_output(self, socket_name: str, **_kwargs) -> str:
path = resolve_input(self.inputs.get("Filepath"))
@ -1053,14 +1059,14 @@ class SLICENODE_PrusaslicerSlice(ExecSliceNode):
bl_label = "PrusaSlicer Slice"
button_label = "Slice with PrusaSlicer"
prusaslicer_executable: StringProperty(name="PrusaSlicer Path", subtype='FILE_PATH') # pyright: ignore[reportInvalidTypeForm]
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]
def init(self, context):
self._init_exec()
self.add_input("SLICESOCK_Object", "Object")
self.add_input("SLICESOCK_PrusaslicerConf", "Config")
self.add_output("SLICESOCK_Gcode")
self.add_output("SLICESOCK_GcodeRo")
def draw_buttons(self, context, layout):
super().draw_buttons(context, layout)
@ -1186,10 +1192,13 @@ class SLICENODE_GcodeToMesh(ExecSliceNode):
def init(self, context):
self._init_exec()
self.add_input("SLICESOCK_Gcode")
self.add_input("SLICESOCK_Gcode", "Gcode")
self.add_input("SLICESOCK_String", "Name")
def evaluate(self, operator=None):
gcode: str = resolve_input(self.inputs.get("Gcode"))
gcode: str = self.resolve("Gcode")
mesh_name: str = self.resolve("Name")
if not gcode:
return 'empty'
@ -1247,7 +1256,6 @@ class SLICENODE_GcodeToMesh(ExecSliceNode):
return 'no segments'
# Create or find "Debug" mesh object
mesh_name = "Debug"
if mesh_name in bpy.data.objects:
obj = bpy.data.objects[mesh_name]
mesh = obj.data
@ -1290,7 +1298,7 @@ class SLICENODE_Executor(ExecSliceNode, SliceNodeJoiner):
button_label = "Execute All"
socket_type = "SLICESOCK_ExecutionLink"
auto_refresh: bpy.props.BoolProperty(name="Auto-refresh", default=False) # pyright: ignore[reportInvalidTypeForm]
auto_refresh: bpy.props.BoolProperty(name="Auto-refresh", default=False, update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm]
def init(self, context):
self._init_exec()

View file

@ -7,6 +7,8 @@ from ...registry import register_class
from ...infra.blender_slice_nodes_generic import SliceNodeSocket
from ..slice import SlicePaths
from .tree import notify_tree_changed
class LabelNodeSocket(SliceNodeSocket):
default = ""
def draw(self, context: Any, layout: Any, node: Any, text: str | None):
@ -38,6 +40,7 @@ class SLICESOCK_Object(InputNodeSocket):
value: PointerProperty(
name="Object",
type=bpy.types.Object,
update=notify_tree_changed
) # pyright: ignore[reportInvalidTypeForm]
bl_color = (0.9, 0.7, 0.2, 1.0)
@ -45,7 +48,7 @@ class SLICESOCK_Object(InputNodeSocket):
class SLICESOCK_Float_Common(InputNodeSocket):
bl_label = "Float"
value: FloatProperty(name="Float", default=0) # pyright: ignore[reportInvalidTypeForm]
value: FloatProperty(name="Float", default=0, update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm]
bl_color = (161/255, 161/255, 161/255, 1.0)
@register_class
@ -57,7 +60,7 @@ class SLICESOCK_Float(SLICESOCK_Float_Common): ...
class SLICESOCK_Int_Common(InputNodeSocket):
bl_label = "Integer"
value: IntProperty(name="Int", default=0) # pyright: ignore[reportInvalidTypeForm]
value: IntProperty(name="Int", default=0, update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm]
bl_color = (161/255, 161/255, 161/255, 1.0)
@register_class
@ -70,21 +73,21 @@ class SLICESOCK_IntRo(ReadonlySocket, SLICESOCK_Int_Common): ...
class SLICESOCK_String(InputNodeSocket):
bl_label = "String"
value: StringProperty(name="String", default="") # pyright: ignore[reportInvalidTypeForm]
value: StringProperty(name="String", default="", update=notify_tree_changed) # 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') # pyright: ignore[reportInvalidTypeForm]
value: StringProperty(name="Filepath", default="", subtype='FILE_PATH', update=notify_tree_changed) # 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') # pyright: ignore[reportInvalidTypeForm]
value: bpy.props.FloatVectorProperty(name="Vector", size=3, default=(0.0, 0.0, 0.0), subtype='EULER', update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm]
bl_color = (0.39, 0.39, 0.78, 1.0)
@register_class
class SLICESOCK_VectorRotation(SLICESOCK_VectorRotation_Common): ...
@ -103,12 +106,16 @@ class SLICESOCK_Paths(LabelNodeSocket):
default = SlicePaths()
bl_color = (214/255, 0, 120/255, 1.0)
@register_class
class SLICESOCK_Gcode(LabelNodeSocket):
class SLICESOCK_Gcode_Common(InputNodeSocket):
bl_label = "Gcode"
default = ""
value: StringProperty(name="Gcode", default="", update=notify_tree_changed) # pyright: ignore[reportInvalidTypeForm]
bl_color = (1., 1., 1., 1.)
@register_class
class SLICESOCK_Gcode(SLICESOCK_Gcode_Common): ...
@register_class
class SLICESOCK_GcodeRo(ReadonlySocket, SLICESOCK_Gcode_Common): ...
@register_class
class SLICESOCK_Intrinsics(LabelNodeSocket):
bl_label = "Intrinsics"
@ -131,12 +138,12 @@ class SLICESOCK_ExecutionLink(LabelNodeSocket):
class SLICESOCK_Printer(InputNodeSocket):
bl_label = "Printer"
host: StringProperty(name="Host", default="localhost") # pyright: ignore[reportInvalidTypeForm]
port: IntProperty(name="Port", default=80, min=1, max=65535) # pyright: ignore[reportInvalidTypeForm]
prefix: StringProperty(name="Prefix", default="") # pyright: ignore[reportInvalidTypeForm]
label: StringProperty(name="Label", default="Printer") # pyright: ignore[reportInvalidTypeForm]
username: StringProperty(name="Username", default="") # pyright: ignore[reportInvalidTypeForm]
password: StringProperty(name="Password", default="", subtype='PASSWORD') # pyright: ignore[reportInvalidTypeForm]
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_type: EnumProperty(
name="Type",
@ -145,7 +152,8 @@ class SLICESOCK_Printer(InputNodeSocket):
('CREALITY', "Creality", "Creality Cloud API"),
('MOONRAKER', "Moonraker", "Moonraker (Klipper) API"),
],
default='PRUSALINK'
default='PRUSALINK',
update=notify_tree_changed
) # pyright: ignore[reportInvalidTypeForm]
bl_color = (1.0, 0.45, 0.1, 1.0) # Orange

View file

@ -39,9 +39,15 @@ def get_referenced_objects(node, visited=None):
return objs
_is_updating = False
_tree_changed = False
def notify_tree_changed(self, context):
global _tree_changed
_tree_changed = True
@persistent
def depsgraph_update_handler(scene, depsgraph):
global _is_updating
global _is_updating, _tree_changed
if _is_updating:
return
@ -55,18 +61,31 @@ def depsgraph_update_handler(scene, depsgraph):
if obj.type == 'MESH' and obj.data in updated_meshes:
updated_object_names.add(obj.name)
if not updated_object_names:
# Note: custom NodeTree updates often don't appear in depsgraph.updates
# so we rely on _tree_changed flag set by property update callbacks.
if not updated_object_names and not _tree_changed:
return
tree_needs_refresh = _tree_changed
_tree_changed = False # reset for next run
for tree in bpy.data.node_groups:
if tree.bl_idname != "SLICETREE_NodeTree":
continue
for node in tree.nodes:
if node.bl_idname == "SLICENODE_Executor" and getattr(node, "auto_refresh", False):
referenced = get_referenced_objects(node)
referenced_names = {obj.name for obj in referenced if obj}
if updated_object_names & referenced_names:
# Trigger if the tree changed OR if a referenced object changed
should_refresh = tree_needs_refresh
if not should_refresh:
referenced = get_referenced_objects(node)
referenced_names = {obj.name for obj in referenced if obj}
if updated_object_names & referenced_names:
should_refresh = True
if should_refresh:
_is_updating = True
try:
node.evaluate()