From 5d52c936be12164320baf98800fa9384581ed2d0 Mon Sep 17 00:00:00 2001 From: Nicolas Predella Date: Tue, 4 Aug 2026 13:56:16 +0300 Subject: [PATCH] up --- core/nodeslicer/nodes/output.py | 23 +++++++--- core/pdf_exporter/writer.py | 13 +----- doc/NODES.md | 4 +- tests/test_pdf_exporter.py | 79 ++++++++++++++++++++------------- 4 files changed, 69 insertions(+), 50 deletions(-) diff --git a/core/nodeslicer/nodes/output.py b/core/nodeslicer/nodes/output.py index a8f5d23..a98f0bb 100644 --- a/core/nodeslicer/nodes/output.py +++ b/core/nodeslicer/nodes/output.py @@ -366,7 +366,6 @@ class SLICENODE_MeshToPdf(ExecSliceNode): draw_sa2_holes: NfBoolProperty(name="Holes (Ø)", default=False) # pyright: ignore[reportInvalidTypeForm] show_lineart: NfBoolProperty(name="Lineart Visibility", default=False) # pyright: ignore[reportInvalidTypeForm] show_sa2: NfBoolProperty(name="SA2 Settings", default=False) # pyright: ignore[reportInvalidTypeForm] - show_bbox_size: NfBoolProperty(name="Bounding Box Sizes", default=False) # pyright: ignore[reportInvalidTypeForm] draw_header: NfBoolProperty(name="Technical Header", default=True) # pyright: ignore[reportInvalidTypeForm] show_header: NfBoolProperty(name="Technical Drawing Header", default=False) # pyright: ignore[reportInvalidTypeForm] @@ -412,7 +411,8 @@ class SLICENODE_MeshToPdf(ExecSliceNode): self._init_exec() self.add_input("SLICESOCK_DirPath", "Folder") self.add_input("SLICESOCK_Settings", "Settings") - self.add_multi_socket_group("SLICESOCK_Mesh", name="Geometry") + self.add_input("SLICESOCK_Mesh", "Geometry") + self.add_input("SLICESOCK_Paths", "Paths") def draw_buttons(self, context, layout): super().draw_buttons(context, layout) @@ -451,7 +451,6 @@ class SLICENODE_MeshToPdf(ExecSliceNode): layout.prop(self, "draw_bbox") if self.draw_bbox: layout.prop(self, "bbox_color") - layout.prop(self, "show_bbox_size") # Styling settings layout.prop(self, "font_size") @@ -463,10 +462,16 @@ class SLICENODE_MeshToPdf(ExecSliceNode): import datetime from ...pdf_exporter import extract_unique_top_view_edges, export_mesh_to_pdf - geometries: list[Any] = [g for g in self.resolve_multi_input('Geometry') if g is not None] + geom = self.resolve("Geometry") + slice_paths = self.resolve("Paths") # Currently unused folder: str = self.resolve("Folder") or "" settings = self.resolve("Settings") + if geom is None: + if operator: operator.report({'ERROR'}, "No Geometry connected") + return 'error' + + geometries: list[Any] = [g for g in (geom if isinstance(geom, list) else [geom]) if g is not None] if not geometries: if operator: operator.report({'ERROR'}, "No Geometry connected") return 'error' @@ -475,7 +480,14 @@ class SLICENODE_MeshToPdf(ExecSliceNode): if operator: operator.report({'ERROR'}, "No Folder provided") return 'error' - abs_folder = bpy.path.abspath(f"//{folder}" if not folder.startswith(("/", "\\")) and ":" not in folder else folder) + abs_folder = folder + if hasattr(bpy, "path") and hasattr(bpy.path, "abspath"): + try: + res = bpy.path.abspath(f"//{folder}" if not folder.startswith(("/", "\\")) and ":" not in folder else folder) + if isinstance(res, str) and not type(res).__module__.startswith("unittest.mock"): + abs_folder = res + except Exception: + pass folder_path = Path(abs_folder) is_single_pdf_file = len(geometries) == 1 and folder_path.suffix.lower() == ".pdf" @@ -542,7 +554,6 @@ class SLICENODE_MeshToPdf(ExecSliceNode): draw_sa2_holes=self.draw_sa2_holes, draw_header=self.draw_header, header_info=header_info, - show_bbox_size=self.show_bbox_size, crease_angle=self.crease_angle, margin_mm=self.margin, font_size_mm=self.font_size * 2.834645669291339, diff --git a/core/pdf_exporter/writer.py b/core/pdf_exporter/writer.py index 6a0ae85..90071a2 100644 --- a/core/pdf_exporter/writer.py +++ b/core/pdf_exporter/writer.py @@ -561,7 +561,7 @@ class PDFObjectLabelsElement(PDFElement): slicing_objs_list = exp.slicing_objs has_any_obj_show_name = any(hasattr(o, "pdf_properties") and o.pdf_properties and o.pdf_properties.show_name for o in slicing_objs_list) - if (exp.show_name or has_any_obj_show_name or exp.show_bbox_size or settings_items) and exp.raw_geometry is not None: + if (exp.show_name or has_any_obj_show_name or settings_items) and exp.raw_geometry is not None: label_infos = extract_object_label_info(exp.raw_geometry) if label_infos: eff_font_size_pt = exp.font_size_pt if exp.font_size_pt is not None and exp.font_size_pt > 0 else max(8.0, exp.line_width_pt * 4.0) @@ -585,15 +585,6 @@ class PDFObjectLabelsElement(PDFElement): if so_show_name: lines_to_draw.append((str(obj_name), so_color, True)) - if exp.show_bbox_size: - if exp.main_step >= 1000.0: - w_val = w_obj_mm / 1000.0 - h_val = h_obj_mm / 1000.0 - size_str = f"(X: {w_val:g}m Y: {h_val:g}m)" - else: - size_str = f"(X: {w_obj_mm:g}mm Y: {h_obj_mm:g}mm)" - lines_to_draw.append((size_str, exp.bbox_color, False)) - for key_name, suffix in settings_items: attr_val = get_first_attribute_value(so_item, key_name) if attr_val is not None: @@ -730,7 +721,6 @@ class export_mesh_to_pdf: draw_header: bool = False, header_info: dict[str, Any] | None = None, show_name: bool = False, - show_bbox_size: bool = False, crease_angle: float = 30.0, margin_mm: float = 10.0, line_width_mm: float = 0.0, @@ -755,7 +745,6 @@ class export_mesh_to_pdf: header_info = {} self.header_info = header_info self.show_name = show_name - self.show_bbox_size = show_bbox_size self.crease_angle = crease_angle self.margin_mm = margin_mm self.line_width_mm = line_width_mm diff --git a/doc/NODES.md b/doc/NODES.md index 5caaa21..3e3abf9 100644 --- a/doc/NODES.md +++ b/doc/NODES.md @@ -129,7 +129,8 @@ All nodes are found in the **Add (Shift+A)** menu under the **SliceNodes** categ - **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. - **Mesh to PDF**: Generates a 1:1 true-scale 2D top-view (+Z orthographic) vector PDF from a `SlicingGroup` or `SlicingCollection` with a custom grid and background color. Subpanel menus are collapsed by default. - **Technical Drawing Header**: Collapsible menu containing string inputs for technical drawing info (Company, Part Name, Drawing No., Author, Revision, Material, Tolerance). Automatically computes ISO date and scale length (`1:1`). Renders an ISO/DIN technical drawing header table on the bottom-right of the drawing. - - **Geometry**: Input socket accepting a `SlicingGroup`, `SlicingCollection`, or `SlicingObject`. + - **Geometry**: Input socket accepting a single geometry (`SlicingGroup`, `SlicingCollection`, or `SlicingObject`). Join multiple geometries using Join Geometry nodes. + - **SlicePaths**: Input socket accepting `SlicePaths` (reserved for path rendering). - **Folder**: Target folder or PDF file path on disk. - **Settings**: Input socket accepting configuration settings or attribute names to render as `: ` next to each object. - **Wireframe**: Checkbox to render all projected 3D wireframe edges. @@ -139,7 +140,6 @@ All nodes are found in the **Add (Shift+A)** menu under the **SliceNodes** categ - **Bounding Box**: Checkbox to render the 2D bounding box rectangle of each `SlicingObject`. - **BBox Color**: Color picker for the bounding box rectangle lines (default blue). - **Show Name**: Checkbox to render the object name to the right of the rightmost vertex of each `SlicingObject`. - - **Bounding Box Sizes**: Checkbox to render 2D bounding box size dimensions (e.g. `X: 120mm Y: 230mm` or `X: 1.2m Y: 2.3m`) next to each object label. - **Holes**: Checkbox to render circular bore/hole diameter callouts (Ø) with ISO 129-1 compliant markings. - **Arcs**: Checkbox to render arc cutouts and corner fillet radius callouts (R) with ISO 129-1 compliant markings. - **Chamfers**: Checkbox to render bevel/chamfer callouts (C) with ISO 129-1 compliant markings. diff --git a/tests/test_pdf_exporter.py b/tests/test_pdf_exporter.py index 3581bfa..482e18b 100644 --- a/tests/test_pdf_exporter.py +++ b/tests/test_pdf_exporter.py @@ -329,35 +329,6 @@ class TestPdfExporter(unittest.TestCase): content = f.read() self.assertIn(b"(MyTestPart) Tj", content) - def test_export_pdf_show_bbox_size(self): - so = SlicingObject() - so.name = "GearPart" - tri = np.array([ - [10.0, 20.0, 0.0], - [130.0, 20.0, 0.0], - [10.0, 250.0, 0.0], - [0.0, 0.0, 1.0] - ], dtype=np.float64) - so.mesh = np.array([tri]) - - edges_2d = extract_unique_top_view_edges(so) - - with tempfile.TemporaryDirectory() as tmpdir: - pdf_path = os.path.join(tmpdir, "test_show_bbox.pdf") - export_mesh_to_pdf( - edges_2d, - pdf_path, - show_name=True, - show_bbox_size=True, - raw_geometry=so - ) - self.assertTrue(os.path.exists(pdf_path)) - with open(pdf_path, "rb") as f: - content = f.read() - # Width = 120mm, Height = 230mm -> Line 1: (GearPart) Tj, Line 2: (\(X: 120mm Y: 230mm\)) Tj - self.assertIn(b"(GearPart) Tj", content) - self.assertIn(b"(\\(X: 120mm Y: 230mm\\)) Tj", content) - def test_export_pdf_draw_bbox(self): so = SlicingObject() so.name = "BBoxPart" @@ -611,7 +582,6 @@ class TestPdfExporter(unittest.TestCase): edges_2d, pdf_path, show_name=True, - show_bbox_size=True, raw_geometry=so, settings={"infill_density": "%", "layer_height": " mm", "material": ""} ) @@ -942,6 +912,55 @@ class TestPdfExporter(unittest.TestCase): self.assertIn("header_material", annotations) self.assertIn("header_tolerance", annotations) + def test_mesh_to_pdf_single_geometry_and_slicepaths_inputs(self): + from NodeSlicer.core.nodeslicer.nodes.output import SLICENODE_MeshToPdf + node = SLICENODE_MeshToPdf.__new__(SLICENODE_MeshToPdf) + node.draw_wireframe = True + node.draw_boundary = False + node.draw_contour = False + node.draw_crease = False + node.crease_angle = 30.0 + node.draw_bbox = False + node.draw_sa2_holes = False + node.draw_header = True + node.header_company = "" + node.header_part_name = "" + node.header_drawing_number = "" + node.header_author = "" + node.header_revision = "A" + node.header_material = "" + node.header_tolerance = "" + node.font_size = 0.0 + node.margin = 10.0 + node.wire_color = (0.0, 0.0, 0.0) + node.bg_color = (1.0, 1.0, 1.0) + node.bbox_color = (0.0, 0.0, 1.0) + + class MockOperator: + def __init__(self): + self.reports = [] + def report(self, type_set, message): + self.reports.append((type_set, message)) + + op = MockOperator() + so = SlicingObject() + so.name = "TestObj" + so.mesh = np.array([[[0.0, 0.0, 0.0], [10.0, 0.0, 0.0], [0.0, 10.0, 0.0]]], dtype=np.float64) + + with tempfile.TemporaryDirectory() as tmpdir: + pdf_file = os.path.join(tmpdir, "output.pdf") + resolved_inputs = { + "Geometry": so, + "SlicePaths": None, + "Folder": pdf_file, + "Settings": None, + } + node.resolve = lambda fieldname: resolved_inputs.get(fieldname) + + res = node.evaluate(operator=op) + self.assertEqual(res, "ok", msg=f"Reports: {op.reports}") + self.assertTrue(os.path.exists(pdf_file)) + def test_export_pdf_technical_header_table(self): import tempfile