This commit is contained in:
Nicolas Predella 2026-08-25 12:34:29 +03:00
parent 3793c45f54
commit 29f7b71be1
5 changed files with 124 additions and 4 deletions

View file

@ -232,7 +232,7 @@ class SLICENODE_SeparateMeshByRange(SliceNode):
# Attribute values might be floats or ints, we check for equality # Attribute values might be floats or ints, we check for equality
mask = vals == target_val mask = vals == target_val
num_digits = max(2, self.end_val) num_digits = max(2, len(str(self.end_val)))
if np.all(mask): if np.all(mask):
res.objects.append(so) res.objects.append(so)

View file

@ -32,6 +32,10 @@ class SLICENODE_GetPaths(SliceNode):
sp = SlicePaths(so, extrusion_rate=e_rates, feedrate=f_rates, extra_attributes=extra_attrs_data) sp = SlicePaths(so, extrusion_rate=e_rates, feedrate=f_rates, extra_attributes=extra_attrs_data)
if hasattr(obj, "transforms"): if hasattr(obj, "transforms"):
sp.transforms = list(getattr(obj, "transforms", [])) sp.transforms = list(getattr(obj, "transforms", []))
if hasattr(obj, "name") and getattr(obj, "name"):
name_val = str(getattr(obj, "name"))
sp.name = name_val
sp.objects_info = {"objects": [{"name": name_val, "polygon": []}]}
return sp return sp
@register_class @register_class
@ -109,6 +113,22 @@ class SLICENODE_SliceShells(SliceNode):
slices.first_layer_height = fl_height slices.first_layer_height = fl_height
slices.transforms = list(getattr(coll, "transforms", [])) or list(getattr(merged_object, "transforms", [])) slices.transforms = list(getattr(coll, "transforms", [])) or list(getattr(merged_object, "transforms", []))
names = []
if objects:
for o in objects:
n = getattr(o, "name", "")
if n:
if n.endswith("_merged"):
n = n[:-7]
if n and n not in names:
names.append(n)
if not names and hasattr(coll, "name") and getattr(coll, "name"):
names.append(str(getattr(coll, "name")))
if names:
slices.name = "-".join(names)
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: if len(z_values) > 0 and fl_height is not None and layer_height > 0:
mult = fl_height / layer_height mult = fl_height / layer_height
z_first = z_values[0] z_first = z_values[0]

View file

