visualization

This commit is contained in:
Nicolas Predella 2026-05-20 18:44:29 +03:00
parent 716e619c28
commit f532fada23
3 changed files with 56 additions and 32 deletions

View file

@ -1315,26 +1315,24 @@ class SLICENODE_AirtableSubmit(ExecSliceNode_DatabaseSubmit):
return str(record_id)
@register_class
class SLICENODE_PrusaslicerSlice(ExecSliceNode):
class SLICENODE_PrusaslicerSlice(SliceNode):
bl_label = "PrusaSlicer Slice"
button_label = "Slice with PrusaSlicer"
prusaslicer_executable: NfStringProperty(name="PrusaSlicer Path", subtype='FILE_PATH', default='flatpak run com.prusa3d.PrusaSlicer') # pyright: ignore[reportInvalidTypeForm]
gcode_result: NfStringProperty(name="Gcode Result", default="") # pyright: ignore[reportInvalidTypeForm]
def init(self, context):
self._init_exec()
self.add_input("SLICESOCK_Mesh", "Mesh")
self.add_input("SLICESOCK_PrusaslicerConf", "Config")
self.add_input("SLICESOCK_Overrides", "Overrides")
self.add_output("SLICESOCK_GcodeRo")
def draw_buttons(self, context, layout):
super().draw_buttons(context, layout)
layout.prop(self, "prusaslicer_executable")
def evaluate_output(self, socket_name: str, **kwargs) -> Any:
if socket_name == "Gcode":
self.evaluate()
return self.gcode_result
return super().evaluate_output(socket_name, **kwargs)
@ -1603,6 +1601,7 @@ class SLICENODE_Executor(ExecSliceNode, SliceNodeJoiner):
layout.prop(self, "auto_refresh")
def evaluate(self, operator=None):
from ...infra.blender.blender_slice_nodes_generic import node_execution_feedback
for i, job in enumerate(self.inputs):
# Resolve the input to get the connected node(s)
res = resolve_input(job)
@ -1620,9 +1619,10 @@ class SLICENODE_Executor(ExecSliceNode, SliceNodeJoiner):
operator.report({'INFO'}, f"Executing: {upstream_node.bl_label}")
try:
exec_res = upstream_node.evaluate(operator=operator)
if exec_res != 'ok' and exec_res is not None:
if operator: operator.report({'WARNING'}, f"Node {upstream_node.bl_label} returned {exec_res}")
with node_execution_feedback(upstream_node):
exec_res = upstream_node.evaluate(operator=operator)
if exec_res != 'ok' and exec_res is not None:
if operator: operator.report({'WARNING'}, f"Node {upstream_node.bl_label} returned {exec_res}")
except Exception as e:
if operator: operator.report({'ERROR'}, f"Failed executing {upstream_node.bl_label}: {str(e)}")
return 'error'

View file

@ -5,6 +5,7 @@ from bpy.types import NodeTree, Operator, Menu
from bpy.app.handlers import persistent
from ...infra.blender.mixins import update_collection
from ...infra.blender.blender_slice_nodes_generic import node_execution_feedback, resolve_input
from ...registry import register_class
if TYPE_CHECKING:
@ -84,7 +85,8 @@ def tree_update_handler(scene, depsgraph):
if should_refresh:
_is_updating = True
try:
node.evaluate()
with node_execution_feedback(node):
node.evaluate()
except Exception as e:
print(f"Auto-refresh failed: {e}")
import traceback
@ -130,9 +132,10 @@ class SLICENODE_OT_Execute(Operator):
node = node_tree.nodes[self.node_name]
try:
res = node.evaluate(operator=self)
if res == 'ok':
self.report({"INFO"}, f"Execution Successful")
with node_execution_feedback(node):
res = node.evaluate(operator=self)
if res == 'ok':
self.report({"INFO"}, f"Execution Successful")
except Exception as e:
self.report({"ERROR"}, f"Execution failed: {str(e)}")
import traceback

View file

@ -1,5 +1,7 @@
from __future__ import annotations
import typing
import bpy
from contextlib import contextmanager
from typing import Any
from bpy.types import NodeSocket, Node
@ -41,6 +43,24 @@ class SliceNode(Node, AutoBlIdName):
## Evaluation helpers
@contextmanager
def node_execution_feedback(node: bpy.types.Node):
# Save original color
old_use_color = node.use_custom_color
old_color = node.color[:]
node.use_custom_color = True
node.color = (75/255, 73/255, 19/255) # Green
# Refresh UI
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
try:
yield
finally:
node.use_custom_color = old_use_color
node.color = old_color
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
def _follow_reroute(socket: NodeSocket) -> NodeSocket:
current: NodeSocket = socket
seen: set[int] = set()
@ -106,27 +126,28 @@ def _resolve_output(
visited.add(key)
try:
if not isinstance(socket.node, SliceNode):
return default
with node_execution_feedback(socket.node):
if not isinstance(socket.node, SliceNode):
return default
if getattr(socket.node, "mute", False):
# Attempt bypass: find a matching input socket by name or type
input_to_bypass = socket.node.inputs.get(socket.name)
if not input_to_bypass:
for inp in socket.node.inputs:
if inp.bl_idname == socket.bl_idname:
input_to_bypass = inp
break
if input_to_bypass:
value = resolve_input(typing.cast(Any, input_to_bypass), cache=cache, visited=visited)
cache[key] = value
return value
return default
if getattr(socket.node, "mute", False):
# Attempt bypass: find a matching input socket by name or type
input_to_bypass = socket.node.inputs.get(socket.name)
if not input_to_bypass:
for inp in socket.node.inputs:
if inp.bl_idname == socket.bl_idname:
input_to_bypass = inp
break
if input_to_bypass:
value = resolve_input(typing.cast(Any, input_to_bypass), cache=cache, visited=visited)
cache[key] = value
return value
return default
assert socket.node.evaluate_output
value = socket.node.evaluate_output(socket.name, cache=cache, visited=visited)
cache[key] = value
return value
assert socket.node.evaluate_output
value = socket.node.evaluate_output(socket.name, cache=cache, visited=visited)
cache[key] = value
return value
finally:
visited.remove(key)
visited.remove(key)