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.
174 lines
5.3 KiB
Python
174 lines
5.3 KiB
Python
"""DIN 6885 keyway geometry — bore + keyway cutter for shafts and hubs."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from backends.base import AutoCADBackend
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
DIN6885_TABLE: list[tuple[float, float, float, float, float]] = [
|
|
# (bore_min, bore_max, key_width, key_height, depth_in_shaft_t1)
|
|
(6, 8, 2, 2, 1.2),
|
|
(8, 10, 3, 3, 1.8),
|
|
(10, 12, 4, 4, 2.5),
|
|
(12, 17, 5, 5, 3.0),
|
|
(17, 22, 6, 6, 3.5),
|
|
(22, 30, 8, 7, 4.0),
|
|
(30, 38, 10, 8, 5.0),
|
|
(38, 44, 12, 8, 5.0),
|
|
(44, 50, 14, 9, 5.5),
|
|
(50, 58, 16, 10, 6.0),
|
|
(58, 65, 18, 11, 7.0),
|
|
(65, 75, 20, 12, 7.5),
|
|
(75, 85, 22, 14, 9.0),
|
|
(85, 95, 25, 14, 9.0),
|
|
(95, 110, 28, 16, 10.0),
|
|
(110, 130, 32, 18, 11.0),
|
|
]
|
|
|
|
|
|
def keyway_dimensions(bore_diameter: float) -> dict:
|
|
"""Look up DIN 6885 key width/height/depth for a given bore diameter (mm)."""
|
|
if bore_diameter < DIN6885_TABLE[0][0]:
|
|
raise ValueError(
|
|
f"bore_diameter {bore_diameter} mm is below DIN 6885 minimum "
|
|
f"({DIN6885_TABLE[0][0]} mm)."
|
|
)
|
|
if bore_diameter > DIN6885_TABLE[-1][1]:
|
|
raise ValueError(
|
|
f"bore_diameter {bore_diameter} mm exceeds DIN 6885 maximum "
|
|
f"({DIN6885_TABLE[-1][1]} mm)."
|
|
)
|
|
for b_min, b_max, width, height, t1 in DIN6885_TABLE:
|
|
if b_min < bore_diameter <= b_max or (bore_diameter == b_min and b_min == DIN6885_TABLE[0][0]):
|
|
return {
|
|
"width": float(width),
|
|
"height": float(height),
|
|
"depth_shaft": float(t1),
|
|
"depth_hub": float(height) - float(t1),
|
|
}
|
|
raise ValueError(f"No DIN 6885 row matched bore_diameter={bore_diameter}.")
|
|
|
|
|
|
async def draw_keyed_bore(
|
|
backend: "AutoCADBackend",
|
|
*,
|
|
center: tuple[float, float],
|
|
bore_diameter: float,
|
|
keyway_width: float | None = None,
|
|
keyway_depth: float | None = None,
|
|
layer: str = "GEOMETRY",
|
|
center_layer: str = "CENTER",
|
|
) -> dict:
|
|
"""Draw bore + upward-opening keyway notch + horizontal/vertical centerlines."""
|
|
cx, cy = float(center[0]), float(center[1])
|
|
r = float(bore_diameter) / 2.0
|
|
|
|
if keyway_width is None or keyway_depth is None:
|
|
dims = keyway_dimensions(bore_diameter)
|
|
if keyway_width is None:
|
|
keyway_width = dims["width"]
|
|
if keyway_depth is None:
|
|
keyway_depth = dims["depth_hub"]
|
|
|
|
bore = await backend.entity_create_circle(cx, cy, r, layer=layer)
|
|
|
|
half_w = float(keyway_width) / 2.0
|
|
depth = float(keyway_depth)
|
|
bore_top_y = cy + r
|
|
notch_top_y = bore_top_y + depth
|
|
left_x = cx - half_w
|
|
right_x = cx + half_w
|
|
|
|
points = [
|
|
[left_x, bore_top_y],
|
|
[left_x, notch_top_y],
|
|
[right_x, notch_top_y],
|
|
[right_x, bore_top_y],
|
|
]
|
|
keyway_poly = await backend.entity_create_polyline(points, closed=False, layer=layer)
|
|
|
|
overshoot = max(r * 0.2, 5.0)
|
|
horiz = await backend.entity_create_line(
|
|
cx - r - overshoot, cy, cx + r + overshoot, cy, layer=center_layer,
|
|
)
|
|
vert = await backend.entity_create_line(
|
|
cx, cy - r - overshoot, cx, cy + r + depth + overshoot, layer=center_layer,
|
|
)
|
|
|
|
return {
|
|
"bore": bore.handle,
|
|
"keyway_polyline": keyway_poly.handle,
|
|
"centerline_h": horiz.handle,
|
|
"centerline_v": vert.handle,
|
|
}
|
|
|
|
|
|
async def draw_keyway_section(
|
|
backend: "AutoCADBackend",
|
|
*,
|
|
center: tuple[float, float],
|
|
bore_diameter: float,
|
|
face_width: float,
|
|
keyway_width: float | None = None,
|
|
keyway_depth: float | None = None,
|
|
layer: str = "GEOMETRY",
|
|
) -> dict:
|
|
"""Side cross-section: bore as two horizontal lines with rectangular keyway notch on top."""
|
|
cx, cy = float(center[0]), float(center[1])
|
|
r = float(bore_diameter) / 2.0
|
|
half_face = float(face_width) / 2.0
|
|
|
|
if keyway_width is None or keyway_depth is None:
|
|
dims = keyway_dimensions(bore_diameter)
|
|
if keyway_width is None:
|
|
keyway_width = dims["width"]
|
|
if keyway_depth is None:
|
|
keyway_depth = dims["depth_hub"]
|
|
|
|
half_kw = float(keyway_width) / 2.0
|
|
depth = float(keyway_depth)
|
|
top_y = cy + r
|
|
bot_y = cy - r
|
|
notch_top_y = top_y + depth
|
|
x_left = cx - half_face
|
|
x_right = cx + half_face
|
|
notch_left = cx - half_kw
|
|
notch_right = cx + half_kw
|
|
|
|
bore_top = await backend.entity_create_line(
|
|
x_left, top_y, x_right, top_y, layer=layer,
|
|
)
|
|
bore_bottom = await backend.entity_create_line(
|
|
x_left, bot_y, x_right, bot_y, layer=layer,
|
|
)
|
|
|
|
notch_bottom = await backend.entity_create_line(
|
|
notch_left, top_y, notch_right, top_y, layer=layer,
|
|
)
|
|
notch_left_wall = await backend.entity_create_line(
|
|
notch_left, top_y, notch_left, notch_top_y, layer=layer,
|
|
)
|
|
notch_top = await backend.entity_create_line(
|
|
notch_left, notch_top_y, notch_right, notch_top_y, layer=layer,
|
|
)
|
|
notch_right_wall = await backend.entity_create_line(
|
|
notch_right, notch_top_y, notch_right, top_y, layer=layer,
|
|
)
|
|
|
|
return {
|
|
"bore_top": bore_top.handle,
|
|
"bore_bottom": bore_bottom.handle,
|
|
"keyway": [
|
|
notch_bottom.handle,
|
|
notch_left_wall.handle,
|
|
notch_top.handle,
|
|
notch_right_wall.handle,
|
|
],
|
|
}
|