180 lines
7.8 KiB
Python
180 lines
7.8 KiB
Python
from __future__ import annotations
|
|
from typing import TYPE_CHECKING
|
|
import bpy
|
|
from bpy.types import NodeTree, Operator, Menu
|
|
from bpy.app.handlers import persistent
|
|
|
|
from ...registry import register_class
|
|
|
|
if TYPE_CHECKING:
|
|
from bpy.stub_internal.rna_enums import OperatorReturnItems
|
|
|
|
def get_referenced_objects(node, visited=None):
|
|
if visited is None:
|
|
visited = set()
|
|
if node in visited:
|
|
return set()
|
|
visited.add(node)
|
|
|
|
objs = set()
|
|
# Check all input sockets for objects
|
|
for inp in node.inputs:
|
|
# If it's an object socket, it might have a value if not linked
|
|
if inp.bl_idname == "SLICESOCK_Object":
|
|
if not inp.is_linked and inp.value:
|
|
objs.add(inp.value)
|
|
|
|
# Always follow links to find more objects upstream
|
|
if inp.is_linked:
|
|
for link in inp.links:
|
|
objs.update(get_referenced_objects(link.from_node, visited))
|
|
|
|
# Also check output sockets of the node itself,
|
|
# as some nodes might "hold" an object (like InputObject)
|
|
for outp in node.outputs:
|
|
if outp.bl_idname == "SLICESOCK_Object":
|
|
if outp.value:
|
|
objs.add(outp.value)
|
|
|
|
return objs
|
|
|
|
_is_updating = False
|
|
_tree_changed = False
|
|
|
|
def tree_update_handler(scene, depsgraph):
|
|
global _tree_changed
|
|
|
|
# Names of objects that have changed
|
|
updated_object_names = {update.id.name for update in depsgraph.updates if isinstance(update.id, bpy.types.Object)}
|
|
|
|
# Also check for mesh updates (happens in Edit Mode)
|
|
updated_meshes = {update.id.name for update in depsgraph.updates if isinstance(update.id, bpy.types.Mesh)}
|
|
if updated_meshes:
|
|
for obj in bpy.data.objects:
|
|
if obj.type == 'MESH' and obj.data in updated_meshes:
|
|
updated_object_names.add(obj.name)
|
|
|
|
# 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):
|
|
# 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()
|
|
except Exception as e:
|
|
print(f"Auto-refresh failed: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
finally:
|
|
_is_updating = False
|
|
|
|
@persistent
|
|
def depsgraph_update_handler(scene, depsgraph):
|
|
global _is_updating, _tree_changed
|
|
if _is_updating: return
|
|
|
|
tree_update_handler(scene, depsgraph)
|
|
|
|
|
|
@register_class
|
|
class SLICETREE_NodeTree(NodeTree):
|
|
bl_idname = "SLICETREE_NodeTree"
|
|
bl_label = "NodeSlicer Tree"
|
|
bl_icon = "NODETREE"
|
|
|
|
@register_class
|
|
class SLICENODE_OT_Execute(Operator):
|
|
bl_idname = "slicenode.execute"
|
|
bl_label = "Execute"
|
|
bl_description = "Execute"
|
|
|
|
node_name: bpy.props.StringProperty() # pyright: ignore[reportInvalidTypeForm]
|
|
|
|
def execute(self, context) -> set['OperatorReturnItems']:
|
|
node_tree = context.space_data.edit_tree # pyright: ignore[reportAttributeAccessIssue, reportOptionalMemberAccess]
|
|
node = node_tree.nodes[self.node_name]
|
|
|
|
try:
|
|
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
|
|
traceback.print_exc()
|
|
|
|
return {"FINISHED"}
|
|
|
|
@register_class
|
|
class SLICENODE_MT_Menu(Menu):
|
|
bl_idname = "SLICENODE_MT_Menu"
|
|
bl_label = "SliceNodes"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
assert layout
|
|
layout.operator("node.add_node", text="String").type = "SLICENODE_InputString"
|
|
layout.operator("node.add_node", text="Float").type = "SLICENODE_InputFloat"
|
|
layout.operator("node.add_node", text="Integer").type = "SLICENODE_InputInt"
|
|
layout.operator("node.add_node", text="Rotation").type = "SLICENODE_InputVectorRotation"
|
|
layout.operator("node.add_node", text="Math").type = "SLICENODE_FloatMath"
|
|
layout.operator("node.add_node", text="Integer Math").type = "SLICENODE_IntMath"
|
|
layout.operator("node.add_node", text="Object").type = "SLICENODE_InputObject"
|
|
layout.operator("node.add_node", text="Printer").type = "SLICENODE_InputPrinter"
|
|
layout.operator("node.add_node", text="PrusaSlicer Config").type = "SLICENODE_PrusaslicerConf"
|
|
layout.operator("node.add_node", text="Printer List").type = "SLICENODE_PrinterList"
|
|
layout.operator("node.add_node", text="Printer Intrinsics").type = "SLICENODE_InputIntrinsics"
|
|
layout.operator("node.add_node", text="Test").type = "SLICENODE_Test"
|
|
layout.operator("node.add_node", text="Get Mesh").type = "SLICENODE_GetMesh"
|
|
layout.operator("node.add_node", text="Get Paths").type = "SLICENODE_GetPaths"
|
|
layout.separator()
|
|
layout.operator("node.add_node", text="Slice Shells").type = "SLICENODE_SliceShells"
|
|
layout.operator("node.add_node", text="Buffer Shells").type = "SLICENODE_BufferShells"
|
|
layout.operator("node.add_node", text="Buffer Shells Multiple").type = "SLICENODE_BufferShellsMultiple"
|
|
layout.operator("node.add_node", text="Infill").type = "SLICENODE_Infill"
|
|
layout.operator("node.add_node", text="Paths to Gcode").type = "SLICENODE_PathsToGcode"
|
|
layout.separator()
|
|
layout.operator("node.add_node", text="Gcode Block").type = "SLICENODE_GcodeBlock"
|
|
layout.operator("node.add_node", text="Gcode Helper").type = "SLICENODE_GcodeHelper"
|
|
layout.operator("node.add_node", text="Load Gcode").type = "SLICENODE_LoadGcode"
|
|
layout.separator()
|
|
layout.operator("node.add_node", text="Join Gcode").type = "SLICENODE_JoinGcode"
|
|
layout.operator("node.add_node", text="Join Mesh").type = "SLICENODE_JoinMesh"
|
|
layout.operator("node.add_node", text="Join Paths").type = "SLICENODE_JoinPaths"
|
|
layout.operator("node.add_node", text="Join Execution").type = "SLICENODE_JoinExecution"
|
|
layout.operator("node.add_node", text="Boolean Paths").type = "SLICENODE_JoinSlices"
|
|
layout.separator()
|
|
layout.operator("node.add_node", text="Gcode to File").type = "SLICENODE_GcodeToFile"
|
|
layout.operator("node.add_node", text="Gcode to Text Block").type = "SLICENODE_GcodeToTextBlock"
|
|
layout.operator("node.add_node", text="Start Print").type = "SLICENODE_StartPrint"
|
|
layout.operator("node.add_node", text="Gcode to Mesh").type = "SLICENODE_GcodeToMesh"
|
|
layout.operator("node.add_node", text="PrusaSlicer Slice").type = "SLICENODE_PrusaslicerSlice"
|
|
layout.operator("node.add_node", text="Executor").type = "SLICENODE_Executor"
|
|
|
|
def draw_add_menu(self, context):
|
|
space = context.space_data
|
|
tree = getattr(space, "edit_tree", None)
|
|
if tree and tree.bl_idname == "SLICETREE_NodeTree":
|
|
self.layout.separator()
|
|
self.layout.menu("SLICENODE_MT_Menu")
|