@ -3,6 +3,7 @@ from numpy import float64
import numpy as np import numpy as np
import pyclipper import pyclipper
from typing import Any, Literal from typing import Any, Literal
import copy
from shapely.geometry.base import BaseGeometry from shapely.geometry.base import BaseGeometry
from ..infra.blender.blender_mesh_capture import PdfProperties from ..infra.blender.blender_mesh_capture import PdfProperties
@ -164,8 +165,10 @@ class SlicePaths(list[np.ndarray]):
transforms: list[dict] transforms: list[dict]
object_properties: list[tuple[str, str]] object_properties: list[tuple[str, str]]
pdf_properties: PdfProperties pdf_properties: PdfProperties
name: str
objects_info: dict | None
def __init__(self, paths: list[np.ndarray] = [], groups: list[int] = [], extrusion_rate: list[np.ndarray] | None = None, feedrate: list[np.ndarray] | None = None, extra_attributes: dict[str, list[np.ndarray]] | None = None, layer_height: float = 0.2, first_layer_height: float | None = None, transforms: list[dict] | None = None): def __init__(self, paths: list[np.ndarray] = [], groups: list[int] = [], extrusion_rate: list[np.ndarray] | None = None, feedrate: list[np.ndarray] | None = None, extra_attributes: dict[str, list[np.ndarray]] | None = None, layer_height: float = 0.2, first_layer_height: float | None = None, transforms: list[dict] | None = None, name: str = "", objects_info: dict | None = None):
super().__init__(paths) super().__init__(paths)
self.groups = [] self.groups = []
@ -188,6 +191,8 @@ class SlicePaths(list[np.ndarray]):
self.layer_height = layer_height self.layer_height = layer_height
self.first_layer_height = first_layer_height self.first_layer_height = first_layer_height
self.transforms = list(transforms) if transforms is not None else [] self.transforms = list(transforms) if transforms is not None else []
self.name = name
self.objects_info = copy.deepcopy(objects_info) if objects_info is not None else None
def copy_with(self, paths: list[np.ndarray], groups: list[int] | None = None, extrusion_rate: list[np.ndarray] | None = None, feedrate: list[np.ndarray] | None = None, extra_attributes: dict[str, list[np.ndarray]] | None = None) -> SlicePaths: def copy_with(self, paths: list[np.ndarray], groups: list[int] | None = None, extrusion_rate: list[np.ndarray] | None = None, feedrate: list[np.ndarray] | None = None, extra_attributes: dict[str, list[np.ndarray]] | None = None) -> SlicePaths:
"""Returns a new SlicePaths object with new paths/groups, preserving all metadata and instance attributes.""" """Returns a new SlicePaths object with new paths/groups, preserving all metadata and instance attributes."""
@ -349,11 +354,30 @@ class SlicePaths(list[np.ndarray]):
merged.append_with_group(path, group=g, extrusion_rate=ext, feedrate=feed, extra_attributes=extra) merged.append_with_group(path, group=g, extrusion_rate=ext, feedrate=feed, extra_attributes=extra)
merged_transforms = [] merged_transforms = []
merged_names = []
merged_objs = []
for sp in paths_list: for sp in paths_list:
if hasattr(sp, "objects_info") and getattr(sp, "objects_info"):
for obj_entry in sp.objects_info.get("objects", []):
if obj_entry not in merged_objs:
merged_objs.append(dict(obj_entry))
n_str = obj_entry.get("name", "")
if n_str and n_str not in merged_names:
merged_names.append(n_str)
elif hasattr(sp, "name") and getattr(sp, "name"):
n_str = str(getattr(sp, "name"))
if n_str and n_str not in merged_names:
merged_names.append(n_str)
merged_objs.append({"name": n_str, "polygon": []})
for t in getattr(sp, "transforms", []): for t in getattr(sp, "transforms", []):
if t not in merged_transforms: if t not in merged_transforms:
merged_transforms.append(t) merged_transforms.append(t)
merged.transforms = merged_transforms merged.transforms = merged_transforms
if merged_objs:
merged.objects_info = {"objects": merged_objs}
merged.name = "-".join(merged_names)
return merged return merged
@ -1583,6 +1607,8 @@ class Pather(object):
self.retraction_feedrate: float = 0.0 self.retraction_feedrate: float = 0.0
self.lift: float = 0.0 self.lift: float = 0.0
self.transforms: list[dict] = list(getattr(paths, "transforms", [])) self.transforms: list[dict] = list(getattr(paths, "transforms", []))
self.objects_info: dict | None = getattr(paths, "objects_info", None)
self.name: str = str(getattr(paths, "name", ""))
@property @property
def layer_width(self) -> float: def layer_width(self) -> float:
@ -1653,6 +1679,13 @@ class Pather(object):
f";OFFSET_Y={offset_y:.6f}", f";OFFSET_Y={offset_y:.6f}",
]) ])
if self.objects_info:
import json
header.append(f"; objects_info = {json.dumps(self.objects_info)}")
elif self.name:
import json
header.append(f'; objects_info = {json.dumps({"objects": [{"name": self.name, "polygon": []}]})}')
if hasattr(self, "transforms") and self.transforms: if hasattr(self, "transforms") and self.transforms:
import json import json
header.append(f";PREPARATION_TRANSFORMS={json.dumps(self.transforms)}") header.append(f";PREPARATION_TRANSFORMS={json.dumps(self.transforms)}")

View file

