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
This commit is contained in:
co-authored by
Claude Opus 5
parent
86c5d6ad65
commit
a76d9e08a3
@@ -0,0 +1,187 @@
|
||||
"""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"
|
||||
)
|
||||
Reference in New Issue
Block a user