slicing with prusaslicer

This commit is contained in:
Nicolas Predella 2026-05-15 16:59:45 +03:00
parent 3a856358dc
commit e69a92c9a3
2 changed files with 80 additions and 13 deletions

View file

@ -1,6 +1,11 @@
from __future__ import annotations from __future__ import annotations
import os import os
import subprocess
import tempfile
import shlex
import shutil
from math import floor from math import floor
from pathlib import Path
from typing import Any, TypeAlias from typing import Any, TypeAlias
import bpy import bpy
import numpy as np import numpy as np
@ -10,8 +15,9 @@ from bpy.props import StringProperty, FloatProperty, IntProperty, EnumProperty,
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 from ...infra.blender_mesh_capture import 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 ...infra._3mf_writer._3mf import prepare_3mf
class SliceIntrinsics: class SliceIntrinsics:
bed_x: float bed_x: float
@ -1045,19 +1051,27 @@ class SLICENODE_StartPrint(ExecSliceNode):
@register_class @register_class
class SLICENODE_PrusaslicerSlice(ExecSliceNode): class SLICENODE_PrusaslicerSlice(ExecSliceNode):
bl_label = "PrusaSlicer Slice" 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): def init(self, context):
self._init_exec() self._init_exec()
self.add_input("SLICESOCK_Object", "Object") self.add_input("SLICESOCK_Object", "Object")
self.add_input("SLICESOCK_PrusaslicerConf", "Config") 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): 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")) obj = resolve_input(self.inputs.get("Object"))
conf_data = resolve_input(self.inputs.get("Config")) 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]) 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) slicing_group.offset(offset)
# Generate the ConfigWriter with no 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={}) 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()) temp_dir = Path(tempfile.gettempdir())
output_path = temp_dir / f"{obj.name}_NodeSlicer.3mf" output_path = temp_dir / f"{obj.name}_NodeSlicer.3mf"
try: try:
prepare_3mf(output_path, slicing_group, conf_writer, z_gcodes=[]) 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: 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 import traceback
traceback.print_exc() traceback.print_exc()
return 'error' return 'error'

View file

@ -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. - **Header**: Toggles the generation of standard NodeSlicer G-code headers and units setup.
- **Load Gcode**: Reads a G-code file from disk. - **Load Gcode**: Reads a G-code file from disk.
- **Gcode to File**: Writes G-code string to a file on 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 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. - **Gcode to Mesh**: Decodes G-code and creates a Blender mesh ("Debug" object) for visualization.
- **Test**: Triggers evaluation for debugging purposes. - **Test**: Triggers evaluation for debugging purposes.