Files
pdf2cad/tests/test_assembler.py
2026-03-03 21:24:02 +00:00

151 lines
5.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Tests for part geometry assembly."""
import json
from dataclasses import FrozenInstanceError
import pymupdf
import pytest
from pdf2imos.extract.geometry import extract_geometry
from pdf2imos.extract.text import extract_text
from pdf2imos.interpret.line_classifier import classify_lines
from pdf2imos.interpret.title_block import detect_title_block, extract_title_block_info
from pdf2imos.interpret.view_segmenter import segment_views
from pdf2imos.models import (
DimensionAnnotation,
DimensionDirection,
PageExtraction,
PartGeometry,
ViewType,
)
from pdf2imos.parse.dimensions import extract_dimensions
from pdf2imos.reconstruct.assembler import assemble_part_geometry
def make_full_pipeline(pdf_path):
"""Run full pipeline up to assembly."""
doc = pymupdf.open(str(pdf_path))
page = doc[0]
page_height = page.rect.height
geo = extract_geometry(page)
texts = extract_text(page)
extraction = PageExtraction(
paths=geo.paths,
texts=tuple(texts),
page_width=geo.page_width,
page_height=page_height,
)
title_rect, filtered = detect_title_block(extraction)
title_info = extract_title_block_info(extraction, title_rect) if title_rect else {}
views = segment_views(filtered)
# Extract dimensions per view
dims_by_view: dict[ViewType, list[DimensionAnnotation]] = {}
for view in views:
classified = classify_lines(list(view.paths))
view_dims = extract_dimensions(view, classified, page_height)
dims_by_view[view.view_type] = view_dims
part_name = title_info.get("part_name", "unknown")
return views, dims_by_view, part_name
class TestAssemblePartGeometry:
def test_returns_part_geometry_or_none(self, simple_panel_pdf):
views, dims_by_view, part_name = make_full_pipeline(simple_panel_pdf)
result = assemble_part_geometry(views, dims_by_view, part_name)
assert result is None or isinstance(result, PartGeometry)
def test_panel_assembles_correctly(self, simple_panel_pdf):
"""simple_panel.pdf should assemble to ~600×720×18mm."""
views, dims_by_view, part_name = make_full_pipeline(simple_panel_pdf)
result = assemble_part_geometry(views, dims_by_view, part_name)
if result is None:
pytest.skip("Assembly returned None — insufficient dimensions")
# Width: ~600mm ±5mm (relaxed tolerance for fixture PDF)
assert 580 <= result.width_mm <= 650, f"Width out of range: {result.width_mm}"
# Height: ~720mm ±5mm
assert 700 <= result.height_mm <= 750, f"Height out of range: {result.height_mm}"
# Depth: ~18mm ±5mm
assert 10 <= result.depth_mm <= 30, f"Depth out of range: {result.depth_mm}"
def test_result_is_frozen_dataclass(self, simple_panel_pdf):
views, dims_by_view, part_name = make_full_pipeline(simple_panel_pdf)
result = assemble_part_geometry(views, dims_by_view, part_name)
if result is None:
pytest.skip("Assembly returned None")
try:
result.width_mm = 0 # type: ignore[misc]
msg = "Should be frozen"
raise AssertionError(msg)
except (FrozenInstanceError, AttributeError):
pass
def test_origin_is_zero(self, simple_panel_pdf):
views, dims_by_view, part_name = make_full_pipeline(simple_panel_pdf)
result = assemble_part_geometry(views, dims_by_view, part_name)
if result is None:
pytest.skip("Assembly returned None")
assert result.origin == (0.0, 0.0, 0.0)
def test_to_dict_serializable(self, simple_panel_pdf):
views, dims_by_view, part_name = make_full_pipeline(simple_panel_pdf)
result = assemble_part_geometry(views, dims_by_view, part_name)
if result is None:
pytest.skip("Assembly returned None")
d = result.to_dict()
json.dumps(d) # Should not raise
def test_empty_dims_returns_none(self):
"""No dimensions → returns None."""
result = assemble_part_geometry([], {})
assert result is None
def test_cabinet_assembles(self, cabinet_basic_pdf):
"""cabinet_basic.pdf (600×720×400mm) assembles successfully."""
views, dims_by_view, part_name = make_full_pipeline(cabinet_basic_pdf)
result = assemble_part_geometry(views, dims_by_view, part_name)
if result is None:
pytest.skip("Assembly returned None for cabinet")
# Cabinet is 600×720×400mm — width should be 600
assert 580 <= result.width_mm <= 650, f"Cabinet width: {result.width_mm}"
def test_uses_front_view_for_width_and_height(self):
"""Front view horizontal → width, vertical → height."""
front_dims = [
DimensionAnnotation(
value_mm=600,
direction=DimensionDirection.HORIZONTAL,
dim_line_start=(0, 0),
dim_line_end=(600, 0),
text_bbox=(0, 0, 0, 0),
),
DimensionAnnotation(
value_mm=720,
direction=DimensionDirection.VERTICAL,
dim_line_start=(0, 0),
dim_line_end=(0, 720),
text_bbox=(0, 0, 0, 0),
),
]
side_dims = [
DimensionAnnotation(
value_mm=18,
direction=DimensionDirection.HORIZONTAL,
dim_line_start=(0, 0),
dim_line_end=(18, 0),
text_bbox=(0, 0, 0, 0),
),
]
dims = {ViewType.FRONT: front_dims, ViewType.SIDE: side_dims}
result = assemble_part_geometry([], dims, "test_panel")
assert result is not None
assert result.width_mm == pytest.approx(600)
assert result.height_mm == pytest.approx(720)
assert result.depth_mm == pytest.approx(18)