706 lines
No EOL
23 KiB
Python
706 lines
No EOL
23 KiB
Python
from __future__ import annotations
|
|
from numpy._typing._array_like import NDArray
|
|
from numpy import float64
|
|
|
|
bl_info = {
|
|
"name": "NodeSlicer",
|
|
"author": "OpenAI",
|
|
"version": (0, 1, 0),
|
|
"blender": (3, 6, 0),
|
|
"location": "Node Editor > Add",
|
|
"description": "Very small custom nodes example",
|
|
"category": "Node",
|
|
}
|
|
|
|
from math import floor
|
|
from typing import TYPE_CHECKING, Any
|
|
import bpy
|
|
import os
|
|
import numpy as np
|
|
|
|
from bpy.types import NodeTree, Operator, Menu
|
|
from bpy.props import PointerProperty, StringProperty, FloatProperty, IntProperty
|
|
|
|
from .registry import register_class
|
|
from .core import slice
|
|
from .core.slice import Pather, slice_at_range
|
|
from .infra.blender_mesh_capture import SlicingObject, capture_mesh_edges
|
|
from .infra.blender_slice_nodes_generic import SliceNode, SliceNodeSocket, resolve_input
|
|
|
|
if TYPE_CHECKING:
|
|
from bpy.stub_internal.rna_enums import OperatorReturnItems
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Node tree
|
|
# -----------------------------------------------------------------------------
|
|
|
|
@register_class
|
|
class SLICETREE_NodeTree(NodeTree):
|
|
bl_idname = "SLICETREE_NodeTree"
|
|
bl_label = "NodeSlicer Tree"
|
|
bl_icon = "NODETREE"
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Sockets
|
|
# -----------------------------------------------------------------------------
|
|
|
|
class LabelNodeSocket(SliceNodeSocket):
|
|
default = ""
|
|
def draw(self, context: Any, layout: Any, node: Any, text: str | None):
|
|
assert layout
|
|
layout.label(text=text or self.name)
|
|
|
|
class InputNodeSocket(SliceNodeSocket):
|
|
def draw(self, context: Any, layout: Any, node: Any, text: str | None):
|
|
assert layout
|
|
if not self.is_output and self.is_linked:
|
|
layout.label(text=text or self.name)
|
|
else:
|
|
layout.prop(self, "value", text=text or self.name)
|
|
|
|
def _draw_as_ro(self, context: Any, layout: Any, node: Any, text: str | None):
|
|
layout.label(text=text or self.name)
|
|
|
|
def _draw_as_rw(self, context: Any, layout: Any, node: Any, text: str | None):
|
|
layout.prop(self, "value", text=text or self.name)
|
|
|
|
class ReadonlySocket():
|
|
def draw(self, context: Any, layout: Any, node: Any, text: str | None):
|
|
return self._draw_as_ro(context, layout, node, text) # pyright: ignore[reportAttributeAccessIssue]
|
|
|
|
@register_class
|
|
class SLICESOCK_Object(InputNodeSocket):
|
|
bl_label = "Object"
|
|
|
|
value: PointerProperty(
|
|
name="Object",
|
|
type=bpy.types.Object,
|
|
) # pyright: ignore[reportInvalidTypeForm]
|
|
color = (0.9, 0.7, 0.2, 1.0)
|
|
|
|
|
|
class SLICESOCK_Float_Common(InputNodeSocket):
|
|
bl_label = "Float"
|
|
|
|
value: FloatProperty(name="Float", default=0) # pyright: ignore[reportInvalidTypeForm]
|
|
color = (161/255, 161/255, 161/255, 1.0)
|
|
|
|
@register_class
|
|
class SLICESOCK_FloatRo(ReadonlySocket, SLICESOCK_Float_Common): ...
|
|
@register_class
|
|
class SLICESOCK_Float(SLICESOCK_Float_Common): ...
|
|
|
|
|
|
class SLICESOCK_Int_Common(InputNodeSocket):
|
|
bl_label = "Integer"
|
|
|
|
value: IntProperty(name="Int", default=0) # pyright: ignore[reportInvalidTypeForm]
|
|
color = (161/255, 161/255, 161/255, 1.0)
|
|
|
|
@register_class
|
|
class SLICESOCK_Int(SLICESOCK_Int_Common): ...
|
|
@register_class
|
|
class SLICESOCK_IntRo(ReadonlySocket, SLICESOCK_Int_Common): ...
|
|
|
|
|
|
@register_class
|
|
class SLICESOCK_String(InputNodeSocket):
|
|
bl_label = "String"
|
|
|
|
value: StringProperty(name="String", default="") # pyright: ignore[reportInvalidTypeForm]
|
|
color = (0.4, 0.7, 1.0, 1.0)
|
|
|
|
@register_class
|
|
class SLICESOCK_Mesh(LabelNodeSocket):
|
|
bl_label = "Mesh"
|
|
default = None
|
|
color = (0, 214/255, 163/255, 1.0)
|
|
|
|
@register_class
|
|
class SLICESOCK_Slices(LabelNodeSocket):
|
|
bl_label = "Slices"
|
|
default = []
|
|
color = (0, 214/255, 96/255, 1.0)
|
|
|
|
@register_class
|
|
class SLICESOCK_Paths(LabelNodeSocket):
|
|
bl_label = "Paths"
|
|
default = []
|
|
color = (214/255, 0, 120/255, 1.0)
|
|
|
|
@register_class
|
|
class SLICESOCK_Gcode(LabelNodeSocket):
|
|
bl_label = "Gcode"
|
|
default = ""
|
|
color = (1., 1., 1., 1.)
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Nodes
|
|
# -----------------------------------------------------------------------------
|
|
|
|
# Basic Inputs
|
|
|
|
class InputNode(SliceNode):
|
|
input_type: str = ""
|
|
|
|
def __init_subclass__(cls, **kwargs):
|
|
super().__init_subclass__(**kwargs)
|
|
if "bl_label" not in cls.__dict__: cls.bl_label = cls.input_type
|
|
|
|
def init(self, context) -> None:
|
|
self.add_output(f"SLICESOCK_{self.bl_label}")
|
|
|
|
def evaluate_output(self, socket_name: str, **_kwargs):
|
|
return self.outputs.get(socket_name).value
|
|
|
|
@register_class
|
|
class SLICENODE_InputString(InputNode):
|
|
input_type = "String"
|
|
|
|
@register_class
|
|
class SLICENODE_InputFloat(InputNode):
|
|
input_type = "Float"
|
|
|
|
@register_class
|
|
class SLICENODE_InputInt(InputNode):
|
|
input_type = "Int"
|
|
|
|
@register_class
|
|
class SLICENODE_InputObject(InputNode):
|
|
input_type = "Object"
|
|
bl_icon = "OBJECT_DATA"
|
|
|
|
# Math
|
|
|
|
@register_class
|
|
class SLICENODE_IntMath(SliceNode):
|
|
bl_label = "Integer Math"
|
|
|
|
operation: bpy.props.EnumProperty(
|
|
name="Operation",
|
|
items=[
|
|
('ADD', "Add", "Add the two values"),
|
|
('SUBTRACT', "Subtract", "Subtract the second value from the first"),
|
|
('MULTIPLY', "Multiply", "Multiply the two values"),
|
|
('DIVIDE', "Divide", "Integer division"),
|
|
('ABS', "Absolute", "Absolute value of the first value"),
|
|
('NEGATE', "Negate", "Negate the first value"),
|
|
('POWER', "Power", "First value raised to the power of the second"),
|
|
('MIN', "Minimum", "The smaller of the two values"),
|
|
('MAX', "Maximum", "The larger of the two values"),
|
|
('SIGN', "Sign", "The sign of the first value (-1, 0, or 1)"),
|
|
('DIVIDE_ROUND', "Divide Round", "Division rounded to the nearest integer"),
|
|
('DIVIDE_FLOOR', "Divide Floor", "Division rounded down"),
|
|
('DIVIDE_TRUNCATE', "Divide Truncate", "Division rounded towards zero"),
|
|
('MODULO_FLOORED', "Floored Modulo", "Modulo using floor division"),
|
|
('MODULO_TRUNCATED', "Truncated Modulo", "Modulo using truncated division"),
|
|
],
|
|
default='ADD'
|
|
) # pyright: ignore[reportInvalidTypeForm]
|
|
|
|
def init(self, context):
|
|
self.add_output("SLICESOCK_IntRo", "Value")
|
|
|
|
self.add_input("SLICESOCK_Int", "Value")
|
|
self.add_input("SLICESOCK_Int", "Value")
|
|
|
|
def draw_buttons(self, context, layout):
|
|
assert layout
|
|
layout.prop(self, "operation", text="")
|
|
|
|
def evaluate_output(self, socket_name: str, **_kwargs) -> int:
|
|
a: int = resolve_input(self.inputs[0])
|
|
b: int = resolve_input(self.inputs[1])
|
|
|
|
op = self.operation
|
|
try:
|
|
if op == 'ADD': return a + b
|
|
if op == 'SUBTRACT': return a - b
|
|
if op == 'MULTIPLY': return a * b
|
|
if op == 'DIVIDE': return a // b if b != 0 else 0
|
|
if op == 'ABS': return abs(a)
|
|
if op == 'NEGATE': return -a
|
|
if op == 'POWER': return int(a ** b) if not (a == 0 and b < 0) else 0
|
|
if op == 'MIN': return min(a, b)
|
|
if op == 'MAX': return max(a, b)
|
|
if op == 'SIGN': return (a > 0) - (a < 0)
|
|
if op == 'DIVIDE_ROUND': return int(round(a / b)) if b != 0 else 0
|
|
if op == 'DIVIDE_FLOOR': return a // b if b != 0 else 0
|
|
if op == 'DIVIDE_TRUNCATE': return int(a / b) if b != 0 else 0
|
|
if op == 'MODULO_FLOORED': return a % b if b != 0 else 0
|
|
if op == 'MODULO_TRUNCATED': return a - (b * int(a / b)) if b != 0 else 0
|
|
except Exception:
|
|
return 0
|
|
return 0
|
|
|
|
@register_class
|
|
class SLICENODE_GetMesh(SliceNode):
|
|
bl_label = "Get Mesh"
|
|
|
|
def init(self, context):
|
|
self.add_input("SLICESOCK_Object")
|
|
self.add_output("SLICESOCK_Mesh")
|
|
|
|
def evaluate_output(self, socket_name: str, **_kwargs) -> SlicingObject | None:
|
|
if socket_name != "Mesh": return None
|
|
|
|
obj: Any = resolve_input(self.inputs.get("Object"))
|
|
|
|
so = SlicingObject(obj, obj.name)
|
|
|
|
return so
|
|
|
|
@register_class
|
|
class SLICENODE_GetPaths(SliceNode):
|
|
bl_label = "Get Paths"
|
|
|
|
def init(self, context):
|
|
self.add_input("SLICESOCK_Object")
|
|
self.add_output("SLICESOCK_Paths")
|
|
|
|
def evaluate_output(self, socket_name: str, **_kwargs) -> list[np.ndarray]:
|
|
obj: Any = resolve_input(self.inputs.get("Object"))
|
|
|
|
so: list[NDArray[float64]] = capture_mesh_edges(obj)
|
|
|
|
return so
|
|
|
|
@register_class
|
|
class SLICENODE_SliceShells(SliceNode):
|
|
bl_label = "Slice Shells"
|
|
|
|
def init(self, context):
|
|
self.add_input("SLICESOCK_Mesh")
|
|
self.add_input("SLICESOCK_Float", "Layer Height").value = 0.2
|
|
self.add_output("SLICESOCK_Slices")
|
|
|
|
def evaluate_output(self, socket_name: str, **_kwargs) -> list[np.ndarray]:
|
|
if socket_name != "Slices": return []
|
|
|
|
slicingmesh: SlicingObject | None = resolve_input(self.inputs.get("Mesh"))
|
|
layer_height: float = resolve_input(self.inputs.get("Layer Height"))
|
|
|
|
if not slicingmesh: return []
|
|
|
|
min = slicingmesh.mesh[:,0:3,2:3].min()
|
|
min = floor(min / layer_height) * layer_height
|
|
max = slicingmesh.mesh[:,0:3,2:3].max()
|
|
|
|
z_values = np.arange(min, max + layer_height, layer_height)
|
|
|
|
slices_group: list[list[np.ndarray]] | None = slice_at_range([slicingmesh.mesh], z_values)
|
|
assert slices_group
|
|
slices = slices_group[0]
|
|
|
|
return slices
|
|
|
|
|
|
@register_class
|
|
class SLICENODE_BufferShells(SliceNode):
|
|
bl_label = "Buffer Shells"
|
|
|
|
shape_option: bpy.props.EnumProperty(
|
|
name="Miter",
|
|
items=[
|
|
('round', "Round", ""),
|
|
('square', "Square", ""),
|
|
('flat', "Flat", "")
|
|
]
|
|
) # pyright: ignore[reportInvalidTypeForm]
|
|
|
|
def init(self, context):
|
|
self.add_input("SLICESOCK_Slices")
|
|
self.add_input("SLICESOCK_Float", "Distance").value = 0
|
|
self.add_output("SLICESOCK_Slices")
|
|
|
|
def draw_buttons(self, context, layout):
|
|
assert layout
|
|
layout.prop(self, "shape_option")
|
|
|
|
def evaluate_output(self, socket_name: str, **_kwargs) -> list[np.ndarray]:
|
|
if socket_name != "Slices": return []
|
|
|
|
slices: list[np.ndarray] = resolve_input(self.inputs.get("Slices"))
|
|
distance: float = resolve_input(self.inputs.get("Distance"))
|
|
|
|
buffered_slices = slice.buffer_slice(slices, distance, miter=self.shape_option)
|
|
|
|
return buffered_slices
|
|
|
|
@register_class
|
|
class SLICENODE_SlicesToPaths(SliceNode):
|
|
bl_label = "Slices to Paths"
|
|
|
|
def init(self, context):
|
|
self.add_input("SLICESOCK_Slices")
|
|
self.add_output("SLICESOCK_Paths")
|
|
|
|
def evaluate_output(self, socket_name: str, **_kwargs) -> list[np.ndarray]:
|
|
slices: list[np.ndarray] = resolve_input(self.inputs.get("Slices"))
|
|
return slices
|
|
|
|
@register_class
|
|
class SLICENODE_PathsToGcode(SliceNode):
|
|
bl_label = "Paths to Gcode"
|
|
|
|
def init(self, context):
|
|
self.add_input("SLICESOCK_Paths")
|
|
self.add_output("SLICESOCK_Gcode")
|
|
|
|
def evaluate_output(self, socket_name: str, **_kwargs) -> str:
|
|
paths: list[np.ndarray] = resolve_input(self.inputs.get("Paths"))
|
|
pather: Pather = Pather(paths)
|
|
gcode: str = pather.to_gcode()
|
|
return gcode
|
|
|
|
@register_class
|
|
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="")
|
|
|
|
def evaluate_output(self, socket_name: str, **_kwargs) -> str:
|
|
return self.gcode
|
|
|
|
### Joiners
|
|
|
|
class SliceNodeJoiner(SliceNode):
|
|
socket_type: str = ""
|
|
output_socket_type: str = ""
|
|
|
|
def init(self, context):
|
|
self.add_output(self.output_socket_type or self.socket_type)
|
|
|
|
def update(self) -> None:
|
|
def is_active(input) -> bool:
|
|
return input.is_linked or getattr(input, 'value', False)
|
|
|
|
if len(self.inputs) == 0:
|
|
self.add_input(self.socket_type)
|
|
return
|
|
|
|
if is_active(self.inputs[-1]):
|
|
self.add_input(self.socket_type)
|
|
|
|
while True:
|
|
if len(self.inputs) < 2: break
|
|
if not is_active(self.inputs[-1]) and not is_active(self.inputs[-2]):
|
|
self.inputs.remove(self.inputs[-2])
|
|
continue
|
|
break
|
|
|
|
@register_class
|
|
class SLICENODE_JoinGcode(SliceNodeJoiner):
|
|
bl_label = "Join Gcode"
|
|
socket_type = "SLICESOCK_Gcode"
|
|
|
|
def evaluate_output(self, socket_name: str, **_kwargs) -> str:
|
|
gcode_input: list[str] = []
|
|
for input in self.inputs:
|
|
gcode_input += [resolve_input(input)] or [""]
|
|
return "\n".join(gcode_input)
|
|
|
|
|
|
@register_class
|
|
class SLICENODE_JoinSlices(SliceNodeJoiner):
|
|
bl_label = "Join Slices"
|
|
socket_type = "SLICESOCK_Slices"
|
|
|
|
operation: bpy.props.EnumProperty(
|
|
name="Operation",
|
|
items=[
|
|
('UNION', "Union", "Union of the slices"),
|
|
('INTERSECTION', "Intersection", "Intersection of the slices"),
|
|
('DIFFERENCE', "Subtract", "Clipping of the first slice"),
|
|
('XOR', "Exclusive or", "Exclusive or of the slices"),
|
|
],
|
|
default='UNION'
|
|
) # pyright: ignore[reportInvalidTypeForm]
|
|
|
|
def draw_buttons(self, context, layout):
|
|
assert layout
|
|
layout.prop(self, "operation", text="")
|
|
|
|
def evaluate_output(self, socket_name: str, **_kwargs) -> list[np.ndarray]:
|
|
accumulated: list[np.ndarray] = []
|
|
current: list[np.ndarray] = []
|
|
for input in self.inputs:
|
|
current =resolve_input(input)
|
|
|
|
if not current:
|
|
continue
|
|
|
|
if not accumulated:
|
|
accumulated = current
|
|
current = []
|
|
continue
|
|
|
|
accumulated = slice.boolean_slice(accumulated, current, self.operation)
|
|
current = []
|
|
|
|
return accumulated
|
|
|
|
@register_class
|
|
class SLICENODE_JoinMesh(SliceNodeJoiner):
|
|
bl_label = "Join Mesh"
|
|
socket_type = "SLICESOCK_Mesh"
|
|
|
|
def evaluate_output(self, socket_name: str, **_kwargs) -> SlicingObject:
|
|
merged_mesh = SlicingObject()
|
|
|
|
for input in self.inputs:
|
|
mesh =resolve_input(input)
|
|
if mesh:
|
|
merged_mesh.mesh = np.concatenate([merged_mesh.mesh, mesh.mesh], axis=0)
|
|
|
|
return merged_mesh
|
|
|
|
@register_class
|
|
class SLICENODE_JoinPaths(SliceNodeJoiner):
|
|
bl_label = "Join Paths"
|
|
socket_type = "SLICESOCK_Paths"
|
|
|
|
def evaluate_output(self, socket_name: str, **_kwargs) -> list[np.ndarray]:
|
|
merged_paths = []
|
|
|
|
for input in self.inputs:
|
|
paths = resolve_input(input)
|
|
if paths:
|
|
merged_paths.extend(paths)
|
|
|
|
return merged_paths
|
|
|
|
### I/O
|
|
|
|
@register_class
|
|
class SLICENODE_LoadGcode(SliceNode):
|
|
bl_label = "Load Gcode File"
|
|
|
|
def init(self, context):
|
|
self.add_input("SLICESOCK_String", "Filepath")
|
|
self.add_output("SLICESOCK_Gcode")
|
|
|
|
def evaluate_output(self, socket_name: str, **_kwargs) -> str:
|
|
path = resolve_input(self.inputs.get("Filepath"))
|
|
if not path: return ""
|
|
abs_path = bpy.path.abspath(path)
|
|
if os.path.exists(abs_path):
|
|
with open(abs_path, 'r', encoding='utf-8') as f:
|
|
return f.read()
|
|
return ""
|
|
|
|
class ExecSliceNode(SliceNode):
|
|
button_label: str = 'Execute'
|
|
|
|
def draw_buttons(self, context, layout):
|
|
assert layout
|
|
layout.operator("slicenode.execute", text=self.button_label).node_name = self.name
|
|
|
|
@register_class
|
|
class SLICENODE_GcodeToFile(ExecSliceNode):
|
|
bl_label = "Gcode To File"
|
|
|
|
button_label = "Generate File"
|
|
|
|
def init(self, context):
|
|
self.add_input("SLICESOCK_Gcode")
|
|
self.add_input("SLICESOCK_String", "Filename")
|
|
|
|
def evaluate(self):
|
|
gcode: str = resolve_input(self.inputs.get("Gcode"))
|
|
filename: str = resolve_input(self.inputs.get("Filename"))
|
|
|
|
filepath = bpy.path.abspath(filename)
|
|
os.makedirs(os.path.dirname(filepath) or ".", exist_ok=True)
|
|
|
|
with open(filepath, "w", encoding="utf-8", newline="\n") as f:
|
|
f.write(gcode)
|
|
|
|
return 'ok'
|
|
|
|
@register_class
|
|
class SLICENODE_GcodeToMesh(ExecSliceNode):
|
|
bl_label = "Gcode to Mesh"
|
|
|
|
button_label = "Update Debug Mesh"
|
|
|
|
def init(self, context):
|
|
self.add_input("SLICESOCK_Gcode")
|
|
|
|
def evaluate(self):
|
|
gcode: str = resolve_input(self.inputs.get("Gcode"))
|
|
if not gcode:
|
|
return 'empty'
|
|
|
|
segments = []
|
|
last_x, last_y, last_z = 0.0, 0.0, 0.0
|
|
last_e = 0.0
|
|
|
|
for line in gcode.splitlines():
|
|
line = line.split(';')[0].strip()
|
|
if not line: continue
|
|
|
|
parts = line.split()
|
|
cmd = parts[0].upper()
|
|
|
|
params = {}
|
|
for p in parts[1:]:
|
|
if len(p) < 2: continue
|
|
try:
|
|
# Scale coordinates by 0.001 (mm to Blender units)
|
|
val = float(p[1:])
|
|
if p[0].upper() in 'XYZ':
|
|
val *= 0.001
|
|
params[p[0].upper()] = val
|
|
except ValueError: continue
|
|
|
|
new_x = params.get('X', last_x)
|
|
new_y = params.get('Y', last_y)
|
|
new_z = params.get('Z', last_z)
|
|
new_e = params.get('E', last_e)
|
|
|
|
if cmd == 'G1':
|
|
# Check if it's an extrusion move
|
|
# In absolute mode (typical), E must increase.
|
|
# In relative mode, E must be > 0.
|
|
# We'll assume absolute mode as per Pather.to_gcode() M82
|
|
is_extruding = new_e > last_e
|
|
|
|
if is_extruding:
|
|
segments.append(((last_x, last_y, last_z), (new_x, new_y, new_z)))
|
|
|
|
last_x, last_y, last_z = new_x, new_y, new_z
|
|
last_e = new_e
|
|
|
|
if not segments:
|
|
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
|
|
mesh.clear_geometry()
|
|
else:
|
|
mesh = bpy.data.meshes.new(mesh_name)
|
|
obj = bpy.data.objects.new(mesh_name, mesh)
|
|
assert bpy.context.collection
|
|
bpy.context.collection.objects.link(obj)
|
|
|
|
vertices = []
|
|
edges = []
|
|
for i, (p1, p2) in enumerate(segments):
|
|
vertices.extend([p1, p2])
|
|
edges.append((2*i, 2*i + 1))
|
|
|
|
mesh.from_pydata(vertices, edges, [])
|
|
mesh.update()
|
|
|
|
return 'ok'
|
|
|
|
@register_class
|
|
class SLICENODE_Test(ExecSliceNode):
|
|
bl_label = "Test"
|
|
|
|
button_label = "Test"
|
|
|
|
def init(self, context):
|
|
self.add_input("SLICESOCK_Gcode")
|
|
|
|
def evaluate(self):
|
|
_ = resolve_input(self.inputs.get("Gcode"))
|
|
return 'ok'
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Operator
|
|
# -----------------------------------------------------------------------------
|
|
|
|
@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]
|
|
_ = node.evaluate()
|
|
|
|
self.report({"INFO"}, f"Execution Successful")
|
|
return {"FINISHED"}
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Menu
|
|
# -----------------------------------------------------------------------------
|
|
|
|
@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="Integer Math").type = "SLICENODE_IntMath"
|
|
layout.operator("node.add_node", text="Object").type = "SLICENODE_InputObject"
|
|
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="Slices to Paths").type = "SLICENODE_SlicesToPaths"
|
|
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="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 Slices").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 Mesh").type = "SLICENODE_GcodeToMesh"
|
|
|
|
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")
|
|
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Registration
|
|
# -----------------------------------------------------------------------------
|
|
|
|
### Load collected modules
|
|
modules = registry.get()
|
|
|
|
def register():
|
|
registry.blender_register_classes()
|
|
bpy.types.NODE_MT_add.append(draw_add_menu)
|
|
|
|
print('Nodeslicer Loaded')
|
|
|
|
def unregister():
|
|
bpy.types.NODE_MT_add.remove(draw_add_menu)
|
|
registry.blender_unregister_classes()
|
|
|
|
print('Nodeslicer Unloaded')
|
|
|
|
if __name__ == "__main__":
|
|
register() |