Documents.Add(<file>) accepts only a genuine DWT. Measured on AutoCAD 2026: Add(templates/iso_a3_pid.dxf) returned the default acadiso drawing (layers ['0'], Layout1/Layout2) exactly as Add() and Add(<missing.dwt>) did, so drawing_new(template=<bundled name>) reported ok:true with source 'bundled_dxf' over a blank document, and the explicit .dxf path route was just as blank. ComBackend.drawing_new now converts any non-.dwt template through AutoCAD itself (Documents.Open read-only -> SaveAs ac2018_Template, older constants tried next -> Close(False)), caches the .dwt in the temp folder keyed on path/size/mtime, hands Add the .dwt, and refuses a missing template file instead of letting Add default silently. The tool result carries template.dwt / template.dwt_cached. Re-measured end to end through server.drawing_new on the real ComBackend: ansi_b_mech -> 11 layers, tab B, DIMTAD 0 / DIMTIH 1 / DIMEXO 1.5; iso_a3_pid -> 17 layers, tab A3, second call reuses the cached .dwt (0.8 s conversion once). Fake-ActiveX tests pin the call shape and that the conversion document never stays open. Headlessly the build's fallback wrote spec 4.1 only as header DIM variables, and the renderer folds six of them, so DIMTAD/TIH/TOH/EXE/EXO/GAP/LUNIT/LWD were a no-op: ansi_b_mech dimensioned with ISO text placement while system_get_variable and AutoCAD said ANSI. The fallback now also stores each value on the Standard dimstyle; the five DXFs are rebuilt (--check passes) and a test reads the effective values off a DIMENSION drawn from each template and asserts header and style agree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
191 lines
6.4 KiB
Python
191 lines
6.4 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 — the live
|
|
engine then converts that DXF into a real ``.dwt`` itself before
|
|
``Documents.Add`` (``ComBackend.drawing_new``), because AutoCAD ignores a
|
|
DXF handed to ``Add`` and silently returns the default drawing. 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"
|
|
)
|