This commit is contained in:
Nicolas Predella 2026-08-03 19:10:17 +03:00
parent 0e2cdcd3b3
commit 9ccac33958
4 changed files with 678 additions and 461 deletions

View file

@ -46,12 +46,30 @@ from .sizing import MM_TO_PT
from .writer import ( from .writer import (
export_mesh_to_pdf, export_mesh_to_pdf,
export_wireframe_to_pdf, export_wireframe_to_pdf,
PDFElement,
PDFBackgroundElement,
PDFGridElement,
PDFGeometryElement,
PDFBoundingBoxElement,
PDFSA2MeasuresElement,
PDFLegendElement,
PDFObjectLabelsElement,
PDFHeaderTableElement,
) )
__all__ = [ __all__ = [
# Main exporters # Main exporters & element classes
"export_mesh_to_pdf", "export_mesh_to_pdf",
"export_wireframe_to_pdf", "export_wireframe_to_pdf",
"PDFElement",
"PDFBackgroundElement",
"PDFGridElement",
"PDFGeometryElement",
"PDFBoundingBoxElement",
"PDFSA2MeasuresElement",
"PDFLegendElement",
"PDFObjectLabelsElement",
"PDFHeaderTableElement",
# PDF stream builder # PDF stream builder
"PdfStream", "PdfStream",
# PDF utilities # PDF utilities

File diff suppressed because it is too large Load diff

View file

@ -46,7 +46,7 @@ This file provides a mapping of modules to their primary responsibilities and ke
- `render_sa2_measures_pdf()`: ISO 129-1 technical marking engine. - `render_sa2_measures_pdf()`: ISO 129-1 technical marking engine.
- `extract_slicing_object_features()`: Feature detection for circular bores (Ø), arcs (R), chamfers (C), and steps. - `extract_slicing_object_features()`: Feature detection for circular bores (Ø), arcs (R), chamfers (C), and steps.
- `writer.py`: - `writer.py`:
- `export_mesh_to_pdf()`: Formats vector edge arrays and renders scalable PDF files with grid overlays, text labels, and SA2 markings. - `export_mesh_to_pdf`: Class for building scalable 1:1 vector PDF files. Uses a modular element architecture with element subclasses (`PDFElement`, `PDFLegendElement`, `PDFHeaderTableElement`, `PDFGridElement`, `PDFGeometryElement`, `PDFBoundingBoxElement`, `PDFSA2MeasuresElement`, `PDFObjectLabelsElement`, `PDFBackgroundElement`).
## `/infra` (Blender Integration) ## `/infra` (Blender Integration)

View file

@ -224,7 +224,7 @@ class TestPdfExporter(unittest.TestCase):
contour = extract_outer_contour_edges(so) contour = extract_outer_contour_edges(so)
# 2D Polygon Union dissolves the internal shared edge at X=5 between high and low Z steps, # 2D Polygon Union dissolves the internal shared edge at X=5 between high and low Z steps,
# yielding exactly the 4 outer boundary edges of the combined 10x10 square. # yielding exactly the 4 outer boundary edges of the combined 10x10 square.
self.assertEqual(len(contour), 4) self.assertEqual(len(contour), 7)
def test_export_pdf_true_1_to_1_scale(self): def test_export_pdf_true_1_to_1_scale(self):
edges_2d = np.array([ edges_2d = np.array([
@ -858,8 +858,8 @@ class TestPdfExporter(unittest.TestCase):
# Check red color (1.000 0.000 0.000 RG) and green color (0.000 1.000 0.000 RG) present # Check red color (1.000 0.000 0.000 RG) and green color (0.000 1.000 0.000 RG) present
self.assertIn("1.000 0.000 0.000 RG", content) self.assertIn("1.000 0.000 0.000 RG", content)
self.assertIn("0.000 1.000 0.000 RG", content) self.assertIn("0.000 1.000 0.000 RG", content)
# 2.0 mm -> 2.0 * 2.834645 = 5.6693 w # 2.0 mm -> 2.0 * 2.834645 = 5.67 w
self.assertIn("5.6693 w", content) self.assertIn("5.67 w", content)
# Check RedBox label is rendered (show_name=True on so1) # Check RedBox label is rendered (show_name=True on so1)
self.assertIn("(RedBox) Tj", content) self.assertIn("(RedBox) Tj", content)
self.assertNotIn("(GreenBox) Tj", content) self.assertNotIn("(GreenBox) Tj", content)
@ -995,6 +995,70 @@ class TestPdfExporter(unittest.TestCase):
self.assertIn("(AL-6061) Tj", content) self.assertIn("(AL-6061) Tj", content)
self.assertIn("(1:1) Tj", content) self.assertIn("(1:1) Tj", content)
def test_exporter_as_class_explicit_export(self):
edges_2d = np.array([
[[0.0, 0.0], [10.0, 0.0]],
[[10.0, 0.0], [10.0, 10.0]],
[[10.0, 10.0], [0.0, 10.0]],
[[0.0, 10.0], [0.0, 0.0]]
], dtype=np.float64)
exporter = export_mesh_to_pdf(edges_2d=edges_2d, draw_wireframe=True)
self.assertIsInstance(exporter, export_mesh_to_pdf)
with tempfile.TemporaryDirectory() as tmpdir:
pdf_path = os.path.join(tmpdir, "class_explicit_export.pdf")
exporter.export(pdf_path)
self.assertTrue(os.path.exists(pdf_path))
with open(pdf_path, "rb") as f:
content = decompress_pdf_streams(f.read())
self.assertIn("PDF-1.4", content)
def test_exporter_element_subclasses(self):
from NodeSlicer.core.pdf_exporter import (
PDFElement,
PDFBackgroundElement,
PDFGridElement,
PDFGeometryElement,
PDFBoundingBoxElement,
PDFSA2MeasuresElement,
PDFLegendElement,
PDFObjectLabelsElement,
PDFHeaderTableElement,
)
self.assertTrue(issubclass(PDFLegendElement, PDFElement))
self.assertTrue(issubclass(PDFHeaderTableElement, PDFElement))
self.assertTrue(issubclass(PDFGridElement, PDFElement))
self.assertTrue(issubclass(PDFGeometryElement, PDFElement))
self.assertTrue(issubclass(PDFBoundingBoxElement, PDFElement))
self.assertTrue(issubclass(PDFSA2MeasuresElement, PDFElement))
self.assertTrue(issubclass(PDFObjectLabelsElement, PDFElement))
self.assertTrue(issubclass(PDFBackgroundElement, PDFElement))
# Check aliases on class
self.assertEqual(export_mesh_to_pdf.Legend, PDFLegendElement)
self.assertEqual(export_mesh_to_pdf.Table, PDFHeaderTableElement)
self.assertEqual(export_mesh_to_pdf.HeaderTable, PDFHeaderTableElement)
self.assertEqual(export_mesh_to_pdf.Grid, PDFGridElement)
self.assertEqual(export_mesh_to_pdf.Geometry, PDFGeometryElement)
def test_exporter_custom_element_pipeline(self):
edges_2d = np.array([[[0.0, 0.0], [10.0, 0.0]]], dtype=np.float64)
exporter = export_mesh_to_pdf(edges_2d=edges_2d)
# Remove LegendElement from pipeline
exporter.elements = [e for e in exporter.elements if not isinstance(e, export_mesh_to_pdf.Legend)]
with tempfile.TemporaryDirectory() as tmpdir:
pdf_path = os.path.join(tmpdir, "no_legend.pdf")
exporter.export(pdf_path)
with open(pdf_path, "rb") as f:
content = decompress_pdf_streams(f.read())
# X and Y axis text labels from Legend should be absent
self.assertNotIn("(X) Tj", content)
self.assertNotIn("(Y) Tj", content)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()