diff --git a/core/nodeslicer/nodes/output.py b/core/nodeslicer/nodes/output.py index 50dbee5..730c9fc 100644 --- a/core/nodeslicer/nodes/output.py +++ b/core/nodeslicer/nodes/output.py @@ -593,7 +593,7 @@ class SLICENODE_MeshToPdf(ExecSliceNode): out_dir = folder_path if folder_path.suffix == "" else folder_path.parent os.makedirs(out_dir, exist_ok=True) if isinstance(target_item, SlicingGroup) and target_item.collections: - raw_name = "_".join(target_item.collections.keys()) + raw_name = next(iter(target_item.collections.keys())) elif hasattr(target_item, 'name') and getattr(target_item, 'name'): raw_name = str(getattr(target_item, 'name')) else: diff --git a/core/nodeslicer/nodes/path.py b/core/nodeslicer/nodes/path.py index db1bf45..1ddb437 100644 --- a/core/nodeslicer/nodes/path.py +++ b/core/nodeslicer/nodes/path.py @@ -188,7 +188,7 @@ class SLICENODE_SliceShells(SliceNode): names.append(str(getattr(coll, "name"))) if names: - slices.name = "-".join(names) + slices.name = names[0] slices.objects_info = {"objects": [{"name": n, "polygon": []} for n in names]} if len(z_values) > 0 and fl_height is not None and layer_height > 0: diff --git a/core/slice.py b/core/slice.py index ec58b70..caeca10 100644 --- a/core/slice.py +++ b/core/slice.py @@ -392,7 +392,7 @@ class SlicePaths(list[np.ndarray]): merged.transforms = [dict(t) for t in getattr(base, "transforms", [])] if merged_objs: merged.objects_info = {"objects": merged_objs} - merged.name = "-".join(merged_names) + merged.name = merged_names[0] if merged_names else "" return merged @@ -1233,7 +1233,7 @@ def boolean_slice(subject: SlicePaths, object: SlicePaths, operation: str = 'UNI results.transforms = [dict(t) for t in subj_trans] if subj_trans else ([dict(t) for t in obj_trans] if obj_trans else []) if merged_objs: results.objects_info = {"objects": merged_objs} - results.name = "-".join(merged_names) + results.name = merged_names[0] if merged_names else "" return results @@ -1361,7 +1361,7 @@ def minimum_coverage_slice( results.transforms = [dict(t) for t in getattr(primary, "transforms", [])] if merged_objs: results.objects_info = {"objects": merged_objs} - results.name = "-".join(merged_names) + results.name = merged_names[0] if merged_names else "" return results @@ -1429,6 +1429,8 @@ def maximum_coverage_slice( setattr(results, attr, copy.deepcopy(val) if isinstance(val, (list, dict)) else val) results.transforms = [dict(t) for t in getattr(sp, "transforms", [])] + results.name = getattr(sp, "name", "") + results.objects_info = copy.deepcopy(getattr(sp, "objects_info", None)) return results # Multiple inputs: sequentially union all inputs with boolean_slice diff --git a/infra/blender/blender_mesh_capture.py b/infra/blender/blender_mesh_capture.py index 065f3f3..aba752b 100644 --- a/infra/blender/blender_mesh_capture.py +++ b/infra/blender/blender_mesh_capture.py @@ -136,7 +136,7 @@ class SlicingObject(): if not objects: return merged # Take metadata from first object if available - merged.name = objects[0].name + "_merged" + merged.name = objects[0].name if objects else "" merged.parent = objects[0].parent merged.pdf_properties = copy.deepcopy(objects[0].pdf_properties) if hasattr(objects[0], "pdf_properties") and objects[0].pdf_properties else PdfProperties() merged.pdf_object_properties = list(getattr(objects[0], "pdf_object_properties", [])) diff --git a/infra/networking/gcode_utils.py b/infra/networking/gcode_utils.py index 9c2b939..e0dfb91 100644 --- a/infra/networking/gcode_utils.py +++ b/infra/networking/gcode_utils.py @@ -54,11 +54,11 @@ def get_gcode_name_from_metadata(gcode: str, default: str = "output") -> str: if parts_match: try: gcode_info = json.loads(parts_match.group(1)) - names: list[str] = [str(v['name'].split(" id:")[0]) for v in gcode_info['objects']] - counts = Counter(names) - object_names = "-".join(f"{c}x{n}" if c > 1 else n for n, c in counts.items()) - parts = [object_names, filament, printer] - return "-".join([str(p) for p in parts if p]) + names: list[str] = [str(v['name'].split(" id:")[0]) for v in gcode_info.get('objects', []) if v.get('name')] + if names: + object_names = names[0] + parts = [object_names, filament, printer] + return "-".join([str(p) for p in parts if p]) except Exception: pass @@ -67,8 +67,7 @@ def get_gcode_name_from_metadata(gcode: str, default: str = "output") -> str: if obj_matches: names = [m.strip().split(" id:")[0] for m in obj_matches if m.strip()] if names: - counts = Counter(names) - object_names = "-".join(f"{c}x{n}" if c > 1 else n for n, c in counts.items()) + object_names = names[0] parts = [object_names, filament, printer] return "-".join([str(p) for p in parts if p]) diff --git a/tests/test_slice.py b/tests/test_slice.py index 41bce95..f9380e1 100644 --- a/tests/test_slice.py +++ b/tests/test_slice.py @@ -829,7 +829,7 @@ class TestSlice(unittest.TestCase): gcode_multi = pather_multi.to_gcode(include_header=True) self.assertIn("; objects_info =", gcode_multi) name_multi = get_gcode_name_from_metadata(gcode_multi) - self.assertEqual(name_multi, "PartA-PartB") + self.assertEqual(name_multi, "PartA") # 3. Test get_gcode_name_from_metadata fallback object comments gcode_fallback = "; Generated Gcode\n; OBJECT_NAME = CustomBracket\nG1 X10 Y10\n" @@ -1529,13 +1529,11 @@ class TestSlice(unittest.TestCase): sp2.transforms = [{"type": "transform", "translation": (0.0, 0.0, 0.0), "rotation": (0.0, 0.0, 0.0), "scale": (2.0, 2.0, 2.0)}] merged_zip = SlicePaths.zip_paths([sp1, sp2]) - self.assertIn("ObjectA", merged_zip.name) - self.assertIn("ObjectB", merged_zip.name) + self.assertEqual(merged_zip.name, "ObjectA") self.assertEqual(len(merged_zip.transforms), 1) merged_inter = SlicePaths.path_operations([sp1, sp2], operation='INTERSECTION') - self.assertIn("ObjectA", merged_inter.name) - self.assertIn("ObjectB", merged_inter.name) + self.assertEqual(merged_inter.name, "ObjectA") self.assertEqual(len(merged_inter.transforms), 1) def test_complex_donut_intersection_and_bullseye(self): @@ -1828,6 +1826,52 @@ class TestCodeReviewBugFixes(unittest.TestCase): self.assertAlmostEqual(np.min(res[0][:, 0]), -1.0, places=2) self.assertAlmostEqual(np.max(res[0][:, 0]), 21.0, places=2) + def test_name_preservation_across_all_nodes_and_operations(self): + from NodeSlicer.core.nodeslicer.nodes.path import SLICENODE_SliceShells + from NodeSlicer.core.slice import SlicePaths, path_operations_slice + from NodeSlicer.infra.blender.blender_mesh_capture import SlicingCollection, SlicingObject + from NodeSlicer.infra.networking.gcode_utils import get_gcode_name_from_metadata + + # 1. SlicingObject.merge + so1 = SlicingObject() + so1.name = "FirstMesh" + so1.mesh = np.zeros((1, 4, 3)) + so2 = SlicingObject() + so2.name = "SecondMesh" + so2.mesh = np.zeros((1, 4, 3)) + so_merged = SlicingObject.merge([so1, so2]) + self.assertEqual(so_merged.name, "FirstMesh") + + # 2. SLICENODE_SliceShells + coll = SlicingCollection([], "MyCollection") + coll.objects = [so1, so2] + node = SLICENODE_SliceShells() + resolve_map = { + "Mesh": coll, + "Layer Height": 0.2, + "First Layer Height": 0.2, + "Rotation": (0.0, 0.0, 0.0) + } + with patch.object(node, "resolve", side_effect=lambda name: resolve_map.get(name)): + with patch("NodeSlicer.core.nodeslicer.nodes.path.slice_at_range", return_value=[[np.array([[0, 0, 0.2], [10, 0, 0.2], [10, 10, 0.2], [0, 10, 0.2], [0, 0, 0.2]])]]): + slices = node.evaluate_output("Paths") + self.assertEqual(slices.name, "FirstMesh") + + # 3. Path Operations (ZIP, EXTEND, MAX_COVERAGE, MIN_COVERAGE, INTERSECTION, SUBTRACT, XOR) + p1 = np.array([[0, 0, 0.2], [10, 0, 0.2], [10, 10, 0.2], [0, 10, 0.2], [0, 0, 0.2]]) + p2 = np.array([[5, 0, 0.2], [15, 0, 0.2], [15, 10, 0.2], [5, 10, 0.2], [5, 0, 0.2]]) + sp_a = SlicePaths([p1], groups=[0], name="Alpha") + sp_b = SlicePaths([p2], groups=[0], name="Beta") + + for op in ['ZIP', 'EXTEND', 'MAX_COVERAGE', 'MIN_COVERAGE', 'INTERSECTION', 'SUBTRACT', 'XOR']: + res = path_operations_slice([sp_a, sp_b], operation=op) + self.assertEqual(res.name, "Alpha", f"Failed for operation {op}") + + # 4. G-code name from metadata with multiple objects in objects_info + gc = '; objects_info = {"objects": [{"name": "BracketA", "polygon": []}, {"name": "BracketB", "polygon": []}]}\n; filament_type = PLA\n; printer_model = MK4\n' + gname = get_gcode_name_from_metadata(gc) + self.assertEqual(gname, "BracketA-PLA-MK4") + if __name__ == '__main__': unittest.main()