Source code for iso10110.report

"""ISO 10110 System Report

Batches :class:`~optiland.iso10110.drawing.ElementDrawing` instances for every
element in a :class:`~optiland.iso10110.spec.DrawingSpec` and provides
combined save/preview methods.

Bernhard Lutzer, 2026
"""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from optiland.iso10110.spec import DrawingSpec

from optiland.iso10110.drawing import ElementDrawing
from optiland.iso10110.style import DrawingStyle


[docs] class ISO10110Report: """Generate ISO 10110 drawings for all elements in a system. Args: spec: The :class:`~optiland.iso10110.spec.DrawingSpec` that carries both the optic geometry and all tolerance annotations. style: :class:`~optiland.iso10110.style.DrawingStyle` font-size scale factors and border margin, applied to every element's drawing. Defaults to ``DrawingStyle()``, reproducing the built-in appearance exactly. Example:: report = ISO10110Report(spec) report.generate() report.save_dxf("output/") report.save_pdf("output/system_drawings.pdf") report.show() """ def __init__( self, spec: DrawingSpec, paper: str = "A4", orientation: str = "portrait", style: DrawingStyle | None = None, ) -> None: self.spec = spec self.paper = paper self.orientation = orientation self.style = style if style is not None else DrawingStyle() self._drawings: list[ElementDrawing] = [] # ------------------------------------------------------------------ # Generation # ------------------------------------------------------------------
[docs] def generate(self) -> list[ElementDrawing]: """Build an :class:`ElementDrawing` for every element. Note this only constructs the :class:`ElementDrawing` instances — it does not eagerly build each one's DXF document, so calling this (and the PDF/PNG/show methods below, which call it automatically) never requires ``ezdxf`` to be installed. ``ElementDrawing.generate()`` (DXF-specific) is invoked lazily by :meth:`save_dxf` instead. Returns: List of generated :class:`ElementDrawing` instances. """ total = len(self.spec.elements) self._drawings = [ ElementDrawing( element, self.spec, idx, paper=self.paper, orientation=self.orientation, total_sheets=total, style=self.style, ) for idx, element in enumerate(self.spec.elements) ] return self._drawings
# ------------------------------------------------------------------ # DXF export # ------------------------------------------------------------------
[docs] def save_dxf(self, directory: str | Path) -> list[Path]: """Save one DXF file per element into *directory*. Returns: List of written file paths. """ if not self._drawings: self.generate() directory = Path(directory) directory.mkdir(parents=True, exist_ok=True) paths = [] for d in self._drawings: proj = (self.spec.project_name or "element").replace(" ", "_") fname = directory / f"{proj}_E{d.element_index + 1:02d}.dxf" d.save_dxf(fname) paths.append(fname) return paths
# ------------------------------------------------------------------ # PDF export # ------------------------------------------------------------------
[docs] def save_pdf( self, path: str | Path, combined: bool = True, ) -> Path: """Save drawings to PDF. Args: path: If *combined* is True, the path of the combined multi-page PDF. If False, treated as a directory and one PDF per element is written. combined: When True (default) all elements go into a single multi-page PDF. Returns: Path to the written file (or the directory when *combined=False*). """ if not self._drawings: self.generate() path = Path(path) if combined: import matplotlib.pyplot as plt from matplotlib.backends.backend_pdf import PdfPages path.parent.mkdir(parents=True, exist_ok=True) with PdfPages(str(path)) as pdf: for d in self._drawings: fig = d._mpl_figure() pdf.savefig( fig, bbox_inches="tight", facecolor="white", edgecolor="none" ) plt.close(fig) return path else: path.mkdir(parents=True, exist_ok=True) proj = (self.spec.project_name or "element").replace(" ", "_") for d in self._drawings: pdf_path = path / f"{proj}_E{d.element_index + 1:02d}.pdf" d.save_pdf(pdf_path) return path
# ------------------------------------------------------------------ # PNG export # ------------------------------------------------------------------
[docs] def save_png( self, directory: str | Path, dpi: int = 200, ) -> list[Path]: """Save one PNG file per element into *directory*. Args: directory: Destination folder (created if needed). dpi: Resolution (default 200). Returns: List of written file paths. """ if not self._drawings: self.generate() directory = Path(directory) directory.mkdir(parents=True, exist_ok=True) paths = [] proj = (self.spec.project_name or "element").replace(" ", "_") for d in self._drawings: png_path = directory / f"{proj}_E{d.element_index + 1:02d}.png" d.save_png(png_path, dpi=dpi) paths.append(png_path) return paths
# ------------------------------------------------------------------ # Preview # ------------------------------------------------------------------
[docs] def show( self, element_index: int | None = None, figsize: tuple | None = None, ) -> None: """Display drawing(s) using matplotlib. Args: element_index: If given, show only that element (0-based). If None, show all elements sequentially. figsize: Optional figure size in inches. """ if not self._drawings: self.generate() if element_index is not None: self._drawings[element_index].show(figsize=figsize) else: for d in self._drawings: d.show(figsize=figsize)
# ------------------------------------------------------------------ # Accessors # ------------------------------------------------------------------ @property def drawings(self) -> list[ElementDrawing]: """List of generated :class:`ElementDrawing` instances.""" return self._drawings def __len__(self) -> int: return len(self._drawings) def __repr__(self) -> str: # pragma: no cover return f"ISO10110Report({len(self.spec.elements)} elements)"