#!/usr/bin/env python3 """Verify the complete SOFT FACET V2 PDF/SVG/JSON output matrix.""" from __future__ import annotations import argparse import json import math import xml.etree.ElementTree as ET from pathlib import Path from pypdf import PdfReader from generate_a3_templates import SURFACE_STYLES def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--preset", default="v60_02") parser.add_argument("--output-root", type=Path, default=Path(__file__).resolve().parents[1] / "output") return parser.parse_args() def require(condition: bool, message: str) -> None: if not condition: raise AssertionError(message) def verify_style(root: Path, preset: str, style: str) -> None: pdf_path = root / "pdf" / f"{preset}_{style}_clay_templates_a3.pdf" data_path = root / "data" / f"{preset}_{style}_clay_templates.json" svg_dir = root / "svg" / style require(pdf_path.is_file(), f"missing PDF: {pdf_path}") require(data_path.is_file(), f"missing JSON: {data_path}") reader = PdfReader(str(pdf_path)) require(len(reader.pages) == 3, f"{style}: expected 3 PDF pages") for page_number, page in enumerate(reader.pages, start=1): width_mm = float(page.mediabox.width) * 25.4 / 72.0 height_mm = float(page.mediabox.height) * 25.4 / 72.0 require(math.isclose(width_mm, 420.0, abs_tol=0.01), f"{style}: page {page_number} width") require(math.isclose(height_mm, 297.0, abs_tol=0.01), f"{style}: page {page_number} height") text = page.extract_text() or "" require(style in text, f"{style}: style label missing on page {page_number}") require("50 x 50" in text, f"{style}: calibration square label missing on page {page_number}") data = json.loads(data_path.read_text(encoding="utf-8")) require(data["model_version"] == "2.0", f"{style}: wrong model version") require(data["surface_style"] == style, f"{style}: JSON style mismatch") require(0 < data["release_scale"] <= 1, f"{style}: invalid release scale") require(data["minimum_release_draft_angle_deg"] >= 5, f"{style}: release angle below production minimum") for field in ( "master_top_radius_mm", "master_bottom_radius_mm", "master_height_mm", "average_meridian_length_mm", "single_piece_outer_radius_mm", "single_piece_inner_radius_mm", "support_max_span_mm", "support_hole_diameter_mm", ): require(data[field] > 0, f"{style}: non-positive {field}") for field in ("gore_top_widths_mm", "gore_bottom_widths_mm", "gore_heights_mm"): require(len(data[field]) == 4, f"{style}: {field} must contain four values") require(all(value > 0 for value in data[field]), f"{style}: invalid {field}") svg_paths = sorted(svg_dir.glob(f"{preset}_{style}_*.svg")) require(len(svg_paths) == 6, f"{style}: expected 6 SVG files, found {len(svg_paths)}") for svg_path in svg_paths: ET.parse(svg_path) def main() -> None: args = parse_args() for style in SURFACE_STYLES: verify_style(args.output_root, args.preset, style) print(f"ok: {style}") print(f"verified {len(SURFACE_STYLES)} styles") if __name__ == "__main__": main()