Files
autocad-mcp-pro/tests/test_engineering_titleblock.py
T
U-C4N 88a9005a6a Add deterministic engineering CAD layer (gear, keyway, titleblock, validator)
Introduces an engineering/ package that lets MCP clients produce production-
quality technical drawings without LLM-improvised geometry. Adds 7 new MCP
tools, an 8-step pre-completion validator, and an automatic linetype + layer
bootstrap on drawing_new so CENTER/HIDDEN/PHANTOM and standard mechanical
layers (GEOMETRY, DIM, HATCH, TEXT, TITLEBLOCK, ...) are always available.

- engineering/gear.py: pure-Python involute math, 40+ points/flank, helical
  + spur front views, deterministic section A-A (no helix lines in section).
- engineering/keyway.py: DIN 6885 keyway table + parametric keyed bore /
  side-section drawers.
- engineering/titleblock.py: ISO 7200 A3 (420x297 mm) title block; title
  text is written verbatim to the title cell.
- engineering/validator.py: DrawingValidator with 8 checks (real DIMENSION
  entities, no hidden orphans, linetypes loaded, bore + keyway in both
  views when expected, title consistency e.g. forbid 'HELICAL SPUR GEAR'
  when helix_angle truthy, saved-to-disk + file-on-disk, screenshot
  exported, dwg path returned).
- backends/ezdxf_backend.py: AutoCAD-only linetype fallback (HIDDEN,
  BORDER families) so ezdxf doc gets correct patterns when linetype_load
  is called for names ezdxf.tools.standards does not ship.
- backends/base.py: concrete helpers set_layer_active / ensure_linetypes.
- server.py: Section 12 with 7 engineering tools, drawing_new(bootstrap=
  True), and an ENGINEERING WORKFLOW block in the MCP instructions string.
- 30 new tests covering gear math, DIN6885 lookup, ISO A3 layout, title
  verbatim, and every validator check. Tool count 89 -> 96.
2026-04-28 12:24:08 +03:00

105 lines
3.8 KiB
Python

"""Tests for engineering.titleblock — ISO 7200 A3 layout."""
import pytest
from engineering.titleblock import TitleBlockMetadata, apply_iso_a3_titleblock
pytestmark = pytest.mark.asyncio
def _meta(**overrides) -> TitleBlockMetadata:
base = dict(
title="HELICAL GEAR",
drawing_no="AM-2026-001",
part_no="GR-24T-M3",
material="C45",
scale="1:1",
units="mm",
drawn_by="Umutcan",
checked_by="QA",
date="2026-04-28",
sheet="1/1",
revision="A",
company="Anka-Makine",
)
base.update(overrides)
return TitleBlockMetadata(**base)
async def test_titleblock_outer_border_dimensions(backend):
"""Outer border bbox must equal A3 sheet (420 x 297 mm)."""
result = await apply_iso_a3_titleblock(backend, metadata=_meta())
bbox = result["bbox"]
width = bbox["max"][0] - bbox["min"][0]
height = bbox["max"][1] - bbox["min"][1]
assert width == pytest.approx(420.0)
assert height == pytest.approx(297.0)
async def test_title_text_exact_match(backend):
"""Title text in the drawing must equal metadata.title verbatim."""
result = await apply_iso_a3_titleblock(
backend, metadata=_meta(title="HELICAL GEAR"),
)
title_ent = await backend.entity_get(result["title_text"])
assert title_ent.type == "TEXT"
assert title_ent.properties["text"] == "HELICAL GEAR"
async def test_titleblock_creates_text_for_each_metadata_field(backend):
"""value_texts must contain a handle for every metadata field."""
result = await apply_iso_a3_titleblock(backend, metadata=_meta())
expected_keys = {
"drawing_no", "revision", "sheet",
"part_no", "material", "scale",
"drawn_by", "checked_by", "date", "company",
}
assert set(result["value_texts"].keys()) == expected_keys
# All handles must resolve to TEXT entities
for key, handle in result["value_texts"].items():
ent = await backend.entity_get(handle)
assert ent.type == "TEXT", f"{key} expected TEXT, got {ent.type}"
async def test_titleblock_origin_offset(backend):
"""Passing origin=(100,50) must shift the bbox by that amount."""
result = await apply_iso_a3_titleblock(
backend, metadata=_meta(), origin=(100.0, 50.0),
)
bbox = result["bbox"]
assert bbox["min"][0] == pytest.approx(100.0)
assert bbox["min"][1] == pytest.approx(50.0)
assert bbox["max"][0] == pytest.approx(520.0)
assert bbox["max"][1] == pytest.approx(347.0)
async def test_titleblock_borders_on_titleblock_layer(backend):
"""Outer + inner borders + grid lines must be on TITLEBLOCK layer."""
result = await apply_iso_a3_titleblock(backend, metadata=_meta())
outer = await backend.entity_get(result["outer_border"])
inner = await backend.entity_get(result["inner_border"])
assert outer.layer == "TITLEBLOCK"
assert inner.layer == "TITLEBLOCK"
for handle in result["titleblock_lines"]:
ent = await backend.entity_get(handle)
assert ent.layer == "TITLEBLOCK"
async def test_titleblock_text_on_text_layer(backend):
"""Title and all value texts must be on TEXT layer."""
result = await apply_iso_a3_titleblock(backend, metadata=_meta())
title = await backend.entity_get(result["title_text"])
assert title.layer == "TEXT"
for handle in result["value_texts"].values():
ent = await backend.entity_get(handle)
assert ent.layer == "TEXT"
async def test_titleblock_metadata_in_result(backend):
"""Returned metadata dict must round-trip the input metadata."""
md = _meta(title="SPUR GEAR", drawing_no="AM-2026-002")
result = await apply_iso_a3_titleblock(backend, metadata=md)
assert result["metadata"]["title"] == "SPUR GEAR"
assert result["metadata"]["drawing_no"] == "AM-2026-002"
assert result["metadata"]["company"] == "Anka-Makine"