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 ...registry import register_class
|
||||||
from .. import slice
|
from .. import slice
|
||||||
from ..slice import Pather, slice_at_range, SlicePaths
|
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 ...infra.blender_slice_nodes_generic import SliceNode, resolve_input
|
||||||
from .bpy_ns_types import notify_tree_changed
|
from .bpy_ns_types import notify_tree_changed
|
||||||
from ...infra._3mf_writer._3mf import prepare_3mf
|
from ...infra._3mf_writer._3mf import prepare_3mf
|
||||||
|
|
@ -376,18 +376,45 @@ class SLICENODE_IntMath(MathNode):
|
||||||
class SLICENODE_GetMesh(SliceNode):
|
class SLICENODE_GetMesh(SliceNode):
|
||||||
bl_label = "Get Mesh"
|
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):
|
def init(self, context):
|
||||||
self.add_input("SLICESOCK_Object")
|
self.add_input("SLICESOCK_Object")
|
||||||
|
self.add_input("SLICESOCK_Overrides")
|
||||||
|
self.add_input("SLICESOCK_Int", "Extruder").value = 1
|
||||||
self.add_output("SLICESOCK_Mesh")
|
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
|
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
|
@register_class
|
||||||
class SLICENODE_GetPaths(SliceNode):
|
class SLICENODE_GetPaths(SliceNode):
|
||||||
|
|
@ -416,18 +443,24 @@ class SLICENODE_SliceShells(SliceNode):
|
||||||
def evaluate_output(self, socket_name: str, **_kwargs) -> SlicePaths:
|
def evaluate_output(self, socket_name: str, **_kwargs) -> SlicePaths:
|
||||||
if socket_name != "Paths": return 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"))
|
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
|
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)
|
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
|
assert slices_group
|
||||||
slices = slices_group[0]
|
slices = slices_group[0]
|
||||||
|
|
||||||
|
|
@ -809,18 +842,34 @@ class SLICENODE_JoinSlices(SliceNodeJoiner):
|
||||||
|
|
||||||
@register_class
|
@register_class
|
||||||
class SLICENODE_JoinMesh(SliceNodeJoiner):
|
class SLICENODE_JoinMesh(SliceNodeJoiner):
|
||||||
bl_label = "Join Mesh"
|
bl_label = "Join Mesh Parts"
|
||||||
socket_type = "SLICESOCK_Mesh"
|
socket_type = "SLICESOCK_Mesh"
|
||||||
|
|
||||||
def evaluate_output(self, socket_name: str, **_kwargs) -> SlicingObject:
|
def evaluate_output(self, socket_name: str, **_kwargs) -> SlicingCollection:
|
||||||
merged_mesh = SlicingObject()
|
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:
|
for input in self.inputs:
|
||||||
mesh =resolve_input(input)
|
coll = resolve_input(input)
|
||||||
if mesh:
|
if coll:
|
||||||
merged_mesh.mesh = np.concatenate([merged_mesh.mesh, mesh.mesh], axis=0)
|
group.collections[coll.objects[0].name] = coll
|
||||||
|
|
||||||
return merged_mesh
|
return group
|
||||||
|
|
||||||
@register_class
|
@register_class
|
||||||
class SLICENODE_JoinPaths(SliceNodeJoiner):
|
class SLICENODE_JoinPaths(SliceNodeJoiner):
|
||||||
|
|
@ -1120,7 +1169,7 @@ class SLICENODE_PrusaslicerSlice(ExecSliceNode):
|
||||||
|
|
||||||
def init(self, context):
|
def init(self, context):
|
||||||
self._init_exec()
|
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_PrusaslicerConf", "Config")
|
||||||
self.add_input("SLICESOCK_Overrides", "Overrides")
|
self.add_input("SLICESOCK_Overrides", "Overrides")
|
||||||
self.add_output("SLICESOCK_GcodeRo")
|
self.add_output("SLICESOCK_GcodeRo")
|
||||||
|
|
@ -1135,12 +1184,14 @@ class SLICENODE_PrusaslicerSlice(ExecSliceNode):
|
||||||
return super().evaluate_output(socket_name, **kwargs)
|
return super().evaluate_output(socket_name, **kwargs)
|
||||||
|
|
||||||
def evaluate(self, operator=None):
|
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"))
|
conf_data = resolve_input(self.inputs.get("Config"))
|
||||||
overrides = resolve_input(self.inputs.get("Overrides"))
|
overrides = resolve_input(self.inputs.get("Overrides"))
|
||||||
|
|
||||||
if not obj:
|
if not mesh_data:
|
||||||
if operator: operator.report({'ERROR'}, "No Object connected")
|
if operator: operator.report({'ERROR'}, "No Mesh data connected")
|
||||||
return 'no object'
|
return 'no object'
|
||||||
if not conf_data:
|
if not conf_data:
|
||||||
if operator: operator.report({'ERROR'}, "No Config connected")
|
if operator: operator.report({'ERROR'}, "No Config connected")
|
||||||
|
|
@ -1153,8 +1204,12 @@ class SLICENODE_PrusaslicerSlice(ExecSliceNode):
|
||||||
cache = conf_data['cache']
|
cache = conf_data['cache']
|
||||||
|
|
||||||
# Generate the SlicingGroup
|
# 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
|
# Offset by the bed center
|
||||||
def parse_bed_center(bed_shape_str: str) -> np.ndarray:
|
def parse_bed_center(bed_shape_str: str) -> np.ndarray:
|
||||||
try:
|
try:
|
||||||
|
|
@ -1189,7 +1244,7 @@ class SLICENODE_PrusaslicerSlice(ExecSliceNode):
|
||||||
|
|
||||||
# Prepare 3mf path
|
# Prepare 3mf path
|
||||||
temp_dir = Path(tempfile.gettempdir())
|
temp_dir = Path(tempfile.gettempdir())
|
||||||
output_path = temp_dir / f"{obj.name}_NodeSlicer.3mf"
|
output_path = temp_dir / f"tmp.3mf"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
prepare_3mf(output_path, slicing_group, conf_writer, z_gcodes=[])
|
prepare_3mf(output_path, slicing_group, conf_writer, z_gcodes=[])
|
||||||
|
|
@ -1215,6 +1270,7 @@ class SLICENODE_PrusaslicerSlice(ExecSliceNode):
|
||||||
# Final check if command exists
|
# Final check if command exists
|
||||||
if not shutil.which(cmd_parts[0]):
|
if not shutil.which(cmd_parts[0]):
|
||||||
if operator: operator.report({'ERROR'}, f"Command or executable not found: {cmd_parts[0]}")
|
if operator: operator.report({'ERROR'}, f"Command or executable not found: {cmd_parts[0]}")
|
||||||
|
self.gcode_result = ""
|
||||||
return 'error'
|
return 'error'
|
||||||
|
|
||||||
gcode_path = output_path.with_suffix(".gcode")
|
gcode_path = output_path.with_suffix(".gcode")
|
||||||
|
|
@ -1226,20 +1282,25 @@ class SLICENODE_PrusaslicerSlice(ExecSliceNode):
|
||||||
process = subprocess.run(cmd, capture_output=True, text=True)
|
process = subprocess.run(cmd, capture_output=True, text=True)
|
||||||
if process.returncode != 0:
|
if process.returncode != 0:
|
||||||
if operator: operator.report({'ERROR'}, f"PrusaSlicer failed: {process.stderr}")
|
if operator: operator.report({'ERROR'}, f"PrusaSlicer failed: {process.stderr}")
|
||||||
|
self.gcode_result = ""
|
||||||
return 'error'
|
return 'error'
|
||||||
|
|
||||||
# Read G-code
|
# Read G-code
|
||||||
if gcode_path.exists():
|
if gcode_path.exists():
|
||||||
with open(gcode_path, 'r', encoding='utf-8') as f:
|
with open(gcode_path, 'r', encoding='utf-8') as f:
|
||||||
self.gcode_result = f.read()
|
self.gcode_result = f.read()
|
||||||
|
|
||||||
|
output_path.unlink()
|
||||||
|
|
||||||
if operator:
|
if operator:
|
||||||
operator.report({'INFO'}, f"Slicing complete. G-code generated.")
|
operator.report({'INFO'}, f"Slicing complete. G-code generated.")
|
||||||
else:
|
else:
|
||||||
if operator: operator.report({'ERROR'}, "G-code file was not generated by PrusaSlicer")
|
if operator: operator.report({'ERROR'}, "G-code file was not generated by PrusaSlicer")
|
||||||
|
self.gcode_result = ""
|
||||||
return 'error'
|
return 'error'
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
self.gcode_result = ""
|
||||||
if operator: operator.report({'ERROR'}, f"Failed to slice: {str(e)}")
|
if operator: operator.report({'ERROR'}, f"Failed to slice: {str(e)}")
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
@ -1263,6 +1324,10 @@ class SLICENODE_GcodeToMesh(ExecSliceNode):
|
||||||
mesh_name: str = self.resolve("Name")
|
mesh_name: str = self.resolve("Name")
|
||||||
|
|
||||||
if not gcode:
|
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'
|
return 'empty'
|
||||||
|
|
||||||
segments = []
|
segments = []
|
||||||
|
|
|
||||||
|
|
@ -163,7 +163,8 @@ class SLICENODE_MT_Menu(Menu):
|
||||||
layout.operator("node.add_node", text="Load Gcode").type = "SLICENODE_LoadGcode"
|
layout.operator("node.add_node", text="Load Gcode").type = "SLICENODE_LoadGcode"
|
||||||
layout.separator()
|
layout.separator()
|
||||||
layout.operator("node.add_node", text="Join Gcode").type = "SLICENODE_JoinGcode"
|
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 Paths").type = "SLICENODE_JoinPaths"
|
||||||
layout.operator("node.add_node", text="Join Overrides").type = "SLICENODE_JoinOverrides"
|
layout.operator("node.add_node", text="Join Overrides").type = "SLICENODE_JoinOverrides"
|
||||||
layout.operator("node.add_node", text="Join Execution").type = "SLICENODE_JoinExecution"
|
layout.operator("node.add_node", text="Join Execution").type = "SLICENODE_JoinExecution"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from bpy.types import FloatAttribute, Object, Mesh
|
from bpy.types import Object, Mesh
|
||||||
import bpy
|
import bpy
|
||||||
import zlib, struct
|
import zlib, struct
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
|
|
@ -8,7 +8,7 @@ import numpy as np
|
||||||
from numpy import dtype, float64
|
from numpy import dtype, float64
|
||||||
from numpy.typing import NDArray
|
from numpy.typing import NDArray
|
||||||
|
|
||||||
from typing import Any, cast
|
from typing import Any
|
||||||
|
|
||||||
from ..core.geometry import crc32_array
|
from ..core.geometry import crc32_array
|
||||||
from ..infra.blender_bridge import get_all_children
|
from ..infra.blender_bridge import get_all_children
|
||||||
|
|
@ -44,7 +44,16 @@ class SlicingObject():
|
||||||
self.mesh = np.empty((0, 4, 3))
|
self.mesh = np.empty((0, 4, 3))
|
||||||
|
|
||||||
def offset(self, offset: NDArray):
|
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
|
@property
|
||||||
def checksum(self) -> int:
|
def checksum(self) -> int:
|
||||||
|
|
@ -99,6 +108,9 @@ class SlicingCollection():
|
||||||
def offset(self, offset: NDArray):
|
def offset(self, offset: NDArray):
|
||||||
for so in self.objects: so.offset(offset)
|
for so in self.objects: so.offset(offset)
|
||||||
|
|
||||||
|
def scale(self, factor: float):
|
||||||
|
for so in self.objects: so.scale(factor)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def checksum(self) -> int:
|
def checksum(self) -> int:
|
||||||
return crc32_array(np.array([o.checksum for o in self.objects]))
|
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)
|
for k, so in self.collections.items(): so.offset(offset)
|
||||||
self.wipe_tower_xy = self.wipe_tower_xy + offset[0:2]
|
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
|
@property
|
||||||
def checksum(self):
|
def checksum(self):
|
||||||
buf = bytearray()
|
buf = bytearray()
|
||||||
|
|
|
||||||
|
|
@ -27,11 +27,11 @@ class SliceNode(Node, AutoBlIdName):
|
||||||
def resolve(self, fieldname) -> Any:
|
def resolve(self, fieldname) -> Any:
|
||||||
return resolve_input(self.inputs.get(fieldname))
|
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
|
from ..registry import get_label_by_name
|
||||||
return self.outputs.new(bl_idname, name or get_label_by_name(bl_idname))
|
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
|
from ..registry import get_label_by_name
|
||||||
sock = self.inputs.new(bl_idname, name or get_label_by_name(bl_idname))
|
sock = self.inputs.new(bl_idname, name or get_label_by_name(bl_idname))
|
||||||
if multi:
|
if multi:
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue