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.
This commit is contained in:
@@ -358,6 +358,15 @@ class AutoCADBackend(ABC):
|
||||
@abstractmethod
|
||||
async def layer_show(self, name: str) -> dict: ...
|
||||
|
||||
# ── linetype management ──────────────────────────────────────────────────
|
||||
@abstractmethod
|
||||
async def linetype_list(self) -> list[str]: ...
|
||||
|
||||
@abstractmethod
|
||||
async def linetype_load(
|
||||
self, name: str, file: str | None = None,
|
||||
) -> dict: ...
|
||||
|
||||
# ── block operations ─────────────────────────────────────────────────────
|
||||
@abstractmethod
|
||||
async def block_list(self) -> list[BlockInfo]: ...
|
||||
@@ -449,6 +458,33 @@ class AutoCADBackend(ABC):
|
||||
@abstractmethod
|
||||
async def system_run_lisp(self, expression: str) -> dict: ...
|
||||
|
||||
# ── concrete helpers (use existing primitives) ──────────────────────────
|
||||
async def set_layer_active(self, name: str) -> None:
|
||||
"""Convenience: alias for layer_set_current. Concrete; backends need not override."""
|
||||
await self.layer_set_current(name)
|
||||
|
||||
async def ensure_linetypes(
|
||||
self, names: list[str], file: str | None = None,
|
||||
) -> dict[str, str]:
|
||||
"""Idempotently load each linetype in `names`. Returns {name: status}.
|
||||
Status is one of: 'already_loaded', 'loaded', 'failed: <msg>'.
|
||||
"""
|
||||
try:
|
||||
current = {ln.lower() for ln in await self.linetype_list()}
|
||||
except Exception:
|
||||
current = set()
|
||||
results: dict[str, str] = {}
|
||||
for n in names:
|
||||
if n.lower() in current:
|
||||
results[n] = "already_loaded"
|
||||
continue
|
||||
try:
|
||||
await self.linetype_load(n, file=file)
|
||||
results[n] = "loaded"
|
||||
except Exception as exc:
|
||||
results[n] = f"failed: {exc}"
|
||||
return results
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
|
||||
Reference in New Issue
Block a user