611 lines
27 KiB
Python
611 lines
27 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 ...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:
|
|
from bpy.stub_internal.rna_enums import OperatorReturnItems
|
|
|
|
def get_referenced_objects(node, visited=None):
|
|
if visited is None:
|
|
visited = set()
|
|
|
|
ptr = node.as_pointer()
|
|
if ptr in visited:
|
|
return set()
|
|
visited.add(ptr)
|
|
|
|
objs = set()
|
|
|
|
# Special case: if this is a group node, we must also check nodes inside the group
|
|
if node.bl_idname == "SLICENODE_Group" and getattr(node, "slicer_tree", None):
|
|
for internal_node in node.slicer_tree.nodes:
|
|
objs.update(get_referenced_objects(internal_node, visited))
|
|
|
|
# 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 in {"SLICESOCK_Object", "SLICESOCK_ObjectRo"}:
|
|
if not inp.is_linked and getattr(inp, "value", None):
|
|
objs.add(inp.value)
|
|
|
|
# Always follow links to find more objects upstream
|
|
if inp.is_linked:
|
|
for link in inp.links:
|
|
# Special case: if from_node is CollectionObjects, the object is identified by the socket identifier
|
|
if link.from_node.bl_idname == "SLICENODE_CollectionObjects":
|
|
obj = bpy.data.objects.get(link.from_socket.identifier)
|
|
if obj:
|
|
objs.add(obj)
|
|
|
|
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 in {"SLICESOCK_Object", "SLICESOCK_ObjectRo"}:
|
|
if getattr(outp, "value", None):
|
|
objs.add(outp.value)
|
|
elif node.bl_idname == "SLICENODE_CollectionObjects" and outp.is_linked:
|
|
obj = bpy.data.objects.get(outp.identifier)
|
|
if obj:
|
|
objs.add(obj)
|
|
|
|
return objs
|
|
|
|
_is_updating = False
|
|
_tree_changed = False
|
|
|
|
def tree_update_handler(scene, depsgraph):
|
|
global _tree_changed, _is_updating
|
|
|
|
# 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.name 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:
|
|
from ...infra.blender.blender_slice_nodes_generic import feedback_disabled
|
|
with feedback_disabled():
|
|
with node_execution_feedback(node):
|
|
node.evaluate()
|
|
except Exception as e:
|
|
print(f"Auto-refresh failed: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
finally:
|
|
_is_updating = False
|
|
|
|
def object_init_handler(scene, depsgraph):
|
|
obj = bpy.context.active_object
|
|
if not obj or not hasattr(obj, "nodeslicer"):
|
|
return
|
|
|
|
pg = getattr(obj, 'nodeslicer', None)
|
|
if not pg:
|
|
return
|
|
|
|
update_collection(pg.modifiers)
|
|
|
|
@persistent
|
|
def depsgraph_update_handler(scene, depsgraph):
|
|
global _is_updating, _tree_changed
|
|
if _is_updating: return
|
|
|
|
tree_update_handler(scene, depsgraph)
|
|
object_init_handler(scene, depsgraph)
|
|
|
|
# Sync SLICENODE_Group sockets if interface changed
|
|
for tree in bpy.data.node_groups:
|
|
if tree.bl_idname == "SLICETREE_NodeTree":
|
|
for node in tree.nodes:
|
|
if node.bl_idname == "SLICENODE_Group" and hasattr(node, "sync_sockets"):
|
|
try:
|
|
node.sync_sockets()
|
|
except Exception:
|
|
pass
|
|
elif node.bl_idname == "SLICENODE_CollectionObjects" and hasattr(node, "_update_outputs"):
|
|
try:
|
|
node._update_outputs()
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
@register_class
|
|
class SLICETREE_NodeTree(NodeTree):
|
|
bl_idname = "SLICETREE_NodeTree"
|
|
bl_label = "NodeSlicer Tree"
|
|
bl_icon = "NODETREE"
|
|
bl_group_idname = "SLICENODE_Group"
|
|
bl_use_group_interface = True
|
|
|
|
@register_class
|
|
class SLICENODE_OT_GroupToggle(Operator):
|
|
bl_idname = "slicenode.group_toggle"
|
|
bl_label = "Toggle Group"
|
|
bl_description = "Enter or exit the selected node group"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
node_name: bpy.props.StringProperty() # pyright: ignore[reportInvalidTypeForm]
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
assert context.space_data
|
|
return (context.space_data.type == 'NODE_EDITOR' and
|
|
context.space_data.tree_type == "SLICETREE_NodeTree") # pyright: ignore[reportAttributeAccessIssue]
|
|
|
|
def execute(self, context) -> set['OperatorReturnItems']:
|
|
space = context.space_data
|
|
path = getattr(space, 'path', None)
|
|
if not path:
|
|
return {'CANCELLED'}
|
|
|
|
tree = getattr(space, 'edit_tree', None)
|
|
|
|
target_node = None
|
|
if self.node_name and tree and self.node_name in tree.nodes:
|
|
target_node = tree.nodes[self.node_name]
|
|
else:
|
|
target_node = context.active_node
|
|
|
|
# If we have a target group node, we try to enter it
|
|
if target_node and target_node.bl_idname == "SLICENODE_Group":
|
|
group_tree = getattr(target_node, "slicer_tree", None)
|
|
if group_tree:
|
|
path.append(group_tree, node=target_node)
|
|
return {'FINISHED'}
|
|
|
|
# Otherwise, if we are nested, we pop out
|
|
if len(path) > 1:
|
|
path.pop()
|
|
return {'FINISHED'}
|
|
|
|
return {'CANCELLED'}
|
|
|
|
|
|
@register_class
|
|
class SLICENODE_OT_GroupMake(Operator):
|
|
bl_idname = "slicenode.group_make"
|
|
bl_label = "Make Group"
|
|
bl_description = "Make a group from selected nodes"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
assert context.space_data
|
|
return (context.space_data.type == 'NODE_EDITOR' and
|
|
context.space_data.edit_tree and # pyright: ignore[reportAttributeAccessIssue, reportOptionalMemberAccess]
|
|
context.space_data.edit_tree.bl_idname == "SLICETREE_NodeTree" and # pyright: ignore[reportAttributeAccessIssue, reportOptionalMemberAccess]
|
|
any(n.select for n in context.space_data.edit_tree.nodes)) # pyright: ignore[reportAttributeAccessIssue, reportOptionalMemberAccess]
|
|
|
|
def execute(self, context):
|
|
import bpy
|
|
from mathutils import Vector
|
|
space = context.space_data
|
|
tree = space.edit_tree # pyright: ignore[reportAttributeAccessIssue, reportOptionalMemberAccess]
|
|
|
|
selected_nodes = [n for n in tree.nodes if n.select and n.bl_idname != 'NodeReroute']
|
|
if not selected_nodes:
|
|
return {'CANCELLED'}
|
|
|
|
# Store selection to restore later or for mapping
|
|
original_selection = selected_nodes[:]
|
|
|
|
# Create new tree
|
|
new_tree = bpy.data.node_groups.new("NodeGroup", "SLICETREE_NodeTree")
|
|
|
|
# 1. Map connections
|
|
node_map = {}
|
|
for old_node in selected_nodes:
|
|
new_node = new_tree.nodes.new(old_node.bl_idname)
|
|
new_node.location = old_node.location
|
|
new_node.name = old_node.name
|
|
# Copy node properties (simplified)
|
|
for prop in old_node.bl_rna.properties:
|
|
if prop.is_readonly or prop.identifier in {'rna_type', 'name', 'location', 'dimensions'}:
|
|
continue
|
|
try:
|
|
setattr(new_node, prop.identifier, getattr(old_node, prop.identifier))
|
|
except:
|
|
pass
|
|
|
|
# Copy socket properties (value, default_value, etc)
|
|
for old_sock, new_sock in zip(old_node.inputs, new_node.inputs):
|
|
for prop in old_sock.bl_rna.properties:
|
|
if prop.is_readonly or prop.identifier in {'rna_type', 'name', 'identifier', 'is_linked', 'is_output', 'hide'}:
|
|
continue
|
|
try:
|
|
setattr(new_sock, prop.identifier, getattr(old_sock, prop.identifier))
|
|
except:
|
|
pass
|
|
|
|
for old_sock, new_sock in zip(old_node.outputs, new_node.outputs):
|
|
for prop in old_sock.bl_rna.properties:
|
|
if prop.is_readonly or prop.identifier in {'rna_type', 'name', 'identifier', 'is_linked', 'is_output', 'hide'}:
|
|
continue
|
|
try:
|
|
setattr(new_sock, prop.identifier, getattr(old_sock, prop.identifier))
|
|
except:
|
|
pass
|
|
|
|
node_map[old_node] = new_node
|
|
|
|
internal_links = []
|
|
external_inputs = [] # link
|
|
external_outputs = [] # link
|
|
|
|
for link in tree.links:
|
|
from_in_selection = link.from_node in node_map
|
|
to_in_selection = link.to_node in node_map
|
|
|
|
if from_in_selection and to_in_selection:
|
|
internal_links.append(link)
|
|
elif to_in_selection:
|
|
external_inputs.append(link)
|
|
elif from_in_selection:
|
|
external_outputs.append(link)
|
|
|
|
# 2. Create interface and Group Input/Output nodes
|
|
has_interface = hasattr(new_tree, "interface")
|
|
avg_loc = sum((n.location for n in selected_nodes), Vector((0,0))) / len(selected_nodes)
|
|
|
|
print(f"GroupMake: new_tree '{new_tree.name}' interface type: {type(new_tree.interface)}")
|
|
|
|
group_input_node = new_tree.nodes.new("NodeGroupInput")
|
|
group_input_node.location = (min(n.location.x for n in node_map.values()) - 400, avg_loc.y)
|
|
|
|
group_output_node = new_tree.nodes.new("NodeGroupOutput")
|
|
group_output_node.location = (max(n.location.x for n in node_map.values()) + 400, avg_loc.y)
|
|
|
|
# Map external inputs to new tree inputs
|
|
input_mapping = [] # (old_link, interface_item)
|
|
print(f"GroupMake: Processing {len(external_inputs)} external inputs")
|
|
for link in external_inputs:
|
|
# Check if we already have an interface item for this source socket
|
|
item = next((m[1] for m in input_mapping if m[0].from_socket == link.from_socket), None)
|
|
if not item:
|
|
name = link.to_socket.name
|
|
if has_interface:
|
|
socket_type = link.to_socket.bl_idname
|
|
print(f"GroupMake: Creating interface input '{name}' of type '{socket_type}'")
|
|
try:
|
|
item = new_tree.interface.new_socket(name=name, in_out='INPUT', socket_type=socket_type)
|
|
print(f"GroupMake: Successfully created item '{item.name}' (ID: {item.identifier})")
|
|
except Exception as e:
|
|
print(f"GroupMake: FAILED to create interface input: {e}")
|
|
continue
|
|
else:
|
|
item = new_tree.inputs.new(link.to_socket.bl_idname, name)
|
|
input_mapping.append((link, item))
|
|
|
|
# Find the corresponding socket on group_input_node
|
|
target_socket = None
|
|
if has_interface:
|
|
print(f"GroupMake: Looking for GroupInput socket with identifier '{item.identifier}' or name '{item.name}'")
|
|
target_socket = group_input_node.outputs.get(item.identifier) or group_input_node.outputs.get(item.name)
|
|
else:
|
|
target_socket = group_input_node.outputs[new_tree.inputs.find(item.name)]
|
|
|
|
if target_socket:
|
|
new_tree.links.new(target_socket, node_map[link.to_node].inputs[link.to_socket.identifier])
|
|
else:
|
|
print(f"GroupMake: Warning - target socket not found on GroupInput. Available: {[o.name for o in group_input_node.outputs]}")
|
|
|
|
# Map external outputs to new tree outputs
|
|
output_mapping = [] # (old_link, interface_item)
|
|
print(f"GroupMake: Processing {len(external_outputs)} external outputs")
|
|
for link in external_outputs:
|
|
item = next((m[1] for m in output_mapping if m[0].from_socket == link.from_socket), None)
|
|
if not item:
|
|
name = link.from_socket.name
|
|
if has_interface:
|
|
socket_type = link.from_socket.bl_idname
|
|
print(f"GroupMake: Creating interface output '{name}' of type '{socket_type}'")
|
|
try:
|
|
item = new_tree.interface.new_socket(name=name, in_out='OUTPUT', socket_type=socket_type)
|
|
print(f"GroupMake: Successfully created item '{item.name}' (ID: {item.identifier})")
|
|
except Exception as e:
|
|
print(f"GroupMake: FAILED to create interface output: {e}")
|
|
continue
|
|
else:
|
|
item = new_tree.outputs.new(link.from_socket.bl_idname, name)
|
|
output_mapping.append((link, item))
|
|
|
|
target_socket = None
|
|
if has_interface:
|
|
print(f"GroupMake: Looking for GroupOutput socket with identifier '{item.identifier}' or name '{item.name}'")
|
|
target_socket = group_output_node.inputs.get(item.identifier) or group_output_node.inputs.get(item.name)
|
|
else:
|
|
target_socket = group_output_node.inputs[new_tree.outputs.find(item.name)]
|
|
|
|
if target_socket:
|
|
new_tree.links.new(node_map[link.from_node].outputs[link.from_socket.identifier], target_socket)
|
|
else:
|
|
print(f"GroupMake: Warning - target socket not found on GroupOutput. Available: {[i.name for i in group_output_node.inputs]}")
|
|
|
|
# 3. Re-create internal links
|
|
for link in internal_links:
|
|
try:
|
|
new_tree.links.new(node_map[link.from_node].outputs[link.from_socket.identifier],
|
|
node_map[link.to_node].inputs[link.to_socket.identifier])
|
|
except:
|
|
pass
|
|
|
|
# 4. Create the group node and assign tree
|
|
group_node = tree.nodes.new("SLICENODE_Group")
|
|
group_node.location = avg_loc
|
|
|
|
# Robustly get interface items count
|
|
interface_items = []
|
|
if has_interface:
|
|
items_attr = getattr(new_tree.interface, "items", None)
|
|
if items_attr is not None and not callable(items_attr):
|
|
interface_items = items_attr
|
|
else:
|
|
# Try iterating directly
|
|
try:
|
|
interface_items = [item for item in new_tree.interface]
|
|
except:
|
|
pass
|
|
|
|
print(f"GroupMake: Assigning new tree to group node. Interface items found: {len(interface_items)}")
|
|
for item in interface_items:
|
|
print(f" - Item: {getattr(item, 'name', 'unnamed')}, Type: {getattr(item, 'item_type', 'unknown')}, Socket: {getattr(item, 'socket_type', 'unknown')}")
|
|
|
|
# Assigning slicer_tree triggers mirroring via update callback
|
|
group_node.slicer_tree = new_tree
|
|
|
|
# Force an update of the external sockets to be sure
|
|
if hasattr(new_tree, "interface_update"):
|
|
print("GroupMake: Calling interface_update(bpy.context)")
|
|
try:
|
|
new_tree.interface_update(bpy.context)
|
|
except Exception as e:
|
|
print(f"GroupMake: interface_update failed: {e}")
|
|
|
|
# Manually force sync
|
|
if hasattr(group_node, "sync_sockets"):
|
|
group_node.sync_sockets()
|
|
|
|
print(f"GroupMake: Post-sync socket count: {len(group_node.inputs)} in, {len(group_node.outputs)} out")
|
|
|
|
# Re-link external
|
|
# The group_node should have sockets mirrored from new_tree.interface
|
|
for link, item in input_mapping:
|
|
target_socket = None
|
|
if has_interface:
|
|
target_socket = group_node.inputs.get(item.name) # Try name first
|
|
if not target_socket:
|
|
target_socket = group_node.inputs.get(item.identifier)
|
|
else:
|
|
target_socket = group_node.inputs.get(item.name)
|
|
|
|
if target_socket:
|
|
tree.links.new(link.from_socket, target_socket)
|
|
|
|
for link, item in output_mapping:
|
|
target_socket = None
|
|
if has_interface:
|
|
target_socket = group_node.outputs.get(item.name)
|
|
if not target_socket:
|
|
target_socket = group_node.outputs.get(item.identifier)
|
|
else:
|
|
target_socket = group_node.outputs.get(item.name)
|
|
|
|
if target_socket:
|
|
tree.links.new(target_socket, link.to_socket)
|
|
|
|
# 5. Delete old nodes
|
|
for node in selected_nodes:
|
|
tree.nodes.remove(node)
|
|
|
|
# 6. Enter the group
|
|
space.path.append(new_tree, node=group_node) # pyright: ignore[reportAttributeAccessIssue, reportOptionalMemberAccess]
|
|
|
|
return {'FINISHED'}
|
|
|
|
@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:
|
|
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
|
|
traceback.print_exc()
|
|
|
|
return {"FINISHED"}
|
|
|
|
@register_class
|
|
class SLICENODE_MT_DataInput(Menu):
|
|
bl_idname = "SLICENODE_MT_DataInput"
|
|
bl_label = "Data & Input"
|
|
|
|
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="Vector").type = "SLICENODE_InputVector"
|
|
layout.separator()
|
|
layout.operator("node.add_node", text="Object").type = "SLICENODE_InputObject"
|
|
layout.operator("node.add_node", text="Collection Objects").type = "SLICENODE_CollectionObjects"
|
|
layout.operator("node.add_node", text="Settings").type = "SLICENODE_InputSettings"
|
|
layout.operator("node.add_node", text="PrusaSlicer Config").type = "SLICENODE_PrusaslicerConf"
|
|
layout.separator()
|
|
layout.operator("node.add_node", text="Printer").type = "SLICENODE_InputPrinter"
|
|
layout.operator("node.add_node", text="Printer List").type = "SLICENODE_PrinterList"
|
|
layout.operator("node.add_node", text="Printer Intrinsics").type = "SLICENODE_InputIntrinsics"
|
|
|
|
@register_class
|
|
class SLICENODE_MT_Geometry(Menu):
|
|
bl_idname = "SLICENODE_MT_Geometry"
|
|
bl_label = "Geometry"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
assert layout
|
|
layout.operator("node.add_node", text="Get Mesh").type = "SLICENODE_GetMesh"
|
|
layout.operator("node.add_node", text="Transform Geometry").type = "SLICENODE_TransformGeometry"
|
|
layout.operator("node.add_node", text="Drop Geometry").type = "SLICENODE_DropGeometry"
|
|
layout.operator("node.add_node", text="Separate Mesh").type = "SLICENODE_SeparateMesh"
|
|
layout.operator("node.add_node", text="Separate Mesh by Range").type = "SLICENODE_SeparateMeshByRange"
|
|
layout.operator("node.add_node", text="Arrange Slicing Group").type = "SLICENODE_ArrangeMesh"
|
|
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="Buffer Curve").type = "SLICENODE_BufferCurve"
|
|
layout.operator("node.add_node", text="Offset Curve").type = "SLICENODE_OffsetCurve"
|
|
layout.operator("node.add_node", text="Path Offset").type = "SLICENODE_PathOffset"
|
|
layout.operator("node.add_node", text="Rotate Path").type = "SLICENODE_RotatePath"
|
|
layout.operator("node.add_node", text="Simplify Paths").type = "SLICENODE_SimplifyPaths"
|
|
layout.operator("node.add_node", text="Infill").type = "SLICENODE_Infill"
|
|
layout.operator("node.add_node", text="Get Infill Mask").type = "SLICENODE_GetInfillMask"
|
|
layout.operator("node.add_node", text="Boolean Paths").type = "SLICENODE_JoinSlices"
|
|
layout.separator()
|
|
layout.operator("node.add_node", text="Math").type = "SLICENODE_FloatMath"
|
|
layout.operator("node.add_node", text="Integer Math").type = "SLICENODE_IntMath"
|
|
|
|
@register_class
|
|
class SLICENODE_MT_Gcode(Menu):
|
|
bl_idname = "SLICENODE_MT_Gcode"
|
|
bl_label = "Gcode"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
assert layout
|
|
layout.operator("node.add_node", text="Paths to Gcode").type = "SLICENODE_PathsToGcode"
|
|
layout.operator("node.add_node", text="Gcode Block").type = "SLICENODE_GcodeBlock"
|
|
layout.operator("node.add_node", text="Gcode Regex Replace").type = "SLICENODE_GcodeRegexReplace"
|
|
layout.operator("node.add_node", text="Assign Metadata").type = "SLICENODE_AssignMetadata"
|
|
layout.operator("node.add_node", text="Gcode Helper").type = "SLICENODE_GcodeHelper"
|
|
layout.operator("node.add_node", text="Load Gcode").type = "SLICENODE_LoadGcode"
|
|
|
|
@register_class
|
|
class SLICENODE_MT_Joiners(Menu):
|
|
bl_idname = "SLICENODE_MT_Joiners"
|
|
bl_label = "Joiners"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
assert layout
|
|
layout.operator("node.add_node", text="Join Mesh Parts").type = "SLICENODE_JoinMesh"
|
|
layout.operator("node.add_node", text="Join Mesh Groups").type = "SLICENODE_JoinMeshGroups"
|
|
layout.operator("node.add_node", text="Join Paths").type = "SLICENODE_JoinPaths"
|
|
layout.operator("node.add_node", text="Join Gcode").type = "SLICENODE_JoinGcode"
|
|
layout.operator("node.add_node", text="Join Settings").type = "SLICENODE_JoinSettings"
|
|
layout.operator("node.add_node", text="Join Execution").type = "SLICENODE_JoinExecution"
|
|
|
|
@register_class
|
|
class SLICENODE_MT_ActionIO(Menu):
|
|
bl_idname = "SLICENODE_MT_ActionIO"
|
|
bl_label = "Action & I/O"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
assert layout
|
|
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.separator()
|
|
layout.operator("node.add_node", text="Baserow Submit").type = "SLICENODE_BaserowSubmit"
|
|
layout.operator("node.add_node", text="Airtable Submit").type = "SLICENODE_AirtableSubmit"
|
|
layout.separator()
|
|
layout.operator("node.add_node", text="Gcode to Preview Mesh").type = "SLICENODE_GcodeToMesh"
|
|
layout.operator("node.add_node", text="PrusaSlicer Preview").type = "SLICENODE_PrusaslicerPreview"
|
|
layout.operator("node.add_node", text="PrusaSlicer Slice").type = "SLICENODE_PrusaslicerSlice"
|
|
layout.operator("node.add_node", text="Executor").type = "SLICENODE_Executor"
|
|
layout.operator("node.add_node", text="Test").type = "SLICENODE_Test"
|
|
|
|
@register_class
|
|
class SLICENODE_MT_Group(Menu):
|
|
bl_idname = "SLICENODE_MT_Group"
|
|
bl_label = "Group"
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
assert layout
|
|
layout.operator("slicenode.group_make", text="Make Group")
|
|
layout.operator("node.group_ungroup", text="Ungroup")
|
|
layout.operator("slicenode.group_toggle", text="Edit Group")
|
|
layout.separator()
|
|
layout.operator("node.add_node", text="Add Group Node").type = "SLICENODE_Group"
|
|
layout.separator()
|
|
layout.operator("node.add_node", text="Group Input").type = "NodeGroupInput"
|
|
layout.operator("node.add_node", text="Group Output").type = "NodeGroupOutput"
|
|
|
|
@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.menu("SLICENODE_MT_DataInput")
|
|
layout.menu("SLICENODE_MT_Geometry")
|
|
layout.menu("SLICENODE_MT_Gcode")
|
|
layout.menu("SLICENODE_MT_Joiners")
|
|
layout.menu("SLICENODE_MT_ActionIO")
|
|
layout.separator()
|
|
layout.menu("SLICENODE_MT_Group")
|
|
|
|
|
|
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")
|