object properties
This commit is contained in:
parent
a672043385
commit
fd89179634
4 changed files with 112 additions and 30 deletions
|
|
@ -15,7 +15,7 @@ from .bpy_ns_types import NfStringProperty, NfEnumProperty, NfBoolProperty
|
|||
from ...registry import register_class
|
||||
from .. import slice
|
||||
from ..slice import Pather, slice_at_range, SlicePaths
|
||||
from ...infra.blender_mesh_capture import SlicingObject, capture_mesh_edges, SlicingGroup
|
||||
from ...infra.blender_mesh_capture import SlicingCollection, SlicingObject, capture_mesh_edges, SlicingGroup
|
||||
from ...infra.blender_slice_nodes_generic import SliceNode, resolve_input
|
||||
from .bpy_ns_types import notify_tree_changed
|
||||
from ...infra._3mf_writer._3mf import prepare_3mf
|
||||
|
|
@ -376,18 +376,45 @@ class SLICENODE_IntMath(MathNode):
|
|||
class SLICENODE_GetMesh(SliceNode):
|
||||
bl_label = "Get Mesh"
|
||||
|
||||
part_type: NfEnumProperty( # pyright: ignore[reportInvalidTypeForm]
|
||||
name='',
|
||||
items=[
|
||||
('ModelPart', 'Model Part', ''),
|
||||
('NegativeVolume', 'Negative Volume', ''),
|
||||
('ParameterModifier', 'Parameter Modifier', ''),
|
||||
('SupportBlocker', 'Support Blocker', ''),
|
||||
('SupportEnforcer', 'Support Enforcer', '')
|
||||
],
|
||||
default='ModelPart'
|
||||
)
|
||||
|
||||
def init(self, context):
|
||||
self.add_input("SLICESOCK_Object")
|
||||
self.add_input("SLICESOCK_Overrides")
|
||||
self.add_input("SLICESOCK_Int", "Extruder").value = 1
|
||||
self.add_output("SLICESOCK_Mesh")
|
||||
|
||||
def evaluate_output(self, socket_name: str, **_kwargs) -> SlicingObject | None:
|
||||
def draw_buttons(self, context, layout):
|
||||
assert layout
|
||||
layout.prop(self, "part_type")
|
||||
|
||||
def evaluate_output(self, socket_name: str, **_kwargs) -> SlicingCollection | None:
|
||||
if socket_name != "Mesh": return None
|
||||
|
||||
obj: Any = resolve_input(self.inputs.get("Object"))
|
||||
obj: Any = self.resolve("Object")
|
||||
modifiers: dict[str, str] = self.resolve("Overrides")
|
||||
extruder = self.resolve("Extruder")
|
||||
part_type = self.part_type
|
||||
|
||||
so = SlicingObject(obj, obj.name)
|
||||
so: SlicingObject = SlicingObject(obj, obj.name)
|
||||
so.assign_modifiers(modifiers)
|
||||
so.object_type = part_type
|
||||
so.extruder = extruder
|
||||
|
||||
return so
|
||||
sc: SlicingCollection = SlicingCollection([], obj.name)
|
||||
sc.objects = [so]
|
||||
|
||||
return sc
|
||||
|
||||
@register_class
|
||||
class SLICENODE_GetPaths(SliceNode):
|
||||
|
|
@ -416,18 +443,24 @@ class SLICENODE_SliceShells(SliceNode):
|
|||
def evaluate_output(self, socket_name: str, **_kwargs) -> SlicePaths:
|
||||
if socket_name != "Paths": return SlicePaths()
|
||||
|
||||
slicingmesh: SlicingObject | None = resolve_input(self.inputs.get("Mesh"))
|
||||
slicing_collection: SlicingCollection | None = resolve_input(self.inputs.get("Mesh"))
|
||||
layer_height: float = resolve_input(self.inputs.get("Layer Height"))
|
||||
|
||||
if not slicingmesh: return SlicePaths()
|
||||
if not slicing_collection: return SlicePaths()
|
||||
|
||||
min = slicingmesh.mesh[:,0:3,2:3].min()
|
||||
merged_object = SlicingObject()
|
||||
coll: SlicingCollection = self.resolve("Mesh")
|
||||
if coll:
|
||||
for obj in coll.objects:
|
||||
merged_object.mesh = np.concatenate([merged_object.mesh, obj.mesh], axis=0)
|
||||
|
||||
min = merged_object.mesh[:,0:3,2:3].min()
|
||||
min = floor(min / layer_height) * layer_height
|
||||
max = slicingmesh.mesh[:,0:3,2:3].max()
|
||||
max = merged_object.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)
|
||||
slices_group: list[list[np.ndarray]] | None = slice_at_range([merged_object.mesh], z_values)
|
||||
assert slices_group
|
||||
slices = slices_group[0]
|
||||
|
||||
|
|
@ -809,18 +842,34 @@ class SLICENODE_JoinSlices(SliceNodeJoiner):
|
|||
|
||||
@register_class
|
||||
class SLICENODE_JoinMesh(SliceNodeJoiner):
|
||||
bl_label = "Join Mesh"
|
||||
bl_label = "Join Mesh Parts"
|
||||
socket_type = "SLICESOCK_Mesh"
|
||||
|
||||
def evaluate_output(self, socket_name: str, **_kwargs) -> SlicingObject:
|
||||
merged_mesh = SlicingObject()
|
||||
def evaluate_output(self, socket_name: str, **_kwargs) -> SlicingCollection:
|
||||
coll: SlicingCollection
|
||||
merged_coll = resolve_input(self.inputs[0])
|
||||
|
||||
for input in self.inputs[1:]:
|
||||
coll = resolve_input(input)
|
||||
if coll:
|
||||
merged_coll.objects += coll.objects
|
||||
|
||||
return merged_coll
|
||||
|
||||
@register_class
|
||||
class SLICENODE_JoinMeshGroups(SliceNodeJoiner):
|
||||
bl_label = "Join Mesh Groups"
|
||||
socket_type = "SLICESOCK_Mesh"
|
||||
|
||||
def evaluate_output(self, socket_name: str, **_kwargs) -> SlicingGroup:
|
||||
group: SlicingGroup = SlicingGroup([])
|
||||
|
||||
for input in self.inputs:
|
||||
mesh =resolve_input(input)
|
||||
if mesh:
|
||||
merged_mesh.mesh = np.concatenate([merged_mesh.mesh, mesh.mesh], axis=0)
|
||||
coll = resolve_input(input)
|
||||
if coll:
|
||||
group.collections[coll.objects[0].name] = coll
|
||||
|
||||
return merged_mesh
|
||||
return group
|
||||
|
||||
@register_class
|
||||
class SLICENODE_JoinPaths(SliceNodeJoiner):
|
||||
|
|
@ -1120,7 +1169,7 @@ class SLICENODE_PrusaslicerSlice(ExecSliceNode):
|
|||
|
||||
def init(self, context):
|
||||
self._init_exec()
|
||||
self.add_input("SLICESOCK_Object", "Object")
|
||||
self.add_input("SLICESOCK_Mesh", "Mesh")
|
||||
self.add_input("SLICESOCK_PrusaslicerConf", "Config")
|
||||
self.add_input("SLICESOCK_Overrides", "Overrides")
|
||||
self.add_output("SLICESOCK_GcodeRo")
|
||||
|
|
@ -1135,12 +1184,14 @@ class SLICENODE_PrusaslicerSlice(ExecSliceNode):
|
|||
return super().evaluate_output(socket_name, **kwargs)
|
||||
|
||||
def evaluate(self, operator=None):
|
||||
obj = resolve_input(self.inputs.get("Object"))
|
||||
self.gcode_result = ""
|
||||
mesh_data: SlicingCollection | SlicingGroup = resolve_input(self.inputs.get("Mesh"))
|
||||
|
||||
conf_data = resolve_input(self.inputs.get("Config"))
|
||||
overrides = resolve_input(self.inputs.get("Overrides"))
|
||||
|
||||
if not obj:
|
||||
if operator: operator.report({'ERROR'}, "No Object connected")
|
||||
if not mesh_data:
|
||||
if operator: operator.report({'ERROR'}, "No Mesh data connected")
|
||||
return 'no object'
|
||||
if not conf_data:
|
||||
if operator: operator.report({'ERROR'}, "No Config connected")
|
||||
|
|
@ -1153,7 +1204,11 @@ class SLICENODE_PrusaslicerSlice(ExecSliceNode):
|
|||
cache = conf_data['cache']
|
||||
|
||||
# Generate the SlicingGroup
|
||||
slicing_group = SlicingGroup([obj])
|
||||
if isinstance(mesh_data, SlicingGroup):
|
||||
slicing_group = mesh_data
|
||||
else:
|
||||
slicing_group = SlicingGroup([])
|
||||
slicing_group.collections = {mesh_data.objects[0].name: mesh_data}
|
||||
|
||||
# Offset by the bed center
|
||||
def parse_bed_center(bed_shape_str: str) -> np.ndarray:
|
||||
|
|
@ -1189,7 +1244,7 @@ class SLICENODE_PrusaslicerSlice(ExecSliceNode):
|
|||
|
||||
# Prepare 3mf path
|
||||
temp_dir = Path(tempfile.gettempdir())
|
||||
output_path = temp_dir / f"{obj.name}_NodeSlicer.3mf"
|
||||
output_path = temp_dir / f"tmp.3mf"
|
||||
|
||||
try:
|
||||
prepare_3mf(output_path, slicing_group, conf_writer, z_gcodes=[])
|
||||
|
|
@ -1215,6 +1270,7 @@ class SLICENODE_PrusaslicerSlice(ExecSliceNode):
|
|||
# Final check if command exists
|
||||
if not shutil.which(cmd_parts[0]):
|
||||
if operator: operator.report({'ERROR'}, f"Command or executable not found: {cmd_parts[0]}")
|
||||
self.gcode_result = ""
|
||||
return 'error'
|
||||
|
||||
gcode_path = output_path.with_suffix(".gcode")
|
||||
|
|
@ -1226,6 +1282,7 @@ class SLICENODE_PrusaslicerSlice(ExecSliceNode):
|
|||
process = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if process.returncode != 0:
|
||||
if operator: operator.report({'ERROR'}, f"PrusaSlicer failed: {process.stderr}")
|
||||
self.gcode_result = ""
|
||||
return 'error'
|
||||
|
||||
# Read G-code
|
||||
|
|
@ -1233,13 +1290,17 @@ class SLICENODE_PrusaslicerSlice(ExecSliceNode):
|
|||
with open(gcode_path, 'r', encoding='utf-8') as f:
|
||||
self.gcode_result = f.read()
|
||||
|
||||
output_path.unlink()
|
||||
|
||||
if operator:
|
||||
operator.report({'INFO'}, f"Slicing complete. G-code generated.")
|
||||
else:
|
||||
if operator: operator.report({'ERROR'}, "G-code file was not generated by PrusaSlicer")
|
||||
self.gcode_result = ""
|
||||
return 'error'
|
||||
|
||||
except Exception as e:
|
||||
self.gcode_result = ""
|
||||
if operator: operator.report({'ERROR'}, f"Failed to slice: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
|
@ -1263,6 +1324,10 @@ class SLICENODE_GcodeToMesh(ExecSliceNode):
|
|||
mesh_name: str = self.resolve("Name")
|
||||
|
||||
if not gcode:
|
||||
if mesh_name in bpy.data.objects:
|
||||
obj = bpy.data.objects[mesh_name]
|
||||
if obj.type == 'MESH':
|
||||
obj.data.clear_geometry()
|
||||
return 'empty'
|
||||
|
||||
segments = []
|
||||
|
|
|
|||
|
|
@ -163,7 +163,8 @@ class SLICENODE_MT_Menu(Menu):
|
|||
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 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 Overrides").type = "SLICENODE_JoinOverrides"
|
||||
layout.operator("node.add_node", text="Join Execution").type = "SLICENODE_JoinExecution"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
from __future__ import annotations
|
||||
from bpy.types import FloatAttribute, Object, Mesh
|
||||
from bpy.types import Object, Mesh
|
||||
import bpy
|
||||
import zlib, struct
|
||||
from functools import cached_property
|
||||
|
|
@ -8,7 +8,7 @@ import numpy as np
|
|||
from numpy import dtype, float64
|
||||
from numpy.typing import NDArray
|
||||
|
||||
from typing import Any, cast
|
||||
from typing import Any
|
||||
|
||||
from ..core.geometry import crc32_array
|
||||
from ..infra.blender_bridge import get_all_children
|
||||
|
|
@ -44,7 +44,16 @@ class SlicingObject():
|
|||
self.mesh = np.empty((0, 4, 3))
|
||||
|
||||
def offset(self, offset: NDArray):
|
||||
self.mesh += offset
|
||||
self.mesh[:, :3, :] += offset
|
||||
|
||||
def scale(self, factor: float):
|
||||
self.mesh[:, :3, :] *= factor
|
||||
|
||||
def assign_modifiers(self, modifiers: dict[str, str]):
|
||||
self.modifiers = [
|
||||
{"param_id": k, "param_value": v}
|
||||
for k, v in modifiers.items()
|
||||
]
|
||||
|
||||
@property
|
||||
def checksum(self) -> int:
|
||||
|
|
@ -99,6 +108,9 @@ class SlicingCollection():
|
|||
def offset(self, offset: NDArray):
|
||||
for so in self.objects: so.offset(offset)
|
||||
|
||||
def scale(self, factor: float):
|
||||
for so in self.objects: so.scale(factor)
|
||||
|
||||
@property
|
||||
def checksum(self) -> int:
|
||||
return crc32_array(np.array([o.checksum for o in self.objects]))
|
||||
|
|
@ -253,6 +265,10 @@ class SlicingGroup():
|
|||
for k, so in self.collections.items(): so.offset(offset)
|
||||
self.wipe_tower_xy = self.wipe_tower_xy + offset[0:2]
|
||||
|
||||
def scale(self, factor: float):
|
||||
for k, so in self.collections.items(): so.scale(factor)
|
||||
self.wipe_tower_xy *= factor
|
||||
|
||||
@property
|
||||
def checksum(self):
|
||||
buf = bytearray()
|
||||
|
|
|
|||
|
|
@ -27,11 +27,11 @@ class SliceNode(Node, AutoBlIdName):
|
|||
def resolve(self, fieldname) -> Any:
|
||||
return resolve_input(self.inputs.get(fieldname))
|
||||
|
||||
def add_output(self, bl_idname: str, name: str | None = None):
|
||||
def add_output(self, bl_idname: str, name: str = ""):
|
||||
from ..registry import get_label_by_name
|
||||
return self.outputs.new(bl_idname, name or get_label_by_name(bl_idname))
|
||||
|
||||
def add_input(self, bl_idname: str, name: str | None = None, multi: bool = False):
|
||||
def add_input(self, bl_idname: str, name: str = "", multi: bool = False):
|
||||
from ..registry import get_label_by_name
|
||||
sock = self.inputs.new(bl_idname, name or get_label_by_name(bl_idname))
|
||||
if multi:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue