From 51d8aac2c48fd80b79383bc0c5dcf94803a73919 Mon Sep 17 00:00:00 2001 From: Nicolas Predella Date: Thu, 21 May 2026 19:38:35 +0300 Subject: [PATCH] preview --- core/nodeslicer/nodes.py | 68 ++++++++++++++++++++++++++++++++++++++++ core/nodeslicer/tree.py | 1 + doc/NODES.md | 3 ++ 3 files changed, 72 insertions(+) diff --git a/core/nodeslicer/nodes.py b/core/nodeslicer/nodes.py index b467946..40ad2b4 100644 --- a/core/nodeslicer/nodes.py +++ b/core/nodeslicer/nodes.py @@ -1455,6 +1455,74 @@ class SLICENODE_PrusaslicerSlice(SliceNode): return 'ok' +@register_class +class SLICENODE_PrusaslicerPreview(ExecSliceNode): + bl_label = "PrusaSlicer Preview" + button_label = "Open Preview" + + prusaslicer_executable: NfStringProperty( + name="PrusaSlicer Path", + subtype='FILE_PATH', + default='flatpak run com.prusa3d.PrusaSlicer' + ) # pyright: ignore[reportInvalidTypeForm] + + def init(self, context): + self._init_exec() + self.add_input("SLICESOCK_Gcode", "Gcode") + + def draw_buttons(self, context, layout): + super().draw_buttons(context, layout) + layout.prop(self, "prusaslicer_executable") + + def evaluate(self, operator=None): + gcode: str = self.resolve("Gcode") + if not gcode: + if operator: operator.report({'ERROR'}, "No Gcode to preview") + return 'no gcode' + + 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' + + # Write to temp file + # We use a named temporary file that stays around for a bit. + # PrusaSlicer needs the file to exist when it starts. + fd, temp_path = tempfile.mkstemp(suffix='.gcode') + try: + with os.fdopen(fd, 'w', encoding='utf-8') as f: + f.write(gcode) + + cmd = cmd_parts + ["--gcodeviewer", temp_path] + + # Use Popen to not block Blender + subprocess.Popen(cmd) + + if operator: + operator.report({'INFO'}, f"PrusaSlicer Preview launched") + except Exception as e: + if operator: operator.report({'ERROR'}, f"Failed to launch preview: {str(e)}") + return 'error' + + return 'ok' + @register_class class SLICENODE_GcodeToMesh(ExecSliceNode): bl_label = "Gcode Preview" diff --git a/core/nodeslicer/tree.py b/core/nodeslicer/tree.py index 678f461..29515b2 100644 --- a/core/nodeslicer/tree.py +++ b/core/nodeslicer/tree.py @@ -228,6 +228,7 @@ class SLICENODE_MT_ActionIO(Menu): layout.operator("node.add_node", text="Airtable Submit").type = "SLICENODE_AirtableSubmit" layout.separator() layout.operator("node.add_node", text="Gcode Preview").type = "SLICENODE_GcodeToMesh" + layout.operator("node.add_node", text="PrusaSlicer Preview").type = "SLICENODE_PrusaslicerPreview" layout.operator("node.add_node", text="PrusaSlicer Slice").type = "SLICENODE_PrusaslicerSlice" layout.operator("node.add_node", text="Executor").type = "SLICENODE_Executor" layout.operator("node.add_node", text="Test").type = "SLICENODE_Test" diff --git a/doc/NODES.md b/doc/NODES.md index 9eab4a3..fb82fc5 100644 --- a/doc/NODES.md +++ b/doc/NODES.md @@ -94,4 +94,7 @@ All nodes are found in the **Add (Shift+A)** menu under the **SliceNodes** categ - **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 Preview**: Decodes G-code and creates a Blender mesh ("Debug" object) for visualization. +- **PrusaSlicer Preview**: Launches PrusaSlicer's built-in G-code viewer to visualize a G-code string. + - **Executable**: Path to the PrusaSlicer executable (default: `flatpak run com.prusa3d.PrusaSlicer`). + - **Gcode**: Input socket for the G-code string to preview. - **Test**: Triggers evaluation for debugging purposes.