Add AutoCAD MCP premium skill and backend APIs
Introduce the autocad-mcp-premium skill with workflow docs, examples, and references (.claude/skills/autocad-mcp-premium/*). Add PlanSpec/critique plumbing and engineering helpers (engineering/plan_spec.py, engineering/critique.py) plus tests and a smoke script. Extend backend API (backends/base.py) with corner operations and premium meta-tools (trim/extend/fillet/chamfer, drawing_plan, drawing_critique, point_from_snap, construction_xline/clear, drawing_apply_iso_layers, dimension_auto, entity_select_smart) and implement COM backend corner ops (backends/com_backend.py) with placeholders for meta-tools. Update CLAUDE.md to document the premium drawing rules and standard workflow. Misc: add examples, references, and tests for premium features, and minor server/backend adjustments.
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
---
|
||||
name: autocad-mcp-premium
|
||||
description: Use when working with AutoCAD MCP Pro tools and the user asks for technical drawings, P&ID, mechanical engineering output, ISO-conformant drawings, premium-quality CAD, or mentions trim/extend/fillet/chamfer, layer discipline, or "premium drawing". Apply the plan→draw→critique workflow strictly.
|
||||
---
|
||||
|
||||
# AutoCAD MCP Premium Drafting Skill
|
||||
|
||||
Premium drafting discipline for the AutoCAD MCP Pro server. The goal: ISO-conformant
|
||||
output that a real draftsman would sign. The path: a strict workflow + the corner
|
||||
ops + meta-tool layer that ship with the server.
|
||||
|
||||
## When this skill applies
|
||||
|
||||
- The user asks for a **technical drawing**, **P&ID**, **mechanical part**, or
|
||||
any drawing intended for production / fabrication / approval.
|
||||
- The user mentions **TRIM, EXTEND, FILLET, CHAMFER**, **OSNAP**, **ISO 128/129/
|
||||
13567**, **layer discipline**, or **premium drawing**.
|
||||
- You are about to call `entity_create_*` for a deliverable drawing — **stop and
|
||||
apply the workflow below first**.
|
||||
|
||||
## The 9-step Premium Workflow (non-negotiable)
|
||||
|
||||
```text
|
||||
1. drawing_plan(intent, sheet_size, scale) # commit intent BEFORE drawing
|
||||
2. drawing_apply_iso_layers("mech" | "pid" | "iso13567")
|
||||
3. construction_xline(...) # scaffolding (optional)
|
||||
4. entity_create_line / circle / arc / ... # geometry on engineering layers
|
||||
5. entity_trim / entity_fillet / entity_chamfer # close corners explicitly
|
||||
6. handles = entity_select_smart({type, layer, ...}) # don't memorise handles
|
||||
7. dimension_auto(handles, style="chain"|"baseline"|"ordinate")
|
||||
8. issues = drawing_critique(focus=None) # must be []
|
||||
9. construction_clear()
|
||||
10. drawing_finalize(save_path=..., screenshot_path=...)
|
||||
```
|
||||
|
||||
If any of these is skipped, the drawing is **not premium** — it's a sketch.
|
||||
|
||||
## The 8 Non-Negotiable Rules (mirror of CLAUDE.md)
|
||||
|
||||
1. **Plan before draw** — `drawing_plan` first; PlanSpec is the contract.
|
||||
2. **No coordinate guessing** — `point_from_snap(handle, snap, ref_x, ref_y)`.
|
||||
3. **Layer discipline** — engineering layers only; CONSTRUCTION for scaffold.
|
||||
4. **Lineweights are ISO 128** — bootstrap via `drawing_apply_iso_layers`.
|
||||
5. **Corners are explicit** — `entity_trim` / `entity_fillet` / `entity_chamfer`.
|
||||
6. **Dimensions via auto** — `dimension_auto`, not loops of `dimension_linear`.
|
||||
7. **Critique then finalize** — `drawing_critique` must return `[]`.
|
||||
8. **Meta-tools over raw** — never reimplement what a meta-tool already does.
|
||||
|
||||
## Tool Inventory (this skill assumes server has these registered)
|
||||
|
||||
| Group | Tools | Count |
|
||||
|-------|-------|-------|
|
||||
| Drawing | `drawing_*` (info/new/open/save/export/finalize/...) | 11 |
|
||||
| Entity Create | `entity_create_line/circle/arc/polyline/text/...` | 12 |
|
||||
| Dimensions | `dimension_linear/aligned/angular/radius/diameter` | 5 |
|
||||
| Entity Modify | `entity_move/copy/rotate/scale/mirror/offset/delete/...` | 10 |
|
||||
| **Corner Ops** ⭐ | `entity_trim` `entity_extend` `entity_fillet` `entity_chamfer` | **4** |
|
||||
| Entity Query | `entity_get/list/delete_many` | 3 |
|
||||
| Layer Mgmt | `layer_*`, `linetype_list/load` | 14 |
|
||||
| Block Ops | `block_*` | 7 |
|
||||
| Analysis | `analysis_*` | 8 |
|
||||
| View | `view_*` | 4 |
|
||||
| Transactions | `transaction_*` | 3 |
|
||||
| System | `system_*` | 6 |
|
||||
| Engineering | `gear_*`, `keyway_*`, `titleblock_*`, `drawing_finalize` | 7 |
|
||||
| **Premium Meta** ⭐ | `drawing_plan` `drawing_critique` `point_from_snap` `construction_xline/clear` `drawing_apply_iso_layers` `dimension_auto` `entity_select_smart` | **8** |
|
||||
|
||||
## Reference Documents (read on demand)
|
||||
|
||||
- [`references/corner-ops.md`](references/corner-ops.md) — trim/extend/fillet/chamfer
|
||||
semantics, decision tree (when to use which), corner-case table.
|
||||
- [`references/meta-planning.md`](references/meta-planning.md) — `drawing_plan`,
|
||||
`drawing_critique`, `point_from_snap` deep dive + PlanSpec / Issue schema.
|
||||
- [`references/iso-standards.md`](references/iso-standards.md) — ISO 128 line
|
||||
widths, ISO 13567 layer naming, ISO 129 dimension rules — printable tables.
|
||||
- [`references/premium-workflow.md`](references/premium-workflow.md) — full
|
||||
worked examples of the 9-step workflow with code blocks.
|
||||
|
||||
## Examples
|
||||
|
||||
- [`examples/premium-shaft.md`](examples/premium-shaft.md) — Shaft with keyway
|
||||
(Anka-Makine reference part).
|
||||
- [`examples/pid-simple-loop.md`](examples/pid-simple-loop.md) — P&ID flow
|
||||
control loop with ISA-5.1 tags.
|
||||
|
||||
## Anti-patterns to refuse
|
||||
|
||||
- Calling `entity_create_*` before `drawing_plan` for a deliverable drawing.
|
||||
- Computing endpoint/midpoint/intersection coordinates by hand instead of
|
||||
`point_from_snap`.
|
||||
- Drawing on layer 0 (or any non-engineering layer).
|
||||
- Leaving the CONSTRUCTION layer populated at finalize time.
|
||||
- Hand-rolling trim/extend math when `entity_trim` / `entity_extend` exists.
|
||||
- Skipping `drawing_critique` and going straight to `drawing_finalize`.
|
||||
|
||||
When the user requests a premium drawing, **walk the 9 steps in order** and
|
||||
narrate each call. If a critique returns non-empty issues, fix them in place
|
||||
before proceeding to finalize.
|
||||
@@ -0,0 +1,107 @@
|
||||
# Example — Simple P&ID Flow Control Loop
|
||||
|
||||
ISA-5.1 tagged flow loop: pump P-101 → flow transmitter FT-101 → control valve
|
||||
FCV-101 → vessel V-201. Drawn on A3 with the `pid` layer set.
|
||||
|
||||
## Layout
|
||||
|
||||
```text
|
||||
┌────┐ ╭──╮ ┌────────┐
|
||||
│P101├──────────────────┤FT├────────╳──────│ V-201 │
|
||||
└────┘ PROCESS-MAIN ╰┬─╯ PROCESS │ │ │
|
||||
│ signal │FCV │ │
|
||||
↓ │101 │ │
|
||||
(FIC101) │ └────────┘
|
||||
controller │
|
||||
─┴─ control output
|
||||
```
|
||||
|
||||
## Full workflow
|
||||
|
||||
```python
|
||||
# 1. PLAN
|
||||
plan = await drawing_plan(
|
||||
intent="Flow control loop 101: P-101 → FT-101 → FCV-101 → V-201",
|
||||
sheet_size="A3",
|
||||
scale=1.0,
|
||||
layer_set_id="pid",
|
||||
dim_style="ordinate", # P&ID typically minimal dim
|
||||
notes=["ISA-5.1 tag conventions", "Pneumatic signal lines dashed"],
|
||||
)
|
||||
|
||||
# 2. P&ID LAYERS
|
||||
await drawing_apply_iso_layers("pid")
|
||||
|
||||
# 3. EQUIPMENT — pump (circle) and vessel (rectangle)
|
||||
pump = await entity_create_circle(40, 60, 6, layer="PROCESS-EQUIPMENT")
|
||||
await entity_create_text("P-101", 33, 50, height=3.0, layer="INSTRUMENT-TAG-TEXT")
|
||||
|
||||
vessel = await entity_create_rectangle(180, 40, 230, 80, layer="PROCESS-EQUIPMENT")
|
||||
await entity_create_text("V-201", 195, 60, height=3.5, layer="INSTRUMENT-TAG-TEXT")
|
||||
|
||||
# 4. PROCESS LINE — main horizontal (heavy)
|
||||
main_line_a = await entity_create_line(46, 60, 110, 60, layer="PROCESS-PIPING-MAIN")
|
||||
main_line_b = await entity_create_line(110, 60, 150, 60, layer="PROCESS-PIPING-MAIN")
|
||||
main_line_c = await entity_create_line(150, 60, 180, 60, layer="PROCESS-PIPING-MAIN")
|
||||
|
||||
# 5. INSTRUMENT BUBBLES — flow transmitter, control valve
|
||||
ft = await entity_create_circle(110, 60, 4, layer="INSTRUMENT-SYMBOL")
|
||||
await entity_create_text("FT", 107, 60.5, height=2.0, layer="INSTRUMENT-TAG-TEXT")
|
||||
await entity_create_text("101", 106, 57.5, height=2.0, layer="INSTRUMENT-TAG-TEXT")
|
||||
|
||||
# control valve symbol = bowtie (2 triangles) — built from lines for now
|
||||
fcv_top1 = await entity_create_line(150, 60, 154, 64, layer="PROCESS-VALVES")
|
||||
fcv_top2 = await entity_create_line(150, 60, 154, 56, layer="PROCESS-VALVES")
|
||||
fcv_bot1 = await entity_create_line(154, 64, 158, 60, layer="PROCESS-VALVES")
|
||||
fcv_bot2 = await entity_create_line(154, 56, 158, 60, layer="PROCESS-VALVES")
|
||||
await entity_create_text("FCV-101", 145, 70, height=2.5, layer="INSTRUMENT-TAG-TEXT")
|
||||
|
||||
# 6. SIGNAL LINES — FT bubble up to controller, then to FCV (dashed)
|
||||
sig1 = await entity_create_line(110, 64, 110, 90, layer="INSTRUMENT-LINE-SIGNAL")
|
||||
controller = await entity_create_circle(110, 95, 5, layer="INSTRUMENT-SYMBOL")
|
||||
await entity_create_text("FIC", 107, 95.5, height=2.0, layer="INSTRUMENT-TAG-TEXT")
|
||||
await entity_create_text("101", 106, 92.5, height=2.0, layer="INSTRUMENT-TAG-TEXT")
|
||||
sig2 = await entity_create_line(110, 90, 154, 90, layer="INSTRUMENT-LINE-SIGNAL")
|
||||
sig3 = await entity_create_line(154, 90, 154, 64, layer="INSTRUMENT-LINE-SIGNAL")
|
||||
|
||||
# 7. (no dimensions — P&ID is schematic, not dimensioned)
|
||||
|
||||
# 8. CRITIQUE — focus on what matters for P&ID
|
||||
issues = await drawing_critique(focus=["layer_color", "iso128", "construction_left"])
|
||||
assert issues == [], f"Premium gate failed: {issues}"
|
||||
|
||||
# 9. FINALIZE
|
||||
await titleblock_apply_iso_a3(
|
||||
title="P&ID — Flow Control Loop 101",
|
||||
drawing_number="ANK-PID-2026-001",
|
||||
revision="0",
|
||||
scale="NTS",
|
||||
drawn_by="UE",
|
||||
company="Anka-Makine",
|
||||
)
|
||||
|
||||
await drawing_finalize(
|
||||
save_path=r"C:\work\anka\pid-loop-101.dwg",
|
||||
screenshot_path=r"C:\work\anka\pid-loop-101.png",
|
||||
)
|
||||
```
|
||||
|
||||
## What this example demonstrates
|
||||
|
||||
- **`layer_set_id="pid"`** in `drawing_plan` triggers the P&ID layer bootstrap
|
||||
(PROCESS-PIPING-MAIN, INSTRUMENT-SYMBOL, INSTRUMENT-LINE-SIGNAL, etc).
|
||||
- **Layer-by-purpose**: process lines on PROCESS-PIPING-MAIN (heavy 0.7 mm),
|
||||
signal lines on INSTRUMENT-LINE-SIGNAL (dashed 0.18), tags on
|
||||
INSTRUMENT-TAG-TEXT.
|
||||
- **ISA-5.1 tags** in text: `FT-101`, `FCV-101`, `FIC-101`, `P-101`, `V-201`.
|
||||
- **No dimensions** — P&IDs are schematic. We pass a focused critique that
|
||||
doesn't run dimension overlap checks.
|
||||
- The same `drawing_finalize` infrastructure that handles mechanical drawings
|
||||
works for P&IDs without modification.
|
||||
|
||||
## Where to grow
|
||||
|
||||
V2/V3 features that would polish this example:
|
||||
- Symbol library blocks for pumps, valves, instruments (`block_insert`).
|
||||
- ISO 14617 graphic-symbol catalog as MCP tools.
|
||||
- Equipment-tag and line-tag auto-numbering.
|
||||
@@ -0,0 +1,125 @@
|
||||
# Example — Premium Shaft Drawing
|
||||
|
||||
A fully worked example: power-transmission shaft Ø30 × 200 mm with two keyway
|
||||
seats (10 × 4 × 50 mm each per DIN 6885), drawn at 1:2 on A4.
|
||||
|
||||
## Goal
|
||||
|
||||
```text
|
||||
50 100 50
|
||||
┌──┴──┬───┴───┬──┴──┐
|
||||
│ │ │ │ keyway both ends, M0.04 surface finish
|
||||
└─────┴───────┴─────┘ bearing seat in middle (Ø30 h7)
|
||||
────── 200 ──────
|
||||
```
|
||||
|
||||
## Full workflow
|
||||
|
||||
```python
|
||||
# 1. PLAN — commit intent first
|
||||
plan = await drawing_plan(
|
||||
intent="Power transmission shaft Ø30 x 200, 2× DIN 6885 keyway",
|
||||
sheet_size="A4",
|
||||
scale=0.5, # 1:2 (drawing is half real size)
|
||||
layer_set_id="mech",
|
||||
dim_style="chain",
|
||||
notes=[
|
||||
"Material: C45 normalized",
|
||||
"Surface finish Ra 1.6 µm overall",
|
||||
"Keyways: 10 x 4 x 50 mm per DIN 6885 A",
|
||||
],
|
||||
)
|
||||
|
||||
# 2. LAYERS — mech standard
|
||||
await drawing_apply_iso_layers("mech")
|
||||
|
||||
# 3. CONSTRUCTION — centerline as reference
|
||||
cl = await construction_xline(0, 0, 0) # horizontal centerline at y=0
|
||||
|
||||
# 4. GEOMETRY — front view (top half mirrored later)
|
||||
top_edge = await entity_create_line(0, 15, 200, 15, layer="GEOMETRY")
|
||||
right_edge = await entity_create_line(200, 15, 200, -15, layer="GEOMETRY")
|
||||
bot_edge = await entity_create_line(200, -15, 0, -15, layer="GEOMETRY")
|
||||
left_edge = await entity_create_line(0, -15, 0, 15, layer="GEOMETRY")
|
||||
|
||||
# 4b. KEYWAY LEFT — use the engineering primitive (per CLAUDE.md rule)
|
||||
keyway_l = await keyway_draw_section(
|
||||
bore_x=25, bore_y=0, bore_diameter=30,
|
||||
keyway_width=10, keyway_depth=4,
|
||||
)
|
||||
|
||||
# 4c. KEYWAY RIGHT — symmetric on the other end
|
||||
keyway_r = await keyway_draw_section(
|
||||
bore_x=175, bore_y=0, bore_diameter=30,
|
||||
keyway_width=10, keyway_depth=4,
|
||||
)
|
||||
|
||||
# 4d. Centerline (engineering layer, dashed)
|
||||
center = await entity_create_line(-10, 0, 210, 0, layer="CENTER")
|
||||
|
||||
# 5. CORNERS — small chamfer at each end (1×45°)
|
||||
await entity_chamfer(top_edge["handle"], left_edge["handle"], dist1=1)
|
||||
await entity_chamfer(top_edge["handle"], right_edge["handle"], dist1=1)
|
||||
await entity_chamfer(bot_edge["handle"], left_edge["handle"], dist1=1)
|
||||
await entity_chamfer(bot_edge["handle"], right_edge["handle"], dist1=1)
|
||||
|
||||
# 6. SELECT geometry for dim
|
||||
geometry = await entity_select_smart({
|
||||
"type": "LINE",
|
||||
"layer": "GEOMETRY",
|
||||
"length_range": [40, 220], # exclude small chamfer lines
|
||||
})
|
||||
|
||||
# 7. DIMENSIONS — chain dimensioning, 12 mm offset above the part
|
||||
dims = await dimension_auto(
|
||||
[e["handle"] for e in geometry],
|
||||
style="chain",
|
||||
offset=12.0,
|
||||
)
|
||||
|
||||
# 8. CRITIQUE — must be empty before finalize
|
||||
issues = await drawing_critique(focus=None)
|
||||
if issues:
|
||||
for i in issues:
|
||||
print(f" [{i['severity']}] {i['focus']}: {i['message']}")
|
||||
raise RuntimeError("Premium gate failed; fix before finalize")
|
||||
|
||||
# 9. CLEAR + TITLEBLOCK + FINALIZE
|
||||
await construction_clear()
|
||||
|
||||
await titleblock_apply_iso_a3(
|
||||
title="Transmission Shaft",
|
||||
drawing_number="ANK-SH-2026-001",
|
||||
revision="A",
|
||||
scale="1:2",
|
||||
material="C45 normalized",
|
||||
drawn_by="UE",
|
||||
company="Anka-Makine",
|
||||
)
|
||||
|
||||
result = await drawing_finalize(
|
||||
save_path=r"C:\work\anka\sh-2026-001.dwg",
|
||||
screenshot_path=r"C:\work\anka\sh-2026-001.png",
|
||||
expected={
|
||||
"part_type": "shaft",
|
||||
"must_have_keyway": True,
|
||||
"must_have_bore": False, # solid shaft, not bored
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
## What this example demonstrates
|
||||
|
||||
- **Plan-first workflow** (rule 1)
|
||||
- **Engineering primitives** for the keyway, not hand-drawn (rule 8 + CLAUDE.md
|
||||
production drawing rule)
|
||||
- **Construction line** for the centerline reference, cleared before finalize
|
||||
- **Corner explicitness** via `entity_chamfer` (rule 5)
|
||||
- **`entity_select_smart`** with `length_range` to filter out the tiny chamfer
|
||||
lines (rule 8 — meta-tools over loops)
|
||||
- **`dimension_auto`** with chain style (rule 6)
|
||||
- **Critique gate** before finalize (rule 7)
|
||||
- **`titleblock_apply_iso_a3`** + `drawing_finalize` with `expected` contract
|
||||
|
||||
The whole drawing comes out reproducible: the same call sequence generates an
|
||||
identical DXF every time, suitable for `git diff` and ISO 9001 traceability.
|
||||
@@ -0,0 +1,77 @@
|
||||
# Corner Operations Reference
|
||||
|
||||
V1 supports **LINE+LINE** only. Other entity combinations (line+arc, line+circle,
|
||||
polyline-segment) raise `RuntimeError` and will land in V2/V3.
|
||||
|
||||
## Decision tree
|
||||
|
||||
```text
|
||||
┌────── two lines should meet at a sharp corner
|
||||
│ → entity_trim (when one line overshoots)
|
||||
│ → entity_extend (when one line falls short)
|
||||
│
|
||||
Need to close ─┼────── two lines should meet at a rounded corner (R>0)
|
||||
a corner? │ → entity_fillet
|
||||
│
|
||||
└────── two lines should meet at an angled bevel
|
||||
→ entity_chamfer
|
||||
```
|
||||
|
||||
## `entity_trim(target_handle, cutter_handle, keep_x, keep_y)`
|
||||
|
||||
- `target` — the line being shortened.
|
||||
- `cutter` — treated as an **infinite ray** (AutoCAD's "implied extend" trim).
|
||||
- `keep_x/keep_y` — a point on the side of `target` you want to KEEP.
|
||||
- Returns the (in-place modified) target's `EntityInfo`.
|
||||
|
||||
**Tip:** use `point_from_snap(target_handle, "mid")` or another snap to compute
|
||||
`keep_x/keep_y` deterministically.
|
||||
|
||||
## `entity_extend(target_handle, boundary_handle, end_x=None, end_y=None)`
|
||||
|
||||
- Extends `target` until it hits `boundary` (treated as infinite).
|
||||
- `end_x/end_y=None` → auto-pick the target endpoint nearest the boundary midpoint.
|
||||
- Pass `end_x/end_y` explicitly to disambiguate when you need to extend a
|
||||
specific endpoint.
|
||||
|
||||
## `entity_fillet(handle1, handle2, radius, trim=True)`
|
||||
|
||||
- Inserts a tangent **ARC** between the two lines.
|
||||
- `radius=0` → corner-merge (no arc, only trim).
|
||||
- `trim=False` → leave source lines untouched (rare; AutoCAD default is True).
|
||||
- Returns the new ARC handle (or the first source line for `radius=0`).
|
||||
|
||||
## `entity_chamfer(handle1, handle2, dist1, dist2=None, trim=True)`
|
||||
|
||||
- Inserts a straight chamfer **LINE** between the two source lines.
|
||||
- `dist2=None` → symmetric (`dist2 = dist1`); supply for asymmetric chamfers.
|
||||
- Returns the new chamfer LINE handle.
|
||||
|
||||
## Corner-case table
|
||||
|
||||
| Vaka | Behaviour |
|
||||
|------|-----------|
|
||||
| Lines are parallel / anti-parallel | `RuntimeError("…parallel…")` |
|
||||
| Same handle for both | `RuntimeError("…same…")` |
|
||||
| One handle is not a LINE | `RuntimeError("…LINE+LINE only…")` |
|
||||
| Intersection beyond segment ends | trim/fillet still work (implied extend) |
|
||||
| Negative radius / zero distance | `RuntimeError` |
|
||||
| Tiny gap (< 0.5 mm) between lines | flagged by `drawing_critique` as `untrimmed_corner` |
|
||||
|
||||
## Worked example — L-bracket inner corner R5
|
||||
|
||||
```python
|
||||
l1 = await entity_create_line(0, 0, 100, 0) # bottom edge
|
||||
l2 = await entity_create_line(100, 0, 100, 80) # right edge
|
||||
arc = await entity_fillet(l1.handle, l2.handle, radius=5)
|
||||
# arc.center == (95, 5); arc.radius == 5
|
||||
# l1.end is now (95, 0); l2.start is now (100, 5)
|
||||
```
|
||||
|
||||
## Why this matters
|
||||
|
||||
LLMs without these tools compute corner geometry by hand: they pick endpoints,
|
||||
solve intersections, and write coordinates. That fails the moment a line is
|
||||
slightly off, an angle is non-orthogonal, or the user changes a length. The
|
||||
corner ops + `point_from_snap` move that math out of the LLM's head and into
|
||||
deterministic library code.
|
||||
@@ -0,0 +1,109 @@
|
||||
# ISO Standards Reference
|
||||
|
||||
Distilled tables for the standards this skill enforces.
|
||||
|
||||
## ISO 128 — Line widths
|
||||
|
||||
Allowed lineweight set (mm), with 4:2:1 ratio rule:
|
||||
|
||||
| Width | Common use |
|
||||
|-------|------------|
|
||||
| 0.13 | Hatch, dim text shadow |
|
||||
| 0.18 | Hidden, center, phantom, dim text |
|
||||
| 0.25 | Default text, leader |
|
||||
| 0.35 | Auxiliary visible (where contrast needed) |
|
||||
| 0.50 | Visible solid edges (most common production weight) |
|
||||
| 0.70 | Heavy solid (large-scale parts), P&ID main process |
|
||||
| 1.00 | Border, key heavy outlines |
|
||||
| 1.40 | Title borders |
|
||||
| 2.00 | Sheet outer border |
|
||||
|
||||
**Rule:** every layer's lineweight must be one of these (or 0.0 = "Default" =
|
||||
inherits from active style). Anything else fails `drawing_critique` focus
|
||||
`iso128`.
|
||||
|
||||
## ISO 128 — Line types
|
||||
|
||||
| Type | Linetype name | Used for |
|
||||
|------|---------------|----------|
|
||||
| Continuous thick | `Continuous` (0.50+ mm) | Visible edges |
|
||||
| Dashed thin | `HIDDEN` (0.18–0.25) | Hidden edges |
|
||||
| Chain thin | `CENTER` (0.18) | Centerlines, axes of symmetry |
|
||||
| Chain double-dashed | `PHANTOM` (0.18) | Section / cutting plane / alternate position |
|
||||
| Continuous thin | `Continuous` (0.18–0.25) | Dimensions, hatch, leaders |
|
||||
|
||||
## ISO 13567 — Layer naming (4-field)
|
||||
|
||||
Format: `<Agent2c>-<Element6c>-<Presentation2c>-<Status1c>`
|
||||
|
||||
| Field | Length | Examples |
|
||||
|-------|--------|----------|
|
||||
| Agent | 2 chars | A=Architectural, S=Structural, M=Mechanical, P=Plumbing, E=Electrical |
|
||||
| Element | 6 chars | GEOMET=geometry, HIDDEN=hidden edges, DIMEN=dimensions, TITLE=title block |
|
||||
| Presentation | 2 chars | E=edge, T=text, H=hatch |
|
||||
| Status | 1 char | N=new, E=existing, D=demolish |
|
||||
|
||||
Example layer set ships in `engineering/layers.py::ISO13567_LAYERS`.
|
||||
|
||||
## ISO 129-1 — Dimensioning rules
|
||||
|
||||
1. **Each dimension is given once** — never repeat.
|
||||
2. **Extension lines** project at least 8× line width past the dim line.
|
||||
3. **Decimal separator** is the comma (per ISO; we accept point too).
|
||||
4. **Text orientation**: read from below the drawing or from the right side.
|
||||
5. **Font**: ISO 3098 (`isocp.shx` ships with AutoCAD).
|
||||
6. **Dimension styles**: chain, baseline, ordinate are the three standard
|
||||
layouts — choose one per drawing and stick to it (`dim_style` in PlanSpec).
|
||||
|
||||
## ISO 7200 — Title-block fields
|
||||
|
||||
Mandatory:
|
||||
- Legal owner (Anka-Makine)
|
||||
- Drawing number
|
||||
- Title
|
||||
- Date
|
||||
- Sheet / total sheets
|
||||
- Approver name + signature
|
||||
- Creator name
|
||||
- Document type
|
||||
|
||||
Common optional:
|
||||
- Revision
|
||||
- Scale (matches PlanSpec.scale)
|
||||
- Projection symbol (1st angle ISO E vs 3rd angle ISO A)
|
||||
|
||||
This skill assumes you call `titleblock_apply_iso_a3` (or its A3 cousin) to
|
||||
populate these fields after `drawing_finalize`'s validator runs.
|
||||
|
||||
## ISO 14617 / 10628 — P&ID symbology
|
||||
|
||||
When `layer_set_id="pid"` is passed to `drawing_plan`, the bootstrap creates
|
||||
the P&ID layer set:
|
||||
|
||||
| Layer | Linetype | Lineweight | Purpose |
|
||||
|-------|----------|------------|---------|
|
||||
| `PROCESS-PIPING-MAIN` | Continuous | 0.70 | Main process line (heavy) |
|
||||
| `PROCESS-PIPING-SECONDARY` | Continuous | 0.50 | Secondary process line |
|
||||
| `PROCESS-EQUIPMENT` | Continuous | 0.50 | Vessels / pumps / drums |
|
||||
| `PROCESS-VALVES` | Continuous | 0.35 | Valves & fittings |
|
||||
| `INSTRUMENT-SYMBOL` | Continuous | 0.35 | Instrument bubbles (FCFs) |
|
||||
| `INSTRUMENT-LINE-SIGNAL` | DASHED | 0.18 | Pneumatic / electrical signal |
|
||||
| `INSTRUMENT-TAG-TEXT` | Continuous | 0.25 | Tag labels |
|
||||
| `ELECTRICAL-LINE` | DASHDOT | 0.18 | Electrical line |
|
||||
| `UTILITY-LINE` | PHANTOM | 0.25 | Steam / water / air utility |
|
||||
| `INSULATION-HATCH` | Continuous | 0.13 | Insulation hatching |
|
||||
|
||||
### ISA-5.1 tag conventions (P&ID instruments)
|
||||
|
||||
`<First letter><Second letter>-<Loop number><Suffix?>`
|
||||
|
||||
| First letter (measured variable) | Second letter (function) |
|
||||
|----------------------------------|--------------------------|
|
||||
| F = Flow | T = Transmitter |
|
||||
| P = Pressure | I = Indicator |
|
||||
| T = Temperature | C = Controller |
|
||||
| L = Level | V = Valve |
|
||||
| A = Analytical | S = Switch |
|
||||
|
||||
Examples: `FT-101` (flow transmitter, loop 101), `PCV-203A` (pressure control
|
||||
valve, loop 203, A spare).
|
||||
@@ -0,0 +1,113 @@
|
||||
# Meta-Planning Reference
|
||||
|
||||
The premium meta-tool layer wraps the entity primitives in a quality-first
|
||||
workflow. Schemas live in `engineering/plan_spec.py`; checks live in
|
||||
`engineering/critique.py`.
|
||||
|
||||
## `drawing_plan(intent, sheet_size, scale, layer_set_id, view_count, dim_style, notes)`
|
||||
|
||||
Commits a `PlanSpec` to the backend instance. Always call FIRST.
|
||||
|
||||
```yaml
|
||||
PlanSpec:
|
||||
intent: str # "L-bracket 50x80"
|
||||
sheet_size: A4 | A3 | A2 | A1 | A0 # default A3
|
||||
scale: float # 1.0 = 1:1
|
||||
layer_set_id: mech | pid | iso13567 # default mech
|
||||
view_count: int # default 1
|
||||
dim_style: chain | baseline | ordinate | mixed # default chain
|
||||
notes: list[str] # free-form constraints
|
||||
```
|
||||
|
||||
The PlanSpec is held by the backend (not persisted to DXF) and replayed by
|
||||
`drawing_critique` at finalize time.
|
||||
|
||||
## `drawing_critique(focus=None)`
|
||||
|
||||
Returns a list of `Issue` instances. **Empty list = pass.**
|
||||
|
||||
```yaml
|
||||
Issue:
|
||||
severity: error | warning | info
|
||||
focus: <CritiqueFocus enum>
|
||||
message: str
|
||||
handles: list[str] # entities involved
|
||||
detail: dict # numeric context
|
||||
```
|
||||
|
||||
`CritiqueFocus` is a **closed enum** — adding a new focus requires a code change
|
||||
to `plan_spec.py` AND `critique.py`. This is intentional: it keeps the critique
|
||||
scope from drifting.
|
||||
|
||||
| Focus | Checks |
|
||||
|-------|--------|
|
||||
| `iso128` | Layer lineweights are in {0.13, 0.18, 0.25, 0.35, 0.5, 0.7, 1.0, 1.4, 2.0} mm |
|
||||
| `layer_color` | Engineering layers keep their canonical ACI color |
|
||||
| `dim_overlap` | No two dimensions within 5 mm of each other |
|
||||
| `untrimmed_corner` | No two LINE endpoints within 0.5 mm but not exactly equal |
|
||||
| `duplicate_entities` | No two LINEs with identical (or reversed) endpoints |
|
||||
| `construction_left` | CONSTRUCTION layer is empty |
|
||||
|
||||
`focus=None` runs all six. Pass a list (e.g. `["construction_left", "untrimmed_corner"]`)
|
||||
to scope a check during iterative editing.
|
||||
|
||||
## `point_from_snap(handle, snap, ref_x, ref_y)`
|
||||
|
||||
Eliminates LLM coordinate guessing. Returns `(x, y)` tuple.
|
||||
|
||||
| `snap` | LINE | CIRCLE/ARC | Required |
|
||||
|--------|------|-----------|----------|
|
||||
| `end` | start or end (closer to ref) | arc start or end | — (uses ref if given) |
|
||||
| `mid` | midpoint | arc midpoint | — |
|
||||
| `center` | — | center | — |
|
||||
| `quad` | — | nearest of E/N/W/S quadrants | — (uses ref if given) |
|
||||
| `perp` | foot of perpendicular from ref | — | `ref_x`, `ref_y` |
|
||||
| `near` | nearest point on segment (clamped) | nearest on circle/arc | `ref_x`, `ref_y` |
|
||||
| `int` | (V2 — needs 2nd handle) | | |
|
||||
|
||||
## `construction_xline(x, y, angle_deg, layer="CONSTRUCTION")`
|
||||
|
||||
Creates an infinite construction line on a non-printing layer (color 250,
|
||||
lightest weight). Use as scaffolding when laying out geometry.
|
||||
|
||||
`construction_clear(layer="CONSTRUCTION")` — bulk-deletes everything on the
|
||||
layer. Returns `{"ok": True, "deleted": N}`. **Idempotent.**
|
||||
|
||||
## `drawing_apply_iso_layers(standard)`
|
||||
|
||||
| `standard` | Layer count | Use case |
|
||||
|------------|-------------|----------|
|
||||
| `mech` | 10 | Mechanical part / assembly drawings |
|
||||
| `pid` | 15 | Process & Instrumentation Diagrams (ISO 10628) |
|
||||
| `iso13567` | 10 | ISO 13567 layer-naming convention starter |
|
||||
|
||||
Idempotent — existing layers are not modified.
|
||||
|
||||
## `dimension_auto(handles, style, offset)`
|
||||
|
||||
V1 supports LINE entities only. Generates one or more dimensions using the
|
||||
chosen style.
|
||||
|
||||
| `style` | Behaviour |
|
||||
|---------|-----------|
|
||||
| `chain` | One linear dim per handle, perpendicular-offset from each segment |
|
||||
| `baseline` | All dims share the first segment's start point as baseline |
|
||||
| `ordinate` | X and Y coordinate dims for each segment endpoint |
|
||||
|
||||
`offset` (mm) controls the dim-line distance from the geometry.
|
||||
|
||||
## `entity_select_smart(predicate)`
|
||||
|
||||
Predicate is a dict; **all keys optional, AND-ed**:
|
||||
|
||||
```yaml
|
||||
predicate:
|
||||
type: LINE | CIRCLE | ARC | ... # entity type filter
|
||||
layer: <name> # exact match
|
||||
near: [x, y, radius] # entity within radius of point
|
||||
length_range: [min, max] # LINE/ARC only
|
||||
color: <ACI int> # exact match
|
||||
```
|
||||
|
||||
Returns a list of `EntityInfo`. Use this in lieu of memorising handles when
|
||||
chaining `dimension_auto` or other operations.
|
||||
@@ -0,0 +1,159 @@
|
||||
# Premium Workflow — Full Examples
|
||||
|
||||
The 9-step workflow walked through end-to-end. Use these as templates.
|
||||
|
||||
## Template 1 — Mechanical part (L-bracket with rounded inner corner)
|
||||
|
||||
```python
|
||||
# 1. PLAN
|
||||
plan = await drawing_plan(
|
||||
intent="L-bracket 50x80, R5 inner corner, 5mm thick",
|
||||
sheet_size="A4",
|
||||
scale=1.0,
|
||||
layer_set_id="mech",
|
||||
dim_style="chain",
|
||||
)
|
||||
|
||||
# 2. LAYERS
|
||||
await drawing_apply_iso_layers("mech")
|
||||
|
||||
# 3. CONSTRUCTION (optional — set up reference grid)
|
||||
await construction_xline(0, 0, 0) # horizontal axis
|
||||
await construction_xline(0, 0, 90) # vertical axis
|
||||
|
||||
# 4. GEOMETRY (on GEOMETRY layer)
|
||||
l1 = await entity_create_line(0, 0, 100, 0, layer="GEOMETRY")
|
||||
l2 = await entity_create_line(100, 0, 100, 80, layer="GEOMETRY")
|
||||
|
||||
# 5. CORNERS
|
||||
arc = await entity_fillet(l1["handle"], l2["handle"], radius=5)
|
||||
|
||||
# 6. SELECT for dimensioning (avoid handle memorization)
|
||||
geometry_lines = await entity_select_smart({
|
||||
"type": "LINE",
|
||||
"layer": "GEOMETRY",
|
||||
})
|
||||
|
||||
# 7. DIMENSIONS
|
||||
dims = await dimension_auto(
|
||||
[e["handle"] for e in geometry_lines],
|
||||
style="chain",
|
||||
offset=12.0,
|
||||
)
|
||||
|
||||
# 8. CRITIQUE
|
||||
issues = await drawing_critique(focus=None)
|
||||
assert issues == [], f"Premium gate failed: {issues}"
|
||||
|
||||
# 9. CLEAR + FINALIZE
|
||||
await construction_clear()
|
||||
await drawing_finalize(
|
||||
save_path="C:/work/l-bracket.dwg",
|
||||
screenshot_path="C:/work/l-bracket.png",
|
||||
)
|
||||
```
|
||||
|
||||
## Template 2 — P&ID flow control loop
|
||||
|
||||
```python
|
||||
# 1. PLAN
|
||||
plan = await drawing_plan(
|
||||
intent="Flow control loop 101: pump → valve → vessel",
|
||||
sheet_size="A3",
|
||||
scale=1.0,
|
||||
layer_set_id="pid",
|
||||
dim_style="ordinate",
|
||||
notes=["ISA-5.1 tag conventions", "All process lines on PROCESS-PIPING-MAIN"],
|
||||
)
|
||||
|
||||
# 2. LAYERS — bootstraps PROCESS-PIPING-MAIN, INSTRUMENT-*, etc.
|
||||
await drawing_apply_iso_layers("pid")
|
||||
|
||||
# 3. EQUIPMENT (vessels, pumps — use blocks ideally)
|
||||
await entity_create_circle(50, 50, 8, layer="PROCESS-EQUIPMENT") # pump body
|
||||
await entity_create_rectangle(150, 30, 200, 70, layer="PROCESS-EQUIPMENT") # vessel
|
||||
|
||||
# 4. PROCESS LINES
|
||||
main = await entity_create_line(58, 50, 150, 50, layer="PROCESS-PIPING-MAIN")
|
||||
|
||||
# 5. INSTRUMENT BUBBLES (circle + tag text)
|
||||
ft = await entity_create_circle(100, 70, 5, layer="INSTRUMENT-SYMBOL")
|
||||
await entity_create_text("FT-101", 95, 68, height=2.5, layer="INSTRUMENT-TAG-TEXT")
|
||||
|
||||
# 6. SIGNAL LINE (instrument bubble → controller)
|
||||
await entity_create_line(100, 75, 100, 90, layer="INSTRUMENT-LINE-SIGNAL")
|
||||
|
||||
# 7. (no dimensions on P&ID typically — skip)
|
||||
|
||||
# 8. CRITIQUE — focus on what matters for P&ID
|
||||
issues = await drawing_critique(focus=["layer_color", "iso128", "construction_left"])
|
||||
assert issues == []
|
||||
|
||||
# 9. FINALIZE
|
||||
await drawing_finalize(save_path="C:/work/loop-101.dwg")
|
||||
```
|
||||
|
||||
## Template 3 — Iterative drawing (corrections welcome)
|
||||
|
||||
```python
|
||||
# Initial plan
|
||||
plan = await drawing_plan("Bracket draft", sheet_size="A4")
|
||||
await drawing_apply_iso_layers("mech")
|
||||
|
||||
# Draw something wrong: leaves a gap
|
||||
l1 = await entity_create_line(0, 0, 49.8, 0, layer="GEOMETRY")
|
||||
l2 = await entity_create_line(50, 0, 50, 30, layer="GEOMETRY")
|
||||
|
||||
# Critique — flags untrimmed_corner
|
||||
issues = await drawing_critique(focus=["untrimmed_corner"])
|
||||
# issues[0].handles = [l1.handle, l2.handle]
|
||||
|
||||
# Fix in place: extend l1 to meet l2
|
||||
await entity_extend(l1["handle"], l2["handle"])
|
||||
|
||||
# Re-critique
|
||||
issues = await drawing_critique(focus=["untrimmed_corner"])
|
||||
assert issues == []
|
||||
```
|
||||
|
||||
## Common patterns
|
||||
|
||||
### Snap-driven coordinates
|
||||
|
||||
```python
|
||||
# BAD: guessing
|
||||
await entity_create_line(50, 50, 100, 50)
|
||||
|
||||
# GOOD: snap from existing geometry
|
||||
mid = await point_from_snap(some_line["handle"], "mid")
|
||||
end = await point_from_snap(other_line["handle"], "end", ref_x=100, ref_y=50)
|
||||
await entity_create_line(mid["x"], mid["y"], end["x"], end["y"])
|
||||
```
|
||||
|
||||
### Construction-first workflow
|
||||
|
||||
```python
|
||||
# Lay out reference geometry
|
||||
await construction_xline(0, 0, 0) # x-axis
|
||||
await construction_xline(0, 0, 90) # y-axis
|
||||
await construction_xline(100, 0, 90) # vertical at x=100
|
||||
|
||||
# Snap real geometry to construction intersections
|
||||
ref = await point_from_snap(some_line["handle"], "perp", ref_x=0, ref_y=50)
|
||||
# ... create real geometry on GEOMETRY layer ...
|
||||
|
||||
# Wipe scaffold before finalize
|
||||
await construction_clear()
|
||||
```
|
||||
|
||||
### Block-first repetition
|
||||
|
||||
```python
|
||||
# When 12 holes are needed: create one block, then array
|
||||
hole = await entity_create_circle(0, 0, 5, layer="GEOMETRY")
|
||||
await block_create_from_entities("HOLE_M10", [hole["handle"]], 0, 0)
|
||||
|
||||
# Insert + array — far cheaper than 12 entity_create_circle calls
|
||||
ref = await block_insert("HOLE_M10", x=0, y=0)
|
||||
await entity_array_polar(ref["handle"], count=12, fill_angle=360, center_x=50, center_y=50)
|
||||
```
|
||||
Reference in New Issue
Block a user