Files
autocad-mcp-pro/engineering/standards/templates.py
T
Umut CANandClaude Opus 5 a76d9e08a3 feat(templates): five bundled templates built by the server's own tools; drawing_new(template=name)
SECTION 19 grows to five tools with drawing_template_list; drawing_new takes a
bundled name (iso_a3_mech, iso_a1_arch, iso_a3_pid, ansi_b_mech, ansi_d_arch)
or a .dwt/.dxf path and reports template: {name|path, path, source}.

scripts/build_templates.py builds templates/*.dxf through EzdxfBackend
(layer set, header DIM variables until group S's styles land, drawing_settings,
sheet tab, ISO 5457 frame on A3, page_setup_apply) and --check pins them
reproducible. One drift the plan's same-process measurement could not see is
fixed at the source rather than masked: ezdxf orders CLASSES by set iteration,
so two processes disagreed on LAYOUT / ACDBPLACEHOLDER; the section is re-keyed
(required list first, then by name) before the save.

The root .gitignore ignores *.dxf as generated output; templates/*.dxf is
un-ignored there, since the bundled set is the one DXF that is committed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-21 22:03:41 +03:00

188 lines
6.2 KiB
Python

"""The five bundled drawing templates: what each contains, where its files live.
Built by ``scripts/build_templates.py`` through the server's own backend
(layers, styles, settings, the ISO 5457 A3 frame where the sheet is A3, page
setup) and saved as DXF; the ``.dwt`` twins are produced on a live AutoCAD by
``scripts/smoke_settings_com.py --build-dwt`` and committed once.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass, field
from pathlib import Path
__all__ = [
"TEMPLATES_DIR",
"TEMPLATE_CATALOG",
"TemplateSpec",
"resolve_template",
"template_rows",
]
#: ``<repo>/templates`` — shipped in the wheel (pyproject ``only-include``) and the Docker image.
TEMPLATES_DIR = Path(__file__).resolve().parents[2] / "templates"
_ISO_SETTINGS = {
"units": "mm",
"ltscale": 1.0,
"dimscale": 1.0,
"linear_precision": 2,
"angular_precision": 0,
"decimal_separator": ",",
}
_ANSI_SETTINGS = {**_ISO_SETTINGS, "decimal_separator": "."}
@dataclass(frozen=True)
class TemplateSpec:
name: str
standard: str # "ISO" | "ANSI"
sheet: str # human label, e.g. "A3 landscape"
paper: str # engineering.standards.papers.PAPER_SIZES key
orientation: str
layout: str # the paper-space tab's name
layer_set: str # engineering.layers.LAYER_SET_REGISTRY key
dimstyle_preset: str # group S preset id: "iso-25" | "ansi"
dimstyle: str # style name in the drawing
textstyle: str # "ISOCP" | "ROMANS"
scale: str # page setup scale label
title_block: bool
settings: dict = field(default_factory=dict) # drawing_settings facade keys
description: str = ""
TEMPLATE_CATALOG: dict[str, TemplateSpec] = {
spec.name: spec
for spec in (
TemplateSpec(
name="iso_a3_mech",
standard="ISO",
sheet="A3 landscape",
paper="ISO_A3",
orientation="landscape",
layout="A3",
layer_set="mech",
dimstyle_preset="iso-25",
dimstyle="ISO-25",
textstyle="ISOCP",
scale="1:1",
title_block=True,
settings=dict(_ISO_SETTINGS),
description="ISO A3 mechanical part drawing",
),
TemplateSpec(
name="iso_a1_arch",
standard="ISO",
sheet="A1 landscape",
paper="ISO_A1",
orientation="landscape",
layout="A1",
layer_set="iso13567",
dimstyle_preset="iso-25",
dimstyle="ISO-25",
textstyle="ISOCP",
scale="fit",
title_block=False,
settings={**_ISO_SETTINGS, "annotation_scale": "1:50"},
description="ISO A1 architectural sheet, ISO 13567 layers, 1:50 annotation",
),
TemplateSpec(
name="iso_a3_pid",
standard="ISO",
sheet="A3 landscape",
paper="ISO_A3",
orientation="landscape",
layout="A3",
layer_set="pid",
dimstyle_preset="iso-25",
dimstyle="ISO-25",
textstyle="ISOCP",
scale="1:1",
title_block=True,
settings=dict(_ISO_SETTINGS),
description="ISO A3 P&ID sheet",
),
TemplateSpec(
name="ansi_b_mech",
standard="ANSI",
sheet="ANSI B landscape",
paper="ANSI_B",
orientation="landscape",
layout="B",
layer_set="mech",
dimstyle_preset="ansi",
dimstyle="ANSI",
textstyle="ROMANS",
scale="1:1",
title_block=False,
settings=dict(_ANSI_SETTINGS),
description="ANSI B mechanical part drawing (metric)",
),
TemplateSpec(
name="ansi_d_arch",
standard="ANSI",
sheet="ANSI D landscape",
paper="ANSI_D",
orientation="landscape",
layout="D",
layer_set="iso13567",
dimstyle_preset="ansi",
dimstyle="ANSI",
textstyle="ROMANS",
scale="fit",
title_block=False,
settings=dict(_ANSI_SETTINGS),
description="ANSI D architectural sheet (metric)",
),
)
}
def _file(name: str, suffix: str) -> dict:
path = TEMPLATES_DIR / f"{name}{suffix}"
return {"path": str(path), "present": path.is_file()}
def template_rows() -> list[dict]:
"""Catalogue rows for ``drawing_template_list``: the spec plus its files."""
rows = []
for spec in TEMPLATE_CATALOG.values():
row = asdict(spec)
row["files"] = {"dxf": _file(spec.name, ".dxf"), "dwt": _file(spec.name, ".dwt")}
rows.append(row)
return rows
def resolve_template(name_or_path: str, engine: str) -> tuple[str, str]:
"""``(path, source)`` for a catalogue name or a template file path.
A catalogue name resolves to ``templates/<name>.dxf`` (headless) or
``templates/<name>.dwt`` (live), falling back to the DXF with
``source="bundled_dxf"`` when the ``.dwt`` twin is missing. Anything with a
path separator or a ``.dwt``/``.dxf`` suffix is a path (``source="path"``,
not checked here). A bare word that is not in the catalogue is refused
with the catalogue names.
"""
raw = str(name_or_path or "").strip()
if not raw:
raise ValueError("template: pass a bundled template name or a path to a .dwt/.dxf file")
key = raw.lower()
if key in TEMPLATE_CATALOG:
dxf = TEMPLATES_DIR / f"{key}.dxf"
dwt = TEMPLATES_DIR / f"{key}.dwt"
if engine == "com" and dwt.is_file():
return str(dwt), "bundled"
if dxf.is_file():
return str(dxf), "bundled" if engine != "com" else "bundled_dxf"
raise ValueError(
f"template {key!r} is in the catalogue but {dxf} is missing — run "
"`uv run --frozen python scripts/build_templates.py`"
)
looks_like_path = ("/" in raw or "\\" in raw) or Path(raw).suffix.lower() in (".dwt", ".dxf")
if looks_like_path:
return raw, "path"
raise ValueError(
f"unknown template {raw!r}. Bundled templates: {', '.join(TEMPLATE_CATALOG)}; "
"or pass a path to a .dwt/.dxf file"
)