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.
83 lines
2.4 KiB
Python
83 lines
2.4 KiB
Python
"""Generic cutting-plane and section-hatch helpers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from backends.base import AutoCADBackend
|
|
|
|
|
|
async def cutting_plane_line(
|
|
backend: "AutoCADBackend",
|
|
*,
|
|
p1: tuple[float, float],
|
|
p2: tuple[float, float],
|
|
label: str = "A",
|
|
layer: str = "PHANTOM",
|
|
) -> dict:
|
|
"""Draw a section line, arrowheads at each end, and 'X-X' labels."""
|
|
x1, y1 = float(p1[0]), float(p1[1])
|
|
x2, y2 = float(p2[0]), float(p2[1])
|
|
|
|
line = await backend.entity_create_line(x1, y1, x2, y2, layer=layer)
|
|
|
|
dx, dy = x2 - x1, y2 - y1
|
|
length = math.hypot(dx, dy) or 1.0
|
|
ux, uy = dx / length, dy / length
|
|
nx, ny = -uy, ux
|
|
|
|
arrow_len = max(length * 0.05, 4.0)
|
|
arrow_w = arrow_len * 0.4
|
|
arrows: list[str] = []
|
|
|
|
for tip_x, tip_y, dir_x, dir_y in (
|
|
(x1, y1, -ux, -uy),
|
|
(x2, y2, ux, uy),
|
|
):
|
|
bx = tip_x + dir_x * arrow_len
|
|
by = tip_y + dir_y * arrow_len
|
|
wing1_x = bx + nx * arrow_w
|
|
wing1_y = by + ny * arrow_w
|
|
wing2_x = bx - nx * arrow_w
|
|
wing2_y = by - ny * arrow_w
|
|
a1 = await backend.entity_create_line(tip_x, tip_y, wing1_x, wing1_y, layer=layer)
|
|
a2 = await backend.entity_create_line(tip_x, tip_y, wing2_x, wing2_y, layer=layer)
|
|
arrows.extend([a1.handle, a2.handle])
|
|
|
|
text = f"{label}-{label}"
|
|
text_h = max(arrow_len, 3.5)
|
|
offset = arrow_len * 1.5
|
|
labels: list[str] = []
|
|
for tip_x, tip_y, dir_x, dir_y in (
|
|
(x1, y1, -ux, -uy),
|
|
(x2, y2, ux, uy),
|
|
):
|
|
tx = tip_x + dir_x * offset + nx * offset * 0.5
|
|
ty = tip_y + dir_y * offset + ny * offset * 0.5
|
|
t = await backend.entity_create_text(text, tx, ty, height=text_h, layer="TEXT")
|
|
labels.append(t.handle)
|
|
|
|
return {"line": line.handle, "arrows": arrows, "labels": labels}
|
|
|
|
|
|
async def apply_section_hatch(
|
|
backend: "AutoCADBackend",
|
|
*,
|
|
boundary_points: list[list[float]],
|
|
pattern: str = "ANSI31",
|
|
angle: float = 45.0,
|
|
scale: float = 1.0,
|
|
layer: str = "HATCH",
|
|
) -> str:
|
|
"""Wrapper around backend.entity_create_hatch returning the new handle."""
|
|
info = await backend.entity_create_hatch(
|
|
pattern=pattern,
|
|
boundary_points=boundary_points,
|
|
scale=scale,
|
|
angle=angle,
|
|
layer=layer,
|
|
)
|
|
return info.handle
|