From f532fada234489e56308fc654116d4641dab7517 Mon Sep 17 00:00:00 2001 From: Nicolas Predella Date: Wed, 20 May 2026 18:44:29 +0300 Subject: [PATCH] visualization --- core/nodeslicer/nodes.py | 14 ++--- core/nodeslicer/tree.py | 11 ++-- infra/blender/blender_slice_nodes_generic.py | 63 +++++++++++++------- 3 files changed, 56 insertions(+), 32 deletions(-) diff --git a/core/nodeslicer/nodes.py b/core/nodeslicer/nodes.py index 638fd20..df3706b 100644 --- a/core/nodeslicer/nodes.py +++ b/core/nodeslicer/nodes.py @@ -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' diff --git a/core/nodeslicer/tree.py b/core/nodeslicer/tree.py index e4f8a02..678f461 100644 --- a/core/nodeslicer/tree.py +++ b/core/nodeslicer/tree.py @@ -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 diff --git a/infra/blender/blender_slice_nodes_generic.py b/infra/blender/blender_slice_nodes_generic.py index 4265c55..4ed15cf 100644 --- a/infra/blender/blender_slice_nodes_generic.py +++ b/infra/blender/blender_slice_nodes_generic.py @@ -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) \ No newline at end of file + visited.remove(key)