Files
autocad-mcp-pro/engineering/layers.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

75 lines
2.7 KiB
Python

"""Standard engineering layer + linetype scaffold for production drawings."""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from backends.base import AutoCADBackend
log = logging.getLogger(__name__)
ENGINEERING_LAYERS: list[tuple[str, int, str, float, str]] = [
# (name, color, linetype, lineweight, description)
("0", 7, "Continuous", 0.25, "default"),
("GEOMETRY", 7, "Continuous", 0.50, "visible solid edges"),
("HIDDEN", 3, "HIDDEN", 0.25, "hidden edges"),
("CENTER", 1, "CENTER", 0.18, "centerlines"),
("PHANTOM", 6, "PHANTOM", 0.18, "section / cutting planes"),
("DIM", 2, "Continuous", 0.18, "dimensions"),
("TEXT", 7, "Continuous", 0.25, "annotations"),
("HATCH", 8, "Continuous", 0.13, "section hatching"),
("TITLEBLOCK", 7, "Continuous", 0.50, "drawing border + title"),
("CONSTRUCTION", 250, "Continuous", 0.05, "scratch / construction"),
]
STANDARD_LINETYPES: list[str] = ["CENTER", "HIDDEN", "PHANTOM", "DASHED", "DASHDOT"]
async def ensure_standard_linetypes(backend: "AutoCADBackend") -> dict[str, str]:
"""Idempotently load STANDARD_LINETYPES via backend.linetype_load."""
try:
existing = {ln.lower() for ln in await backend.linetype_list()}
except Exception as exc:
log.warning("linetype_list failed; assuming none loaded: %s", exc)
existing = set()
results: dict[str, str] = {}
for name in STANDARD_LINETYPES:
if name.lower() in existing:
results[name] = "already_loaded"
continue
try:
await backend.linetype_load(name)
results[name] = "loaded"
except Exception as exc:
results[name] = f"failed: {exc}"
return results
async def ensure_engineering_layers(backend: "AutoCADBackend") -> dict[str, str]:
"""Idempotently create every layer in ENGINEERING_LAYERS via backend.layer_create."""
await ensure_standard_linetypes(backend)
try:
existing = {lyr.name.lower() for lyr in await backend.layer_list()}
except Exception as exc:
log.warning("layer_list failed; assuming empty: %s", exc)
existing = set()
results: dict[str, str] = {}
for name, color, linetype, lineweight, _desc in ENGINEERING_LAYERS:
if name.lower() in existing:
results[name] = "exists"
continue
try:
await backend.layer_create(
name=name, color=color, linetype=linetype, lineweight=lineweight,
)
results[name] = "created"
except Exception as exc:
results[name] = f"failed: {exc}"
return results