slicing with prusaslicer
This commit is contained in:
parent
3a856358dc
commit
e69a92c9a3
2 changed files with 80 additions and 13 deletions
|
|
@ -1,6 +1,11 @@
|
|||
from __future__ import annotations
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import shlex
|
||||
import shutil
|
||||
from math import floor
|
||||
from pathlib import Path
|
||||
from typing import Any, TypeAlias
|
||||
import bpy
|
||||
import numpy as np
|
||||
|
|
@ -10,8 +15,9 @@ from bpy.props import StringProperty, FloatProperty, IntProperty, EnumProperty,
|
|||
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
|
||||
from ...infra.blender_mesh_capture import SlicingObject, capture_mesh_edges, SlicingGroup
|
||||
from ...infra.blender_slice_nodes_generic import SliceNode, resolve_input
|
||||
from ...infra._3mf_writer._3mf import prepare_3mf
|
||||
|
||||
class SliceIntrinsics:
|
||||
bed_x: float
|
||||
|
|
@ -1045,19 +1051,27 @@ class SLICENODE_StartPrint(ExecSliceNode):
|
|||
@register_class
|
||||
class SLICENODE_PrusaslicerSlice(ExecSliceNode):
|
||||
bl_label = "PrusaSlicer Slice"
|
||||
button_label = "Generate 3MF"
|
||||
button_label = "Slice with PrusaSlicer"
|
||||
|
||||
prusaslicer_executable: StringProperty(name="PrusaSlicer Path", subtype='FILE_PATH') # pyright: ignore[reportInvalidTypeForm]
|
||||
gcode_result: StringProperty(name="Gcode Result", default="") # pyright: ignore[reportInvalidTypeForm]
|
||||
|
||||
def init(self, context):
|
||||
self._init_exec()
|
||||
self.add_input("SLICESOCK_Object", "Object")
|
||||
self.add_input("SLICESOCK_PrusaslicerConf", "Config")
|
||||
self.add_output("SLICESOCK_Gcode")
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
super().draw_buttons(context, layout)
|
||||
layout.prop(self, "prusaslicer_executable")
|
||||
|
||||
def evaluate_output(self, socket_name: str, **kwargs) -> Any:
|
||||
if socket_name == "Gcode":
|
||||
return self.gcode_result
|
||||
return super().evaluate_output(socket_name, **kwargs)
|
||||
|
||||
def evaluate(self, operator=None):
|
||||
from ...infra.blender_mesh_capture import SlicingGroup
|
||||
from ...infra._3mf_writer._3mf import prepare_3mf
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
obj = resolve_input(self.inputs.get("Object"))
|
||||
conf_data = resolve_input(self.inputs.get("Config"))
|
||||
|
||||
|
|
@ -1096,20 +1110,68 @@ class SLICENODE_PrusaslicerSlice(ExecSliceNode):
|
|||
offset = np.array([bed_center[0], bed_center[1], a_z]) - np.array([slicing_group.center_xy[0], slicing_group.center_xy[1], 0])
|
||||
slicing_group.offset(offset)
|
||||
|
||||
# Generate the ConfigWriter with no overrides
|
||||
conf_writer = cache.generate_conf_writer(printer_id, filament_ids, print_id, overrides={})
|
||||
# Generate the ConfigWriter, forcing binary_gcode to 0 to ensure readable ASCII output
|
||||
conf_writer = cache.generate_conf_writer(
|
||||
printer_id,
|
||||
filament_ids,
|
||||
print_id,
|
||||
overrides={'binary_gcode': {'value': '0'}}
|
||||
)
|
||||
|
||||
# Prepare 3MF path
|
||||
# Prepare 3mf path
|
||||
temp_dir = Path(tempfile.gettempdir())
|
||||
output_path = temp_dir / f"{obj.name}_NodeSlicer.3mf"
|
||||
|
||||
try:
|
||||
prepare_3mf(output_path, slicing_group, conf_writer, z_gcodes=[])
|
||||
if operator:
|
||||
operator.report({'INFO'}, f"3MF generated at {output_path}")
|
||||
|
||||
# Actual slicing
|
||||
exe_str = self.prusaslicer_executable
|
||||
if not exe_str:
|
||||
if operator: operator.report({'ERROR'}, "PrusaSlicer executable path is empty")
|
||||
return 'error'
|
||||
|
||||
# Parse command (handles multi-word commands like flatpak)
|
||||
cmd_parts = shlex.split(exe_str)
|
||||
if not cmd_parts:
|
||||
if operator: operator.report({'ERROR'}, "Invalid PrusaSlicer command")
|
||||
return 'error'
|
||||
|
||||
# Try to resolve the first part as a path if it's not a command in PATH
|
||||
if not shutil.which(cmd_parts[0]):
|
||||
abs_path = bpy.path.abspath(cmd_parts[0])
|
||||
if os.path.exists(abs_path):
|
||||
cmd_parts[0] = abs_path
|
||||
|
||||
# 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]}")
|
||||
return 'error'
|
||||
|
||||
gcode_path = output_path.with_suffix(".gcode")
|
||||
|
||||
# Command: <exe> --export-gcode <3mf_path> --output <gcode_path>
|
||||
cmd = cmd_parts + ["--export-gcode", str(output_path), "--output", str(gcode_path)]
|
||||
|
||||
# Run PrusaSlicer
|
||||
process = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if process.returncode != 0:
|
||||
if operator: operator.report({'ERROR'}, f"PrusaSlicer failed: {process.stderr}")
|
||||
return 'error'
|
||||
|
||||
# Read G-code
|
||||
if gcode_path.exists():
|
||||
with open(gcode_path, 'r', encoding='utf-8') as f:
|
||||
self.gcode_result = f.read()
|
||||
|
||||
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")
|
||||
return 'error'
|
||||
|
||||
except Exception as e:
|
||||
if operator: operator.report({'ERROR'}, f"Failed to generate 3MF: {str(e)}")
|
||||
if operator: operator.report({'ERROR'}, f"Failed to slice: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return 'error'
|
||||
|
|
|
|||
|
|
@ -71,6 +71,11 @@ All nodes are found in the **Add (Shift+A)** menu under the **SliceNodes** categ
|
|||
- **Header**: Toggles the generation of standard NodeSlicer G-code headers and units setup.
|
||||
- **Load Gcode**: Reads a G-code file from disk.
|
||||
- **Gcode to File**: Writes G-code string to a file on disk.
|
||||
- **PrusaSlicer Slice**: Exports the current mesh and configuration to a 3MF file and calls PrusaSlicer to generate G-code.
|
||||
- **Executable**: Path to the PrusaSlicer executable.
|
||||
- **Object**: The Blender object to slice.
|
||||
- **Config**: PrusaSlicer configuration (from the **PrusaSlicer Config** node).
|
||||
- **Gcode**: Output socket providing the generated G-code string.
|
||||
- **Gcode to Text Block**: Creates a new Blender text block containing the G-code and opens it in any active text editors.
|
||||
- **Gcode to Mesh**: Decodes G-code and creates a Blender mesh ("Debug" object) for visualization.
|
||||
- **Test**: Triggers evaluation for debugging purposes.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue