output naming
This commit is contained in:
parent
45d47161f8
commit
b09a5fb636
3 changed files with 89 additions and 2 deletions
|
|
@ -20,6 +20,7 @@ class SLICENODE_GcodeToFile(ExecSliceNode):
|
|||
def init(self, context):
|
||||
self._init_exec()
|
||||
self.add_input("NodeSocketString", "Folder")
|
||||
self.add_input("NodeSocketString", "Name")
|
||||
self.add_multi_socket_group("NodeSocketString", name="Gcode")
|
||||
|
||||
def draw_buttons(self, context, layout):
|
||||
|
|
@ -38,7 +39,17 @@ class SLICENODE_GcodeToFile(ExecSliceNode):
|
|||
if operator: operator.report({'ERROR'}, "No Folder provided")
|
||||
return 'error'
|
||||
|
||||
base_name: str = self.resolve("Name")
|
||||
total_parts: int = len(gcodes)
|
||||
num_digits: int = max(2, len(str(total_parts)))
|
||||
|
||||
for i, gc in enumerate(gcodes):
|
||||
if base_name:
|
||||
if total_parts > 1:
|
||||
name = f"{base_name}{i:0{num_digits}d}"
|
||||
else:
|
||||
name = base_name
|
||||
else:
|
||||
name = get_gcode_name_from_metadata(gc, default=f'object_{i}')
|
||||
|
||||
# User's requested logic: .split('.')[0]
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ All nodes are found in the **Add (Shift+A)** menu under the **SliceNodes** categ
|
|||
- **Join Execution**: Synchronizes sequential execution flows.
|
||||
|
||||
### Action & I/O
|
||||
- **Gcode to File**: Writes G-code string to a file on disk.
|
||||
- **Gcode to File**: Writes G-code string to a file on disk. Accepts an optional **Name** input (which applies zero-padded digit suffixes when multiple G-codes are supplied, matching the preview mesh logic). If not plugged, uses the object names extracted from G-code metadata.
|
||||
- **Automatic Binary Conversion**: If the G-code contains the comment `; binary_gcode = 1`, the node automatically converts the output to Prusa's binary G-code format (`.bgcode`) using MeatPack encoding and Heatshrink compression.
|
||||
- **Gcode to Text Block**: Creates a new Blender text block containing the G-code and opens it in any active text editors.
|
||||
- **Start Print**: Sends G-code directly to a network printer.
|
||||
|
|
|
|||
|
|
@ -466,6 +466,82 @@ class TestTransform(unittest.TestCase):
|
|||
node._process_gcode.assert_any_call("GC1", "GcodePreview00", None)
|
||||
node._process_gcode.assert_any_call("GC2", "GcodePreview01", None)
|
||||
|
||||
def test_gcode_to_file_naming_convention(self):
|
||||
from unittest.mock import patch, mock_open
|
||||
from NodeSlicer.core.nodeslicer.nodes.output import SLICENODE_GcodeToFile
|
||||
node = SLICENODE_GcodeToFile()
|
||||
|
||||
mock_bpy.path.abspath = lambda p: p.replace("//", "/workspace/")
|
||||
|
||||
# 1. Single part with Name plugged - no number suffix
|
||||
with patch("NodeSlicer.infra.networking.gcode_utils.prepare_gcode_file", return_value=(b"GC", "Part.gcode")) as mock_prep, \
|
||||
patch("builtins.open", mock_open()), \
|
||||
patch("os.makedirs"):
|
||||
def resolve_side_effect(name):
|
||||
if name == "Folder": return "output"
|
||||
if name == "Name": return "Part"
|
||||
return ""
|
||||
node.resolve = MagicMock(side_effect=resolve_side_effect)
|
||||
node.resolve_multi_input = MagicMock(return_value=["GCODE1"])
|
||||
|
||||
res = node.evaluate()
|
||||
self.assertEqual(res, 'ok')
|
||||
mock_prep.assert_called_once_with("GCODE1", "Part")
|
||||
|
||||
# 2. Three parts with Name plugged - padded to 2 digits (00, 01, 02)
|
||||
with patch("NodeSlicer.infra.networking.gcode_utils.prepare_gcode_file", return_value=(b"GC", "test.gcode")) as mock_prep, \
|
||||
patch("builtins.open", mock_open()), \
|
||||
patch("os.makedirs"):
|
||||
def resolve_side_effect(name):
|
||||
if name == "Folder": return "output"
|
||||
if name == "Name": return "Part"
|
||||
return ""
|
||||
node.resolve = MagicMock(side_effect=resolve_side_effect)
|
||||
node.resolve_multi_input = MagicMock(return_value=["GC1", "GC2", "GC3"])
|
||||
|
||||
res = node.evaluate()
|
||||
self.assertEqual(res, 'ok')
|
||||
self.assertEqual(mock_prep.call_count, 3)
|
||||
mock_prep.assert_any_call("GC1", "Part00")
|
||||
mock_prep.assert_any_call("GC2", "Part01")
|
||||
mock_prep.assert_any_call("GC3", "Part02")
|
||||
|
||||
# 3. Fifteen parts with Name plugged - padded to 2 digits (00..14)
|
||||
with patch("NodeSlicer.infra.networking.gcode_utils.prepare_gcode_file", return_value=(b"GC", "test.gcode")) as mock_prep, \
|
||||
patch("builtins.open", mock_open()), \
|
||||
patch("os.makedirs"):
|
||||
def resolve_side_effect(name):
|
||||
if name == "Folder": return "output"
|
||||
if name == "Name": return "Bracket"
|
||||
return ""
|
||||
node.resolve = MagicMock(side_effect=resolve_side_effect)
|
||||
node.resolve_multi_input = MagicMock(return_value=[f"GC{i}" for i in range(1, 16)])
|
||||
|
||||
res = node.evaluate()
|
||||
self.assertEqual(res, 'ok')
|
||||
self.assertEqual(mock_prep.call_count, 15)
|
||||
mock_prep.assert_any_call("GC1", "Bracket00")
|
||||
mock_prep.assert_any_call("GC15", "Bracket14")
|
||||
|
||||
# 4. Not plugged / empty name - uses object names from metadata
|
||||
gc_meta1 = '; objects_info = {"objects": [{"name": "CustomPart"}]}\nG1 X10'
|
||||
gc_meta2 = 'G1 X20'
|
||||
with patch("NodeSlicer.infra.networking.gcode_utils.prepare_gcode_file", return_value=(b"GC", "test.gcode")) as mock_prep, \
|
||||
patch("builtins.open", mock_open()), \
|
||||
patch("os.makedirs"):
|
||||
def resolve_side_effect(name):
|
||||
if name == "Folder": return "output"
|
||||
if name == "Name": return ""
|
||||
return ""
|
||||
node.resolve = MagicMock(side_effect=resolve_side_effect)
|
||||
node.resolve_multi_input = MagicMock(return_value=[gc_meta1, gc_meta2])
|
||||
|
||||
res = node.evaluate()
|
||||
self.assertEqual(res, 'ok')
|
||||
self.assertEqual(mock_prep.call_count, 2)
|
||||
mock_prep.assert_any_call(gc_meta1, "CustomPart")
|
||||
mock_prep.assert_any_call(gc_meta2, "object_1")
|
||||
|
||||
def test_drop_geometry_applied_to_main_transform_no_double_accumulation(self):
|
||||
"""Verifies that dropping geometry updates the single main transform without accumulating duplicate records."""
|
||||
from NodeSlicer.core.nodeslicer.nodes.mesh import SLICENODE_TransformGeometry, SLICENODE_DropGeometry
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue