[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M71S0PAQB3ZRZWZBSDF4JXBW":3,"skill-same:01M71S0PAQB3ZRZWZBSDF4JXBW":51},{"skillNo":4,"name":5,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":22,"publishedAt":25,"linkOnly":26,"markdownContent":27,"markdownBody":27,"frontMatter":28,"refName":29,"commitSha":30,"skillDirectory":31,"forksCount":32,"repositoryHtmlUrl":33,"repositoryHomepageUrl":6,"ownerHtmlUrl":34,"ownerBlogUrl":35,"ownerDescription":6,"updatedAt":36,"install":37,"resources":50},"01M71S0PAQB3ZRZWZBSDF4JXBW","b123d-drawing",null,"pzfreo\u002Fbuild123d-mcp","pzfreo","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F1773202?v=4","01M71S0HXPCECNMYJD3RJ6QKE5","Paul Fremantle","01M71S0HYR5GR026NQN2V9P3NQ",147,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fpzfreo\u002Fbuild123d-mcp\u002Fblob\u002Fmain\u002Fsrc\u002Fbuild123d_mcp\u002Fskills\u002Fb123d-drawing\u002FSKILL.md",[17],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[23,24],"drawing","b123d drawing","2026-11-10 13:02:02",false,"# Create Engineering Drawing from build123d Geometry\n\nUse this skill when asked to create or fix an engineering drawing for a\nbuild123d component. Drawings export to a project `drawings\u002F` output directory.\n\n**There are two paths — start with the automatic one.**\n\n1. **Automatic (`make_drawing`)** — one call turns a part (or STEP file) into a\n   four-view SVG + DXF with dimensions, centrelines, and an ISO 7200 title\n   block. Use this first for almost every part.\n2. **Builder (`build_drawing`)** — the same pipeline, but it hands back a live\n   `Drawing` you can edit (add\u002Fremove dimensions, add section\u002Fauxiliary views)\n   *before* export. Use this when the automatic drawing needs tweaks.\n3. **Manual pipeline** — build every view and annotation by hand. Only needed\n   for cases the builder cannot express (e.g. a true cut section). Documented at\n   the end as a fallback.\n\nRequires `build123d-drafting-helpers >= 0.7.0` (automatic hole callouts,\npatterns, location dims, and section views).\n\n---\n\n## Step 0 — Understand the part first\n\nBefore drawing anything, build and inspect the geometry:\n\n```\nmcp__build123d-mcp__execute  — build the part in the session\nmcp__build123d-mcp__measure  — confirm volume, bbox, face count\nmcp__build123d-mcp__render_view (save_to='\u002Ftmp\u002Fpreview.png') — visual sanity check\n```\n\nIn the build step, register the part under a stable name with `show(part, \"part\")`\n— the manual pipeline's `suggest_view_layout(object_name=\"part\")` and lint step\nreference it by that name.\n\nNote the bounding-box extents and whether the part is rotationally symmetric.\nThis tells you whether the automatic drawing will capture the key features, and\ndrives any manual layout decisions later.\n\n---\n\n## Step 1 — Generate the drawing automatically (start here)\n\nIn the same `execute()` session where you built the part:\n\n```python\nfrom build123d_drafting import make_drawing\n\nsvg, dxf = make_drawing(\n    part,                       # an in-session build123d object, OR a \"path\u002Fto\u002Fpart.step\"\n    out=\"drawings\u002Fbracket\",     # output stem; \".svg\"\u002F\".dxf\" are appended\n    title=\"BRACKET\",            # ISO 7200 document title\n    number=\"DWG-042\",           # ISO 7200 document identifier\n    tolerance=\"ISO 2768-f\",     # general tolerance\n    drawn_by=\"Your Name\",\n)\n```\n\n`make_drawing` chooses the scale + ISO page size, projects front\u002Fplan\u002Fside\u002Fiso\nviews, and annotates automatically — then lints and writes both SVG and DXF.\nPass an in-memory object directly (no STEP round-trip) or a STEP path.\n\nAs of helpers v0.7.0 the automatic annotation covers **prismatic parts in\nfull**: every recognised hole gets a grouped callout (\"4× ø10 THRU\",\ncounterbore\u002Fdepth symbols), bolt circles get \"EQ SP ON øD BC\" callouts with a\npitch-circle centreline, linear arrays get pitch dims, every hole gets a\ncentre mark and baseline X\u002FY location dims from the min-X\u002FY datum corner, and\nblind\u002Fcounterbored holes trigger an automatic SECTION A–A. Turned parts get\nOD\u002Flength dims, centrelines, and bore leaders. **Do not clear and re-place\nthese annotations** — edit only what is wrong or missing.\n\nThen verify (Step 3). For most parts you are done here.\n\nWhat `make_drawing` does **not** do: functional datum selection, GD&T,\ntolerances by fit, thread callouts, spiral\u002Ffreeform profile dimensions,\ncross-axis hole *location* dims, pattern callouts on turned flanges, and\nsection hatching. If the lint reports `feature_not_dimensioned` \u002F\n`feature_count_mismatch`, something was skipped for page-space reasons — add\njust that callout in Step 2.\n\nThe default location datum is the part's minimum-X\u002FY corner; if the\nfunctional datum differs, re-anchor those dims in Step 2 rather than\nrebuilding the sheet.\n\n---\n\n## Step 2 — Customise with the Drawing builder\n\nWhen the automatic drawing is close but needs edits, swap `make_drawing` for\n`build_drawing`. It runs the identical pipeline but returns a live `Drawing`\n**before** export, so your edits land in the output. For exact signatures and\nkeyword names of every class and function used below (`Dimension`, `Leader`,\n`TitleBlock`, `Drawing` methods, ...), read the `build123d:\u002F\u002Fdrafting-api`\nresource — it is generated from the installed library, so it always matches:\n\n```python\nfrom build123d_drafting import build_drawing, Leader\n\ndwg = build_drawing(part, out=\"drawings\u002Fbracket\", title=\"BRACKET\",\n                    number=\"DWG-042\", tolerance=\"ISO 2768-f\", drawn_by=\"Your Name\")\n\n# Available on dwg:\n#   dwg.views        {\"front\",\"plan\",\"side\",\"iso\"} → (visible, hidden) compounds\n#   dwg.annotations  mutable list of annotation objects\n#   dwg.draft \u002F dwg.scale \u002F dwg.page_w \u002F dwg.page_h\n#   dwg.look_at \u002F dwg.dist   (scaled-space building blocks for custom cameras)\n#   dwg.at(view, x, y, z)    → page point (px, py, 0) mapped from world coordinates\n\n# Add a dimension\u002Fleader the automatic pass missed:\ndwg.add(Leader(tip=dwg.at(\"front\", 10, 0, 5), elbow=(8, 40, 0),\n               label=\"ø4 BORE\", draft=dwg.draft), \"ldr_bore\")\n\n# Drop an automatic annotation by name. Auto names: dim_height, dim_od, dim_width,\n# centerline_front, centerline_side, ldr_z0…, dim_step_0…, title_block.\ndwg.remove(\"dim_od\")\n\n# Re-lint after edits, then export:\nissues = dwg.lint()                       # list of LintIssue; [] when clean\nsvg, dxf = dwg.export(\"drawings\u002Fbracket\")\n```\n\n`make_drawing(...)` is exactly `build_drawing(...).export()`.\n\n**Add a section or auxiliary view** with `add_view()`. Give it a (pre-cut)\nshape, a camera in **scaled** space (compose it from `dwg.look_at` and\n`dwg.dist`, the same convention the standard views use), an up vector, and a\npage position; it returns that view's coordinate helper (`ViewCoordinates`):\n\n```python\nlook = dwg.look_at\nbottom = (look[0], look[1], look[2] - dwg.dist)        # camera below the part\nvc = dwg.add_view(\"bottom\", part, bottom, (0, 1, 0), (260.0, 60.0))\npx, py = vc.pp(world_x, world_y, world_z)              # annotate using the helper\n```\n\nDrop to the **manual pipeline** (below) only when even the builder cannot\nexpress what you need — most commonly a true cut\u002Fsection where you must boolean\nthe part against a cutting box yourself, or a fully bespoke multi-part sheet.\n\n---\n\n## Step 3 — Verify\n\n```\nmcp__build123d-mcp__render_drawing(svg_path='drawings\u002Fpart_name.svg', save_to='\u002Ftmp\u002Fdwg.png')\n```\n\nReview the rendered PNG before moving on: check the views are upright and\ncomplete, dimensions are legible and not colliding, and the title block reads\ncorrectly. `view_annotation_overlap` warnings from the lint step usually show up\nhere as cramped leaders — fix them with the builder (Step 2) if they matter.\n\nThen preserve the label metadata and check the exported file itself:\n\n```\nmcp__build123d-mcp__save_drawing_annotations(svg_path='drawings\u002Fpart_name.svg')\nmcp__build123d-mcp__inspect_drawing(svg_path='drawings\u002Fpart_name.svg')\n```\n\nbuild123d renders text as glyph paths, so label strings are irrecoverable from\na finished SVG — `save_drawing_annotations` writes a `.dims.json` sidecar that\n`inspect_drawing(svg_path=...)` reads back, letting you (or a later session)\ninspect page size, layers, and annotation content without rebuilding anything.\n\nThe sidecar only captures annotations registered **in this session** via\n`annotate()`. If the SVG was produced by a standalone script (Step 4), the\nannotations live in that script's process and the tool will tell you nothing\nwas written — re-exporting the sidecar is then the script's job.\n\n---\n\n## Step 4 — Save a standalone regeneration script (default)\n\nUnless the user opts out, also write a clean, committable script to\n`scripts\u002Fdrawings\u002F\u003Cpart>.py` that regenerates the drawing in a single run. The\ndrawing should live in version control as reproducible code, not only as output\nartifacts. This must be a tidy reproducible script — **not** a dump of your\nexploratory `execute()` session.\n\nIf the project already has a conflicting name at that path (e.g. a\n`scripts\u002Fdrawings.py` module), do **not** create a `scripts\u002Fdrawings\u002F`\ndirectory alongside it — pick a non-colliding path such as\n`scripts\u002F\u003Cpart>_drawing.py` and follow the project's existing script layout.\n\nPick the case that matches how you obtained the geometry:\n\n**A — Drawing from a STEP file** → use `generate_script()`. It writes an\neditable `build_drawing` script (including the customise-before-export seam)\nthat reloads the STEP from disk:\n\n```python\nfrom build123d_drafting import generate_script\n\ngenerate_script(\n    \"path\u002Fto\u002Fpart.step\",\n    out=\"scripts\u002Fdrawings\u002Fbracket\",   # → writes scripts\u002Fdrawings\u002Fbracket.py\n    title=\"BRACKET\", number=\"DWG-042\",\n    tolerance=\"ISO 2768-f\", drawn_by=\"Your Name\",\n)\n```\n\nThe generated script exports its SVG\u002FDXF **next to itself** (the `out=_stem`\nline uses the script's own path), e.g. `scripts\u002Fdrawings\u002Fbracket.svg`. If you\nwant the outputs under `drawings\u002F` instead, edit the final `dwg.export(...)`\nline in the generated script.\n\n**B — Drawing an in-session object** → `generate_script()` cannot embed a live\nobject (it reloads geometry from disk and raises `TypeError` on a `Shape`).\nWrite the script by hand so it is self-contained:\n\n1. Reconstruct the part — import the project's part-building module if one\n   exists (preferred), otherwise inline the minimal construction code.\n2. Call `make_drawing` \u002F `build_drawing` with the same parameters you used.\n\n```python\n#!\u002Fusr\u002Fbin\u002Fenv python3\n\"\"\"BRACKET — regenerates drawings\u002Fbracket.svg + .dxf in one run.\"\"\"\nfrom build123d_drafting import make_drawing\nfrom myproject.bracket import build_bracket   # the part's source of truth\n\npart = build_bracket()\nmake_drawing(part, out=\"drawings\u002Fbracket\", title=\"BRACKET\",\n             number=\"DWG-042\", tolerance=\"ISO 2768-f\", drawn_by=\"Your Name\")\n```\n\nIf the part was built ad-hoc in the session with no importable source, export it\nto STEP once and use case A instead — that keeps the script reproducible without\npasting scratch geometry code.\n\nTell the user where the script was saved.\n\n---\n---\n\n# Manual pipeline (fallback)\n\nUse this **only** when `make_drawing` \u002F `build_drawing` cannot express the\ndrawing (true cut sections, bespoke layouts). It builds every view and\nannotation by hand. Step 0 above still applies; the steps below replace\nSteps 1–3.\n\n## Manual 1 — Choose views (third-angle projection)\n\nStandard four-view layout. Page size is chosen in Manual 2 based on part extents\n(A4 landscape 297 × 210 mm for most parts; A3 landscape 420 × 297 mm for large ones).\n\n| View | Camera position (scaled space) | Up vector | Role |\n|------|-------------------------------|-----------|------|\n| Front (along -Y) | `(cxs, cys − DIST, czs)` | `(0, 0, 1)` | primary dims |\n| Side (along +X)  | `(cxs + DIST, cys, czs)` | `(0, 0, 1)` | depth\u002Fbore |\n| Plan (along +Z)  | `(cxs, cys, czs + DIST)` | `(0, 1, 0)` | footprint |\n| Isometric        | `(cxs+ID, cys+ID, czs+ID)`         | `(0, 0, 1)` | pictorial, no dims |\n\nwhere `cxs = cx * SCALE`, `cys = cy * SCALE`, `czs = cz * SCALE`,\n`DIST = bbox_max * SCALE + 100` (orthographic cameras always outside the scaled bbox),\nand `ID = DIST \u002F (3 ** 0.5)` (iso camera at the same distance along the equal-axis diagonal).\n\n**Critical:** view direction = `look_at − camera`. For a pure orthographic projection the\ncamera's off-axis coordinates must equal the scaled centroid — using `(0, -DIST, 0)` instead\nof `(cxs, cys - DIST, czs)` introduces a silent tilt whenever the centroid is off-axis.\n\nThe iso camera uses equal `+ID` offsets on all three axes for a standard equal-axis view.\nNegate one axis (e.g. `(cxs-ID, cys+ID, czs+ID)`) to flip the pictorial orientation when\na key feature is otherwise hidden.\n\nAxis mapping verification and sheet-position layout are both done in Manual 2 once SCALE\nis known — see the `view_axes` and `suggest_view_layout` calls there.\n\n---\n\n## Manual 2 — Choose page size and scale, then project\n\nUse `choose_scale()` from `build123d_drafting` — it applies the same thresholds as\nthe automated pipeline and returns `TB_W` (title-block width) too:\n\n```python\nfrom build123d_drafting import choose_scale\n\n# Extract geometry from part — drives all layout decisions below.\n_bb   = part.bounding_box()\nx_size = _bb.max.X - _bb.min.X\ny_size = _bb.max.Y - _bb.min.Y\nz_size = _bb.max.Z - _bb.min.Z\ncx = (_bb.min.X + _bb.max.X) \u002F 2\ncy = (_bb.min.Y + _bb.max.Y) \u002F 2\ncz = (_bb.min.Z + _bb.max.Z) \u002F 2\nbbox_max = max(x_size, y_size, z_size)\n\nSCALE, PAGE_W, PAGE_H, TB_W = choose_scale(x_size, y_size, z_size)\n```\n\nCompute sheet positions using the `suggest_view_layout` MCP tool — it accounts for the\ntitle block footprint and warns when views collide with it or with each other:\n\n```\n# TB_W comes from choose_scale() — do NOT hardcode 150.0, it is 120.0 on A4.\n\nmcp__build123d-mcp__suggest_view_layout(\n    object_name=\"part\",        # name passed to show() in Step 0\n    page_w=PAGE_W, page_h=PAGE_H, scale=SCALE,\n    title_block_w=TB_W,\n    title_block_h=24,          # ISO 7200 title block height with revision + legal_owner rows\n)\n```\n\nIf the part is not in the session (e.g. the import failed or timed out), pass\n`extents=[x_size, y_size, z_size]` (+ optional `centroid=[x, y, z]`) instead of\n`object_name` — the layout only needs the bounding box, not live geometry.\n\nCheck `result[\"warnings\"]` — if any view overlaps the title block or another view, the\ntool says so and may suggest a smaller scale or larger page. Address warnings before\ncontinuing. Then extract the positions:\n\n```python\n# Extract positions from suggest_view_layout result.\n# Re-run suggest_view_layout above if the part geometry changes before Manual 2.\nFV_X, FV_Y   = \u003Cresult[\"views\"][\"front\"][\"VIEW_X\"]>, \u003Cresult[\"views\"][\"front\"][\"VIEW_Y\"]>\nSV_X, SV_Y   = \u003Cresult[\"views\"][\"side\"][\"VIEW_X\"]>,  \u003Cresult[\"views\"][\"side\"][\"VIEW_Y\"]>\nPV_X, PV_Y   = \u003Cresult[\"views\"][\"plan\"][\"VIEW_X\"]>,  \u003Cresult[\"views\"][\"plan\"][\"VIEW_Y\"]>\nISO_X, ISO_Y = \u003Cresult[\"views\"][\"iso\"][\"VIEW_X\"]>,   \u003Cresult[\"views\"][\"iso\"][\"VIEW_Y\"]>\n```\n\nThen project:\n\n```python\npart_scaled = part.scale(SCALE)\n\n# Scaled centroid — used as look_at AND as the camera's off-axis coordinates.\n# Off-axis coords must match look_at so view direction stays pure-orthographic.\ncxs, cys, czs = cx * SCALE, cy * SCALE, cz * SCALE\nlook_at_s = (cxs, cys, czs)\nDIST = bbox_max * SCALE + 100          # camera always outside the scaled bbox\nID   = DIST \u002F (3 ** 0.5)               # iso offset — same distance along equal-axis diagonal\n\n# Per-view camera positions: only the on-axis component differs.\n# Remove entries for views you don't need.\nVIEWS = {\n    \"front\": ((cxs, cys - DIST, czs), (0, 0, 1)),  # looking along +Y\n    \"side\":  ((cxs + DIST, cys, czs), (0, 0, 1)),  # looking along -X\n    \"plan\":  ((cxs, cys, czs + DIST), (0, 1, 0)),  # looking down -Z\n}\nVIEW_POS = {\n    \"front\": (FV_X, FV_Y),\n    \"side\":  (SV_X, SV_Y),\n    \"plan\":  (PV_X, PV_Y),\n}\n\n# Project and place each orthographic view; collect results by name.\nview_proj = {}  # {name: (placed_vis, placed_hid_or_None)}\nfor view_name, (camera_pos, up) in VIEWS.items():\n    vis, hid = part_scaled.project_to_viewport(camera_pos, up, look_at_s)\n    if not list(vis):\n        raise ValueError(f\"project_to_viewport returned empty geometry for {view_name} camera {camera_pos}\")\n    vx, vy = VIEW_POS[view_name]\n    placed     = Compound(children=list(vis)).locate(Location((vx, vy, 0)))\n    placed_hid = Compound(children=list(hid)).locate(Location((vx, vy, 0))) if hid else None\n    view_proj[view_name] = (placed, placed_hid)\n\n# Iso uses the scaled part and a distance-safe offset on all three axes.\niso_vis, iso_hid = part_scaled.project_to_viewport(\n    (cxs + ID, cys + ID, czs + ID), (0, 0, 1), look_at_s\n)\niso   = Compound(children=list(iso_vis)).locate(Location((ISO_X, ISO_Y, 0)))\niso_h = Compound(children=list(iso_hid)).locate(Location((ISO_X, ISO_Y, 0))) if iso_hid else None\n```\n\nNow verify the axis mapping for each view. `cxs`\u002F`cys`\u002F`czs` are available here; substitute\nthe actual numeric values for `look_at`:\n\n```\nmcp__build123d-mcp__view_axes(viewport_origin=[cxs, cys-DIST, czs], viewport_up=[0,0,1], look_at=[cxs,cys,czs])\nmcp__build123d-mcp__view_axes(viewport_origin=[cxs+DIST, cys, czs], viewport_up=[0,0,1], look_at=[cxs,cys,czs])\nmcp__build123d-mcp__view_axes(viewport_origin=[cxs, cys, czs+DIST], viewport_up=[0,1,0], look_at=[cxs,cys,czs])\n```\n\nCopy the results into the script as comments — they are the source of truth for the\ncoordinate helpers in Manual 3.\n\n---\n\n## Manual 3 — Coordinate helpers\n\nWrite one pair of helpers per view so annotation coords are derived from world\ngeometry, not hardcoded page numbers. Use the `view_axes` results from Manual 2\nto get the signs right — never assume:\n\n```python\n# Front view (camera along −Y): world_X → page_X (+1), world_Z → page_Y (+1)\ndef FX(x): return FV_X + (x - cx) * SCALE\ndef FZ(z): return FV_Y + (z - cz) * SCALE\n\n# Side view (camera along +X): world_Y → page_X (+1), world_Z → page_Y (+1)\ndef SX(y): return SV_X + (y - cy) * SCALE\ndef SZ(z): return SV_Y + (z - cz) * SCALE\n```\n\nFor ISO views (two world axes share one page axis), use `ViewCoordinates.pp()` instead:\n\n```python\nfrom build123d_drafting import ViewCoordinates, view_axes\n# ViewCoordinates(axes, view_x, view_y, cx, cy, cz, scale)\niso_vc = ViewCoordinates(\n    view_axes((cxs + ID, cys + ID, czs + ID), (0, 0, 1), look_at_s),\n    ISO_X, ISO_Y, cx, cy, cz, SCALE,\n)\npage_x, page_y = iso_vc.pp(world_x, world_y, world_z)\n```\n\nVerify a known extent (e.g. top of part) maps to a sensible page Y before placing\nany annotation.\n\n---\n\n## Manual 4 — Annotate with build123d_drafting\n\n```python\nfrom build123d_drafting import (\n    Centerline, Dimension, Leader, TitleBlock,\n    annotate, draft_preset, lint_drawing, place_dims, set_page,\n)\n\ndraft = draft_preset(font_size=2.5, decimal_precision=1)\n```\n\n**Centrelines and bore leaders for rotationally symmetric parts** (Z-axis cylinders):\n\n```python\n# Z-axis diameters, largest first (or build the list by hand):\nfrom build123d_drafting import analyse_cylinders, dedup_diams\nz_diams = dedup_diams(analyse_cylinders(part)[0])\nif z_diams:\n    # Rotation-axis centreline in front and side views\n    annotate(Centerline(\n        (FX(cx), FZ(_bb.min.Z) - 5, 0),\n        (FX(cx), FZ(_bb.max.Z) + 5, 0),\n    ), \"centerline_front\")\n    annotate(Centerline(\n        (SX(cy), SZ(_bb.min.Z) - 5, 0),\n        (SX(cy), SZ(_bb.max.Z) + 5, 0),\n    ), \"centerline_side\")\n\n    # Inner bore leaders — arrowhead on bore edge, elbow to the left\n    left_edge = FX(_bb.min.X)\n    elbow_x   = left_edge - 10          # ~10 mm gap to left of part outline\n    for i, d in enumerate(z_diams[1:4]):\n        tip_z = FZ(cz) + (i - 1) * 10  # stagger vertically\n        annotate(Leader(\n            tip=(FX(cx - d \u002F 2), tip_z, 0),\n            elbow=(elbow_x, tip_z, 0),\n            label=f\"ø{d:.1f}\" if d != int(d) else f\"ø{int(d)}\",\n            draft=draft,\n        ), f\"bore_z{i}\")\n```\n\n**Stacked dimensions** (use `place_dims` — it handles offset stacking automatically):\n\n```python\ndims = place_dims([\n    ((x0, y_base, 0), (x1, y_base, 0), \"below\", \"14.8\"),\n    ((x0, y_base, 0), (x2, y_base, 0), \"below\",  \"7.1\"),\n], draft)\nfor i, d in enumerate(dims):\n    annotate(d, f\"dim_name_{i}\")\n```\n\n**Leaders** (diameter callouts, part labels):\n\n```python\nldr = Leader(\n    tip=(page_x, page_y, 0),\n    elbow=(elbow_x, elbow_y, 0),\n    label=\"ø4.0 BEARING\",\n    draft=draft,\n)\nannotate(ldr, \"ldr_bearing_d\")\n```\n\n**Title block** (always include — use real values for the part being drawn).\nISO 7200:2004 mandatory fields:\n\n| ISO 7200 field | Parameter |\n|----------------|-----------|\n| Field 1 — Legal owner | `legal_owner=` |\n| Field 2 — Document description | `part_name` (first positional) |\n| Field 3 — Document identifier | `drawing_number` (second positional) |\n| Field 4 — Revision indicator | `revision=` |\n\n```python\ntb = TitleBlock(\n    \"PART NAME\",          # ISO 7200 field 2 — document title\n    \"DWG-NNN\",            # ISO 7200 field 3 — document identifier\n    drawing_scale=SCALE,  # syncs title block cell text with lint_drawing(drawing_scale=SCALE)\n    material=\"CZ121 BRASS\",\n    general_tolerance=\"ISO 2768-f\",\n    designed_by=\"Your Name\",\n    revision=\"A\",                 # ISO 7200 field 4 — revision indicator\n    legal_owner=\"COMPANY NAME\",   # ISO 7200 field 1 — legal owner\n    width=TB_W,\n    draft=draft,\n).locate(Location((PAGE_W - TB_W - 10, 10, 0)))  # right-aligned: PAGE_W − block_width − margin\nannotate(tb, \"title_block\")\n```\n\nEvery annotation object **must** be passed to `annotate()` — otherwise lint and\nexport will not see it.\n\n---\n\n## Manual 5 — Lint gate (run before export)\n\nUse the MCP tool — it reads annotations and view shapes directly from session state:\n\n```\n# show() each placed view compound under a stable name so the MCP tool can find them.\nshow(view_proj[\"front\"][0], \"front_placed\")\nshow(view_proj[\"side\"][0],  \"side_placed\")\nshow(view_proj[\"plan\"][0],  \"plan_placed\")\nshow(iso,                   \"iso_placed\")\n\nmcp__build123d-mcp__lint_drawing(\n    drawing_scale=SCALE,\n    view_shape_names=[\"front_placed\", \"side_placed\", \"plan_placed\", \"iso_placed\"],\n)\n```\n\nThe `view_shape_names` list enables `view_annotation_overlap` and `view_overlap` checks.\nOmit it if you only need label\u002Foverlap checks and don't need view-boundary detection.\n\n```python\n# Alternatively, call the helper directly in execute() for more control:\nall_anns = list(dims) + [ldr1, ldr2, tb]\nset_page(PAGE_W, PAGE_H, margin=10)\nview_shapes = [placed for placed, _ in view_proj.values()] + [iso]\nissues = lint_drawing(all_anns, drawing_scale=SCALE, view_shapes=view_shapes)\nif issues:\n    for iss in issues: print(f\"  [{iss.severity}] {iss.code}: {iss.message}\")\nelse:\n    print(\"Lint: OK\")\n```\n\nDo not export until lint is clean (or all issues are understood and accepted).\n\nCommon lint failures and fixes:\n- `label_axis_swap` — dimension endpoints are swapped (X↔Y); check coord helper signs\n- `label_mismatch` — label string doesn't match the geometric distance; recheck scale\n- `page_bounds` — annotation is outside the 297×210 margin; adjust view position\n- `view_annotation_overlap` — an annotation's bbox overlaps a view outline; move the dim\n- `view_overlap` — two view outlines overlap each other; re-run `suggest_view_layout` at a smaller scale or larger page\n\n---\n\n## Manual 6 — Export SVG and DXF\n\n```python\npart_color = Color(0, 0, 0)\nhid_color  = Color(0.5, 0.5, 0.5)\ndim_color  = Color(0, 0.2, 0.7)\n\nsvg_exp = ExportSVG(margin=10)\nsvg_exp.add_layer(\"part\",   line_color=part_color, line_weight=0.5)\nsvg_exp.add_layer(\"hidden\", line_color=hid_color,  line_weight=0.25,\n                  line_type=LineType.HIDDEN)\nsvg_exp.add_layer(\"dims\",   line_color=dim_color,  fill_color=dim_color,\n                  line_weight=0.05)\nfor placed, _ in view_proj.values():\n    svg_exp.add_shape(placed, layer=\"part\")\nsvg_exp.add_shape(iso, layer=\"part\")\nfor _, placed_hid in view_proj.values():\n    if placed_hid:\n        svg_exp.add_shape(placed_hid, layer=\"hidden\")\nif iso_h:\n    svg_exp.add_shape(iso_h, layer=\"hidden\")\nfor ann in all_anns:\n    svg_exp.add_shape(ann, layer=\"dims\")\nsvg_exp.write(str(output_dir \u002F \"part_name.svg\"))\n\n# Mandatory: fix the SVG viewBox so the full ISO page is preserved, not cropped\n# to the content bounding box (build123d ExportSVG default).\nfrom build123d_drafting import fix_svg_page_size\nfix_svg_page_size(str(output_dir \u002F \"part_name.svg\"), PAGE_W, PAGE_H)\n```\n\nThen export DXF (same layer structure; omit `line_color`, `fill_color`, and `line_type` args):\n\n```python\nfrom build123d import ExportDXF\n\ndxf_exp = ExportDXF()\ndxf_exp.add_layer(\"part\",   line_weight=0.5)\ndxf_exp.add_layer(\"hidden\", line_weight=0.25)\ndxf_exp.add_layer(\"dims\",   line_weight=0.05)\nfor placed, _ in view_proj.values():\n    dxf_exp.add_shape(placed, layer=\"part\")\ndxf_exp.add_shape(iso, layer=\"part\")\nfor _, placed_hid in view_proj.values():\n    if placed_hid:\n        dxf_exp.add_shape(placed_hid, layer=\"hidden\")\nif iso_h:\n    dxf_exp.add_shape(iso_h, layer=\"hidden\")\nfor ann in all_anns:\n    dxf_exp.add_shape(ann, layer=\"dims\")\ndxf_exp.write(str(output_dir \u002F \"part_name.dxf\"))\n```\n\n---\n\n## Manual 7 — Verify the SVG with the MCP server\n\n```\nmcp__build123d-mcp__render_drawing(svg_path='drawings\u002Fpart_name.svg', save_to='\u002Ftmp\u002Fdwg.png')\n```\n\nReview the rendered PNG before moving on.\n\n---\n\n## Manual 8 — Combine into a PDF (optional)\n\nTo assemble multiple drawing SVGs into a single multi-page PDF, rasterise each\nSVG at 200 DPI using `resvg-py` and combine pages with `fpdf2`.\n\n`PAGE_W` and `PAGE_H` are the values chosen in Manual 2 (e.g. 297\u002F210 for A4 or\n420\u002F297 for A3 landscape). Use them throughout so the PDF matches the drawing sheet.\n\n```python\nimport resvg_py\nfrom fpdf import FPDF\n\n# PAGE_W, PAGE_H set in Manual 2\nfmt = \"A4\" if PAGE_W \u003C 400 else \"A3\"\n\npng_bytes = resvg_py.svg_to_bytes(svg_path=str(svg_path), dpi=200)\n\npdf = FPDF(orientation=\"L\", unit=\"mm\", format=fmt)\npdf.add_page()\npdf.image(tmp_png_path, x=0, y=0, w=PAGE_W, h=PAGE_H)\npdf.output(\"drawings\u002Foutput.pdf\")\n```\n\nbuild123d `ExportSVG` writes Y-up coordinates; the `viewBox` origin encodes\nwhere content sits on the sheet. To recover the correct Y position for PDF\n(Y-down, top-left origin):\n\n```python\nvb_y = float(svg_root.get(\"viewBox\").split()[1])   # negative in Y-up drawing coords\nassert vb_y \u003C= 0, f\"Unexpected positive viewBox Y: {vb_y} — check ExportSVG output\"\npdf_y = PAGE_H - abs(vb_y)\n```\n\nIf the assert fires, the SVG was generated with a different coordinate convention;\ninspect the `viewBox` before placing the image.\n\n---\n\n## Manual 9 — Save a standalone regeneration script (default)\n\nThe same default as Step 4 applies: unless the user opts out, save a clean,\ncommittable `scripts\u002Fdrawings\u002F\u003Cpart>.py` that regenerates the drawing in one run.\nFor the manual pipeline there is no `generate_script()` shortcut, so assemble the\nscript by hand — reconstruct the part (import its source module, or load the STEP)\nfollowed by the projection \u002F annotation \u002F export steps above. Keep it tidy and\nreproducible, not a paste of the exploratory session.\n\n---\n\n## Layout rules of thumb\n\n- Leave ≥ 12 mm between any two view outlines.\n- Dimension lines below\u002Fleft of the view they measure; leader elbows clear the geometry.\n- Isometric goes in the corner least occupied by orthographic views (usually bottom-left or far right).\n- Title block: bottom-right, 150–170 mm wide, Y anchor ≈ 11 mm from bottom.\n- Don't put dimensions on the isometric — it is a pictorial only.\n\n---\n\n## Axis sign quick-reference\n\n`view_axes` output drives all coordinate helpers. Common cases:\n\n| Camera (VIEWS key) | Up | world_X | world_Y | world_Z |\n|--------------------|----|---------|---------|---------|\n| -Y (front) | +Z | page_X (+1) | — | page_Y (+1) |\n| +X (side)  | +Z | — | page_X (+1) | page_Y (+1) |\n| +Z (plan)  | +Y | page_X (+1) | page_Y (+1) | — |\n| -X (alt)   | +Z | — | page_X (-1) | page_Y (+1) |\n\nSigns flip when the camera is on the negative axis — always verify with\n`view_axes` rather than assuming.\n",{},"main","49023cd809092daa10dea0e9648a87006591d1f1","src\u002Fbuild123d_mcp\u002Fskills\u002Fb123d-drawing",20,"https:\u002F\u002Fgithub.com\u002Fpzfreo\u002Fbuild123d-mcp","https:\u002F\u002Fgithub.com\u002Fpzfreo","http:\u002F\u002Fpzf.fremantle.org","2026-11-12 02:40:24",{"source":7,"skillName":5,"commands":38,"prompt":48,"zipAvailable":49},[39,42,45],{"tool":40,"command":41},"npx","npx skills add pzfreo\u002Fbuild123d-mcp --skill b123d-drawing",{"tool":43,"command":44},"bunx","bunx skills add pzfreo\u002Fbuild123d-mcp --skill b123d-drawing",{"tool":46,"command":47},"pnpm","pnpm dlx skills add pzfreo\u002Fbuild123d-mcp --skill b123d-drawing","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fpzfreo\u002Fbuild123d-mcp\u002Fblob\u002Fmain\u002Fsrc\u002Fbuild123d_mcp\u002Fskills\u002Fb123d-drawing\u002FSKILL.md",true,[],[52,60,68,76],{"skillNo":53,"name":54,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":55,"categories":56,"keywords":57,"publishedAt":25},"01M71S113XAE1RPP5B4TZJJCGW","b123d-repair","https:\u002F\u002Fgithub.com\u002Fpzfreo\u002Fbuild123d-mcp\u002Fblob\u002Fmain\u002Fsrc\u002Fbuild123d_mcp\u002Fskills\u002Fb123d-repair\u002FSKILL.md",[],[58,59],"repair","b123d repair",{"skillNo":61,"name":62,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":63,"categories":64,"keywords":65,"publishedAt":25},"01M71S0XRPYA245TMQSFZ0FQ9V","b123d-modeling","https:\u002F\u002Fgithub.com\u002Fpzfreo\u002Fbuild123d-mcp\u002Fblob\u002Fmain\u002Fsrc\u002Fbuild123d_mcp\u002Fskills\u002Fb123d-modeling\u002FSKILL.md",[],[66,67],"modeling","b123d modeling",{"skillNo":69,"name":70,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":71,"categories":72,"keywords":73,"publishedAt":25},"01M71S0T5QS1HV2ZCTWVZZ98KJ","b123d-edit","https:\u002F\u002Fgithub.com\u002Fpzfreo\u002Fbuild123d-mcp\u002Fblob\u002Fmain\u002Fsrc\u002Fbuild123d_mcp\u002Fskills\u002Fb123d-edit\u002FSKILL.md",[],[74,75],"edit","b123d edit",{"skillNo":77,"name":5,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":7,"ownerLogin":8,"ownerAvatarUrl":9,"ownerAccountNo":10,"ownerDisplayName":11,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":78,"categories":79,"keywords":80,"publishedAt":25},"01M71S0MW5PWTPJ7MHYYVARWB0","https:\u002F\u002Fgithub.com\u002Fpzfreo\u002Fbuild123d-mcp\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fb123d-drawing\u002FSKILL.md",[],[23,24]]