@ -37,7 +37,7 @@ def parse_gcode_metadata(gcode: str) -> dict[str, Any]:
return fields return fields
def get_gcode_name_from_metadata(gcode: str, default: str = "output") -> str: def get_gcode_name_from_metadata(gcode: str, default: str = "output") -> str:
"""Generates a descriptive name for the G-code based on its objects_info metadata.""" """Generates a descriptive name for the G-code based on its objects_info metadata or object comments."""
parts_match = re.search(r"; objects_info = (.*)", gcode) parts_match = re.search(r"; objects_info = (.*)", gcode)
filament_match = re.search(r"; filament_type = (.*)", gcode) filament_match = re.search(r"; filament_type = (.*)", gcode)
@ -54,8 +54,19 @@ def get_gcode_name_from_metadata(gcode: str, default: str = "output") -> str:
object_names = "-".join(f"{c}x{n}" if c > 1 else n for n, c in counts.items()) object_names = "-".join(f"{c}x{n}" if c > 1 else n for n, c in counts.items())
parts = [object_names, filament, printer] parts = [object_names, filament, printer]
return "-".join([str(p) for p in parts if p]) return "-".join([str(p) for p in parts if p])
except: except Exception:
pass pass
# Fallback search for object name comments (e.g. ; OBJECT_NAME = ..., ; object_name = ..., ; OBJECT: ...)
obj_matches = re.findall(r";\s*(?:OBJECT_NAME|object_name|OBJECT|object)\s*[:=]\s*(.+)", gcode)
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())
parts = [object_names, filament, printer]
return "-".join([str(p) for p in parts if p])
return default return default

View file

@ -706,5 +706,61 @@ class TestSlice(unittest.TestCase):
res_intr = node.evaluate_output("SLICESOCK_Intrinsics") res_intr = node.evaluate_output("SLICESOCK_Intrinsics")
self.assertEqual(res_intr.retraction_feedrate, 1800.0) self.assertEqual(res_intr.retraction_feedrate, 1800.0)
def test_gcode_name_from_metadata_paths_to_gcode(self):
from NodeSlicer.core.slice import SlicePaths, Pather
from NodeSlicer.infra.networking.gcode_utils import get_gcode_name_from_metadata
# 1. Test SlicePaths with explicit name
p1 = np.array([[0.0, 0.0, 0.0], [10.0, 0.0, 0.0]])
sp = SlicePaths([p1], name="GearPart")
pather = Pather(sp)
gcode = pather.to_gcode(include_header=True)
self.assertIn("; objects_info =", gcode)
name = get_gcode_name_from_metadata(gcode)
self.assertEqual(name, "GearPart")
# 2. Test SlicePaths with objects_info (multiple objects)
sp_multi = SlicePaths(
[p1],
objects_info={"objects": [{"name": "PartA", "polygon": []}, {"name": "PartB", "polygon": []}]}
)
pather_multi = Pather(sp_multi)
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")
# 3. Test get_gcode_name_from_metadata fallback object comments
gcode_fallback = "; Generated Gcode\n; OBJECT_NAME = CustomBracket\nG1 X10 Y10\n"
self.assertEqual(get_gcode_name_from_metadata(gcode_fallback), "CustomBracket")
def test_separate_mesh_by_range_padding(self):
from NodeSlicer.core.nodeslicer.nodes.mesh import SLICENODE_SeparateMeshByRange
from NodeSlicer.infra.blender.blender_mesh_capture import SlicingCollection, SlicingObject
node = SLICENODE_SeparateMeshByRange()
node.end_val = 6
so = SlicingObject()
so.name = "CA"
so.mesh = np.zeros((4, 4, 3))
so.attributes = {
"part_id": {
"domain": "FACE",
"type": "INT",
"values": np.array([1, 1, 2, 2])
}
}
coll = SlicingCollection.__new__(SlicingCollection)
coll.objects = [so]
coll.name = "TestColl"
coll.transforms = []
node.resolve = lambda name: coll if name == "Mesh" else ("part_id" if name == "Attribute" else None)
res_1 = node.evaluate_output("1")
self.assertIsNotNone(res_1)
self.assertEqual(res_1.objects[0].name, "CA01")
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()