mesh-avatar-studio
Turn one illustration into an animated 2D mesh avatar with a coding agent and a local editor
翻譯準備中
AgenticCAD — agent-native CAD/CAM: describe it, tweak it in the browser, cut it. Free for non-commercial use.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗Describe the part; a Claude agent writes build123d code, the exact OCCT kernel builds it, and the browser shows it live. Click faces, edges and bodies to reference them in chat, or use the Fusion-style toolbar to model by hand — every manual operation is written into the same script, so the design is always one reproducible Python file. Then generate GRBL toolpaths (adaptive clearing, contours with tabs, drilling, 3D finishing), shop drawings, STEP/STL and G-code, and run the program on a Makera Z1 from the same window. CAM and machine control are experimental.
Website, docs and simulated tutorials: https://agenticcad.github.io/agenticcad/
Free for non-commercial use under the PolyForm Noncommercial 1.0.0 licence. Commercial use is A$99 per user per year — see Licence.
browser (three.js viewer · toolbar · sketch editor · chat) <-- websocket --> server.py (FastAPI)
|-- agent.py Claude Agent SDK session + in-process MCP tools
|-- cad_kernel.py build123d exec, exact B-rep, display meshing, export
|-- cam_kernel.py 2.5D/3D toolpaths, GRBL post
|-- drawing.py / threads.py / library.py / script_edit.py
`-- workspace/ designs, library, machines, tools, history, exports
Desktop app (macOS Apple Silicon .pkg, Windows .msi; both show the licence and terms to accept during install): download from the
website or the releases.
The app requires Claude Code installed separately
(it is Anthropic's tool and is not bundled); the app shows a setup page with the install and sign-in steps if
it is missing, and a "not signed in" banner with two fixes (run claude and log in, or paste an API key in Settings) when the login has expired. Builds are unsigned for now (macOS: if the installer is blocked, right-click → Open or System Settings → Privacy & Security → Open Anyway; Windows: SmartScreen → More info → Run anyway).
Data lives in the OS user-data folder (~/Library/Application Support/AgenticCAD, %LOCALAPPDATA%\AgenticCAD).
From source:
cd agenticcad
.venv/bin/python server.py # http://127.0.0.1:8765
Auth: the Agent SDK's bundled Claude Code binary uses your Claude Code login; set ANTHROPIC_API_KEY
to use an API key instead. The default model is Claude Opus 5.5 (claude-opus-5-5); change it in Settings ⚙ or with AGENTICCAD_MODEL=<model id>.
Setup from scratch: python3 -m venv .venv && .venv/bin/pip install -r requirements.txt.
Building the desktop app (Briefcase, config in pyproject.toml):
.venv/bin/pip install briefcase==0.4.5
.venv/bin/briefcase create macOS && .venv/bin/briefcase build macOS && .venv/bin/briefcase package macOS --adhoc-sign
The package GitHub workflow does this for macOS and Windows on every published release and attaches the
installers. cleanup_paths strips the Claude Code binary that the Agent SDK wheel ships, so the app never
redistributes it; agent.find_claude_cli() locates the user's own install.
cad)| tool | purpose |
|---|---|
build_model(code) | replace the script, rebuild, return summary (bbox, volume, largest faces) or traceback |
edit_model(edits?, append?) | change the existing script in place (exact-text replacements + code appended before result), rebuild; nothing applied if the build fails |
inspect_model(kind?, near?, limit?) | list faces: id, type, area, centre, normal, size, radius |
screenshot(view, highlight_faces?, show_edges?) | browser renders a 1024×768 PNG returned as an image block |
export_model(name?, formats?) | STEP / STL into workspace/exports/ |
get_code() | current script |
save_design(name?) | save the script into workspace/designs/ (only when the user asks) |
kit(action, ...) | the Design Kit: toc, search, read components and guides; note, save_guide, save_part into the workspace's kit |
slicer_info(machine?), slice_for_printing(...) | only when a slicer is installed: printers/profiles; slice for 3D printing and show the layers |
[Selected geometry] block with the face's
type/centre/normal/size so the agent can select the same face in code. Shift-click for several.workspace/images/ so the agent can re-read them. Dimensions in your text override what it estimates.⌂ for the home/fit view. Left-drag orbits, right-drag (or ⌘/Ctrl/⇧ + left-drag on a trackpad) pans, wheel zooms.⟩ or ⌘B hides it, ⟨ on the right edge shows it again. Remembered per browser.workspace/history/<ms>.py.A design is its build123d script. File ▸ Save / Save as… writes workspace/designs/<name>.py;
Open… lists them; New starts an empty design (a fresh workspace starts empty too); Import .py… uploads a script from disk;
Download .py gives you the current one. The File menu in the top bar shows the design name and a • when unsaved.
The last-open design is reopened on restart. The agent gets a [Note] when you open/import/undo or
edit the script by hand, so it re-reads the code before editing.
result may be a single shape, a dict {name: shape}, or nested dicts for components:
result = {"Base": {"Bracket": bracket.part, "Washer": washer}, "Pin": pin}
Bodies stay separate solids (STEP keeps them separate). A shape containing disjoint solids is split
into bodies automatically. The Browser panel (top-left) shows the tree: click a body to select it
(chip in chat), the dot toggles visibility, double-click zooms to it. Faces are numbered globally and
each face knows its body; inspect_model(body=...) filters by body and export_model(body=...)
exports one body.
Rename / delete a body manually: right-click a row in the Browser (or its ⋯) → Rename…, Delete,
Hide/Show, Zoom to, Export STEP/STL of that body. Rename and Delete rewrite the script directly
(script_edit.py, AST-based) when the body is a literal key in
result = {...} — instant, no agent turn, and Undo restores it. If the script doesn't name bodies
that way (e.g. result = part, or auto-split solids) the request is handed to the agent instead.
Deleting the last body at a level is refused.
name = number in the script is an editable field in the Parameters card under the Browser (↑↓ ±1, ⇧ ±10, ⌥ ±0.1).
Changing one rewrites that literal in the script and rebuilds; the agent is told. Agent tools:
get_parameters, set_parameters.I): click up to two faces/points in the viewer → point distance with Δxyz, minimum
face distance, plane gap, centre distance, angle between normals, hole axis spacing; click bodies in the
Browser for volume/mass/centre of mass, two bodies for clearance or interference volume. Agent tools:
measure, mass_properties (material names or g/cm³).D, or File ▸ Shop drawings…; material and sheet in ⚙ Settings): third-angle front/top/right + iso with hidden
lines, overall dimensions, hole callouts (n× Ø, THRU/depth), lettered holes with a coordinate table,
title block (size, mass, scale, date). SVG per body + assembly, plus DXF of the view geometry
(drawing.py). Agent tool: make_drawings.import_step("file.step") is added to the script), as a new design, or straight into the library.workspace/library/<part>/.
Browser ▸ + Part inserts one (adds from_library("name", param=value) as a new body); right-click a
body ▸ Save to library…; the Library pane: search, insert, delete, + Design (parametric), + STEP.
The agent sees the part names in every turn and has library (list/search/get/insert/save_design/
save_body/delete); it is told to check the library before modelling standard or reusable parts.measure accepts edge ids too.python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/python server.py # http://127.0.0.1:8765 (PORT env overrides)
Auth: the Claude Agent SDK uses your Claude Code login (or ANTHROPIC_API_KEY).
inspect_model reports the analytic face type and radius; the viewer's status line lists them.Mesh: draft / normal / fine in the toolbar re-tessellates without rebuilding. Face picking maps triangles back to
B-rep faces, so a click always selects the true face.CYLINDRICAL_SURFACE etc.; no triangles).export_model(tolerance, angular_tolerance)); it never reuses the display mesh.Experimental. Toolpaths are geometrically correct, machine-checked and can be simulated, but not optimised (long, conservative paths, many retracts). Inspect and simulate every program before running it. Modelling, export and drawings are the stable part today.
Agent-native, like the CAD side: a second script per design, cam.py, written by the agent against
cam_kernel.py and built with the build_cam tool. Saved with the design as <name>.cam.py.
workspace/machines/*.json (travel, max feeds, rapid, spindle range, safe/clearance Z,
tool-change policy pause|manual|none|split, collet, 4th axis, max_stepdown per material for the feeds
calculator, start/end G-code blocks) and workspace/tools.json (feeds, plunge, stepdown, stepover and the tool's
shape). Edit them in Settings ▸ Machines (a form per machine, or JSON) and Settings ▸ Tools (a list and an
editor that draws the tool to scale, with the feeds & speeds calculator; see New in 1.0), or ask the agent
(save_machine, save_tool).cam.py,
overrides({"Pocket": {"feed": 500, "stepover": 0.3}}), keyed by operation name ("Face#2" for the second op
with the same name), so they are saved with the design and the agent keeps them.The Machine workspace (Design · CAM · Machine in the top bar) connects to a networked machine: live status and
positions, the machine's camera, Hold / STOP, homing, jogging, go-to, the probe laser, dust collector and light, tool
changes and the auto Z probe. A status strip (state, position, tool, Control, Hold / Pause / Stop program, STOP) sits
over the camera with the jog pad on it, and the Inspector takes you through four steps: Prepare (connect, home,
tool change, accessories), Zero (what the setup wants next to what set the machine's origin, the 3D probe,
Find the block, Zero on the 4th axis), Run and Manual. On a Z1 with the rotary module the strip and the
phone's DRO show A° (work A over machine A) next to X, Y and Z, and the jog pad has an A+ / A− column with its own
step (1°, 10°, 45°, 90°; $J A… F… like the other axes, at half the module's rate, under the same guards). A is shown
and jogged only when the machine is known to have a 4th axis: the machine record has rotary or the status reports a
non-zero A.
First driver: the Makera Z1 (TCP 2222 framed protocol, UDP 3333 discovery, camera on ws://<ip>:82/ws_video;
machine_link.py documents what was read from the firmware and checked on a real machine). Other
machines later.
The machine's own routines do the real work; the app wraps them with checks (before: homed, Idle, Control on,
position and limits; after: the firmware's [PRB:] results, the reported tool and length, no alarm):
Change tool = Makera's M6 Tn: Z up, park, wait (fit the tool, press the front button or Confirm = M490.2),
then the machine measures it on its tool setter and stores the length. The same tool number again re-measures with
M491 (M6 to the same number does nothing). Every tool change is measured, the probe (T0) included, and the app
refuses jogs and go-to moves until the fitted tool has been measured in this session.
Origin = Anchor 1 = G10 L2 P0 X Y at the L-bracket's inside corner, as MakeraStudio does (Z untouched).
Auto Z probe = Makera's M495 X Y O0 F0 at the head's position: probe on, touch (500 then 100 mm/min), work Z
zero on that surface, probe off. Makera's routines switch the wired probe on and check it fired; probing without
them is how a dead probe gets driven into the stock (it happened once in testing; the e-stop caught it).
3D probe (experimental) = Makera's M480.n X Y Z D from where the head is: outside corners (.1–.4: back left,
back right, front right, front left), inside corners (.5–.8), pocket / bore centre (.9) and boss centre (.10). The
firmware moves, probes and sets the work origin; the app requires the 3D probe (T9999) fitted and measured, checks
the routine's travel against the limits, and sends a feed hold if the head leaves that area or Z drops more than
10 mm before the first touch. Lower to N mm above Z0 brings the probe to its start height (down only).
Zero on the 4th axis (Machine ▸ Zero, on a rotary setup): Makera's own routines and commands, one step at a time,
each behind a confirmation; nothing here moves X/Y/Z except the firmware's own probe. The card first reads the
module's position from the controller (Read from the controller: config-get sd coordinate.rotation_offset_x/y/z,
read-only; the numbers MakeraStudio set up for the module), then:
M495 X0 Y0 O0 (O without F) with the wired probe (T0)
fitted and measured: Z goes up, the head goes to the module's reference pad (Anchor 1 + rotation offset),
touches it fast then slow, and the firmware sets work Z0 rotation_offset_z below the pad: the chuck centreline.
Watched like the 3D-probe routines (the head must stay between where it started and the pad, Z above a floor).Safety rules in machine_link:
M490 (on the Z1 it drives the Z motor's pins through a tool-changer routine the machine
doesn't have) and M498.2 (erases the stored tool lengths and G54). No free-form G-code except one line at a time
from Machine ▸ Manual's console (each line confirmed; the commands above, machine settings, tool changes and probing
refused), and no spindle control buttons.play. Progress comes from the machine's status (|P: line,
percent, time) and is shown per operation, on the phone too; tool changes inside the program stop for you and are
measured by the machine. Air cut first runs it N mm higher first. Pause (suspend), Resume and Stop program
(abort, spindle off) stay available, as do Hold and STOP.machine_status: state, positions, a camera frame) and asks before it acts on it: see
assistant approval cards below.Phone control (Settings ▸ Phone control, off by default): a phone page for the Machine workspace — live camera, STOP, Hold / Resume / Unlock, Control, jog pad, tool change with a pinned Confirm bar while the machine waits, origin and auto Z probe, laser / dust / light. Turning it on starts a second listener on the local network (the desktop app stays on 127.0.0.1) that serves only that page and the machine endpoints, each call checked against a random key carried in the QR code's link; New link locks out old phones. The key can be turned off (anyone on the network can then use the page — your choice, and the pane says so). The phone uses the same MachineControl, so every rule above applies unchanged. Plain HTTP on the local network: turn it off when you don't need it.
tools/z1_emu.py is a Z1 emulator (protocol, homing, jogs, hold/halt, the M6 / M491 / M495 / M480 routines with a tool
setter, a stock block and an optional pocket, the camera) used by tests/test_machine_link.py; like the real firmware it does not stop
a jog past the end, so the tests prove the guard does.
program.add(...) line
in cam.py; on a 4th-axis setup the list adds Rotary rough, Rotary finish, Rotary profile and Gear teeth
(and drops adaptive). Chamfer edges puts a chamfer of a given width on the top edges of picked faces or the part's outline,
with a V-bit or chamfer tool, the tip size included in the offset; bigger chamfers take several laps. V-carve cuts
letters or a logo along their centrelines, as deep as each stroke is wide, so the top edge follows the outline and
corners come out sharp; Max depth gives wide strokes a flat bottom; taken in levels, linked at depth.0.1mm x 3.175mm 30deg) and
Fill sets them; Sharp point marks a true zero tip. A V tool without a tip Ø warns instead of being treated as
sharp.machine_control). Hold, pause, stop program and
STOP run at once; everything else is checked, then posted as a card in the chat and on the Machine page and runs
only when you click Approve (typed text never approves). It can also edit operations and workholding and add
operations (cam_edit).AgenticCAD does not bundle a slicer. If OrcaSlicer (or Bambu Studio, same CLI) is installed on the computer
the toolbar's Output menu gets Slice (P), ⚙ Settings gets a 3D printing section and the agent gets two extra tools; otherwise none of them exist.
/Applications/OrcaSlicer.app (macOS), Program Files\OrcaSlicer (Windows), orca-slicer on PATH
(Linux); override with AGENTICCAD_SLICER=/path/to/exe[::/path/to/profiles]. Printer, quality and filament
lists come from the slicer's own profiles (system vendors + your user presets); the printer selected in the
slicer is preselected.inherits chains are flattened (the CLI needs
flat JSON), your overrides applied, and the slicer is run headless (--slice 0 --arrange 1 --export-3mf).
Outputs land in workspace/slicing/ (G-code + 3MF, downloadable from the section).Model (default Claude Opus 5.5; any Claude model id, or the Claude Code default), effort (low…max), max steps per turn, thinking
summary on/off, web tools on/off, extra standing instructions, and MCP servers (stdio / http / sse
configs as JSON, enable toggles, live connection status from the session). Saved to
workspace/settings.json. Tabs: General, MCP servers, Extensions, Drawings & printing, Machines, Fixtures, Tools (the CAM libraries), Phone control. "Save & restart agent" starts a fresh session with the new options (the agent
is told its earlier chat history is gone). API: GET/POST /api/settings, GET /api/agent/status,
POST /api/agent/restart.
Fusion-style 2D sketches on a face or base plane, drawn in the viewer: select a planar face (or none for XY/XZ/YZ + offset) → ✎ Sketch. The camera snaps square to the plane. Tools (keys in brackets):
Line (L) / Arc (A): one closed profile of lines and three-point arcs. Click points; switch between line and arc at any time (an arc takes its end point, then a point it passes through); click the first point, or press Enter, to close.
Rectangle (R), Circle (C), Slot (S) by two clicks; Select (V) shows handles to drag corners, centres, radii and profile points, and Delete removes the selected item.
Snapping: sketch points, the model's corners, circle centres and edge midpoints, edges lying in the sketch plane (drawn dashed), the origin, horizontal/vertical alignment with existing points (dashed guides), then the grid. A marker and label show what it snapped to; hold Alt to place freely, or untick "model".
Typed dimensions: while a shape is in progress just type a number: length and angle for lines and slots, width and height for rectangles, diameter for circles (Tab to switch, Enter to place).
The item list edits numbers directly (profiles list their points), toggles add/subtract per item, and removes items.
Constraints: pick points, lines and circles with Select (shift-click to add more), then use the Constrain row: coincident, point on edge or circle, horizontal, vertical, parallel, perpendicular, tangent, equal, concentric, midpoint and fix. Dim (D) dimensions what's picked: a line's length, a circle's diameter, an arc's radius, the distance between two points or from a point to a line, or the angle between two lines. Type the value, and double-click a dimension to change it later.
Drawing adds the obvious constraints for you: horizontal and vertical segments, and points snapped onto existing sketch points.
The solver keeps everything satisfied while you drag or edit numbers. The status line shows the remaining degrees of freedom, and fully constrained items turn white. A constraint that conflicts with the others, or is already implied by them, is refused with a message. The constraint list removes them (× or Delete), and deleting an item removes its constraints.
Finish writes the sketch into the script as a block
# sketch:sketch1 {"plane": {...}, "items": [...], "constraints": [...]}
with BuildSketch(Plane(origin=..., x_dir=..., z_dir=...)) as _sketch1:
with Locations((5, 0)):
Rectangle(20, 10)
...
sketch1 = _sketch1.sketch
# /sketch:sketch1
so it is a normal build123d Sketch variable the agent can extrude / cut / revolve, and the JSON
header lets the editor reopen it (Browser ▸ Sketches ▸ double-click or ⋯). Sketches render as cyan
outlines and can be selected as chat chips. The agent edits sketches through the sketch tool
(list/get/set/delete, same item and constraint format), so anything it draws stays editable by you and vice
versa. Constraints are solved on save, and a conflicting set is refused.
The agent can extend the app itself. An extension is one Python file in <workspace>/extensions/ (the agent writes it
with extension save, which refuses a file that doesn't load or registers nothing); build123d, the script helpers
and kit are pre-imported, plus four decorators:
@helper # callable from any design script
def wall_thickness(shape, point): ...
@tool("hole_report", "Every hole with diameter, depth and thread") # agent-callable; ext_hole_report after a restart
def hole_report(ctx, body: str = None): ... # (ctx, **args); the JSON schema comes from the signature
@panel("Bolt pattern", icon="✣", description="Holes on a pitch circle")
def bolt_pattern(ctx): # re-rendered whenever one of its controls changes
s = ctx.state
return ui.panel(ui.picker("face", "Face", what="face"),
ui.number("pcd", "PCD", s.get("pcd", 40), min=1, unit="mm"),
ui.button("Apply", call="apply", primary=True))
@on_build # after every successful build; return warnings
def check(ctx): ...
Panels are declared, not coded: a JSON schema of sliders, numbers, selects, checkboxes, face/body pickers (click →
next click in the viewer fills it), tables whose rows select the face they describe, key/values, badges, images and
buttons. The browser renders them from the Extensions menu in the Design toolbar with the app's own styling, so they keep working
across releases. Everything a panel or tool does to the model goes back through a result dict (set_params, wrap
a body's expression, append / edits, code, select, highlight, chat to ask the agent, ui to show more),
so extension code stays plain synchronous Python with read access to the model (ctx.model, ctx.bodies,
ctx.faces, ctx.params(), ctx.holes(), ctx.selection). show_panel shows a one-off table or report without
saving anything. Files hot-reload; Settings ▸ Extensions lists them (enable, disable, view code, delete) and a
broken file shows as FAILED TO LOAD instead of breaking a design. The full API reference is extension api.
Built-in examples, also the reference the agent reads: Parameters (every numeric parameter as a slider with live
rebuild), Hole report (Ø, depth, thread and position of every round hole; click a row to select it) and Bolt
pattern (pick a face, set PCD / count / Ø / angle, Apply writes a pattern_circular cut into the script).
Named variants of a design are parameter overrides declared in the script:
plate_l, plate_w, wall = 60, 40, 8
configurations = {"Small": {"plate_l": 40, "plate_w": 30}, "Large": {"plate_l": 120, "wall": 10}}
The Parameters card shows a selector (Default plus every variant); choosing one rebuilds the viewer with those values
while the script's literals stay as they are, and editing a number while a variant is active edits that variant.
Manage… opens a table to add, edit and delete variants and to Export all: every variant and the Default, built
with real threads, as exports/<design>-<name>.step / .stl. The agent has the same through its configurations
tool (list / set / delete / activate / export), and can write the dict directly. The active variant is remembered per
workspace.
One row above the viewer in the Design workspace: Create Box B / Cylinder C / Sphere O / Sketch K /
Revolve R / Loft G / Sweep W, Modify Press/Pull Q / Hole H / Fillet F / Chamfer X / Shell L /
Move V / Mirror M / Pattern N, Assemble Joint T, Inspect Measure I / Section S / Interference J;
the less-used tools sit under each group's More, and Output (Render E, Drawings D, Slice P) and
Extensions are menus. Hover for a tooltip with the shortcut and what to click. A tool opens its command in the
Inspector (right column), which walks you through it: step indicator, a live prompt ("Click a face…"), chips for what you picked (× to drop),
fields with units (↑↓ ±1, ⇧ ±10, ⌥ ±0.1), ↵ OK, ⌫ removes the last pick, Esc cancels. Every operation is written as code on the body's
expression in result — e.g. _bracket = bp.part hoisted before result, then
"Bracket": _bracket + extrude(_bracket.faces().sort_by_distance((9, 0, 24))[0], amount=5, dir=(0, 0, 1))
— so the design stays a script, Undo works, and the agent sees a [Note] of what you did.
Primitives are placed with their base on the clicked face along its normal; holes can be plain,
counterbored, or tapped (M2…M12); fillet/chamfer take edges or whole faces; shell opens the picked faces.
pattern_circular(peg, count=6, angle=360, axis=...), mirror_about(...),
revolve(...), loft([...]), pipe(path_wire([...]), diameter=6), usable by the agent too.screenshot takes a section too.T, or in the script): revolute(name, bodies, axis, parent=, limits=),
slider(...), couple(follower, leader, ratio) for gear trains and rack and pinion, and drive(joint, start, stop, seconds). A joint with parent= rides on its parent, like planets on a carrier. The Joint tool takes a circular
edge (revolute) or a straight edge (slider) and writes the line for you. ▶ Animate in the bottom bar plays
the drive, gives every free joint a slider, and Check collisions runs the drive through its range looking for
parts that hit each other because of the motion. Overlaps that already exist at rest, such as thread engagement,
don't count. The agent has the same check as check_motion.explode({"Lid": (0, 0, 40)}, axis="Z") in the script sets
explicit offsets and the default mode.appearance({"*Screw*": "black oxide", "Housing": "black anodised", ...}) in the script, about 30 named
materials or , with sensible defaults guessed from body names (screws, coils,
magnets, housings, PCBs…). writes a high-resolution PNG; captures a spin,
explode-and-return and/or the motion as WebM (MP4 where the browser only records that). Both are saved in
exports/renders. The agent's takes , and .Pos(...) * Rot(...) * (...).
All of these rewrite the script (script_edit.add_body / wrap_body_expr), so the design stays one
script and the agent is told what you did.Scripts, files and the kernel are always millimetres. Settings ▸ Units picks how numbers are shown and how the
agent reads a bare number from you: automatic follows the computer's timezone (US zones → inches, everything else
mm), or force mm / in. inch (= 25.4), IN, ft, thou and mm are pre-imported, so a script mixes freely:
plate_l = 2.5 * inch next to plate_t = 6. The Parameters card shows each parameter in its own unit (inch
parameters are edited in inches and written back as x * inch), the viewer stats and measure readouts use the
display units, and shop drawings are dimensioned in them (make_drawings(units="in") overrides per sheet).
Sizes are ISO (M4, M6) or Unified (1/4-20, #10-32, 3/8-16; a bare 3/8 means the coarse series, decimal
.25-20 works too). thread(size) / iso(size) return major, pitch, tpi, tap drill and clearance holes in mm (UN
clearance = ASME close/normal/loose); tap, tapped_hole, bolt, nut, washer take either kind, with inch hex/socket/
nut/washer tables from ASME B18. Drawings call out 1/4-20 UNC THRU alongside M4×0.7 ↧8.
ISO metric tables (pitch, tap drill, clearance fine/medium/coarse, hex/socket/nut/washer sizes) and
script helpers: iso("M4"), tap_drill, clearance_dia, tapped_hole(size, depth, at=..) (cosmetic
cutter), tap(part, size, at=.., depth=.. | through=True, real=False) (cuts, and with real=True models
the helix via bd_warehouse), bolt(size, length, head=hex|socket|none, real=False), nut, washer.
Tapped holes are registered per build: the model summary lists them and shop drawings call them out
("M4×0.7 THRU" instead of "Ø3.3").
Real threads are trimmed to the part's material and joined with a checked union, so a modelled tapped hole never
silently empties the part. While the agent works, real=True threads build plain ("draft threads", about 5× faster
rebuilds on a thread-heavy assembly) and the app models the real helices once at the end of the turn; the
draft_threads setting turns that off.
Pre-imported in scripts: spur_gear(module, teeth, thickness, bore=0, pressure_angle=20, hub_d=0, hub_h=0, keyway=(w, depth)) → involute spur gear solid (Z up, tooth 0 on +X); involute_gear_profile(module, teeth) → closed
Face; gear_dims(module, teeth) (pitch, outside, root, base diameters); gear_centre_distance(module, za, zb).
The outline is one closed Polyline, so it never hits build123d's "Edges are disconnected" (the classic failure of
hand-rolled involutes, which the build error now hints about). The agent is told to build complex parts one body per
build_model call and to use these helpers rather than derive tooth flanks itself. planetary_layout(module, sun, planet, n) gives the
ring tooth count, ratio, planet positions and the mesh phasing (checked interference-free for even and odd tooth
counts); sketch a ring gear as an outline minus involute_gear_profile(m, z_ring, addendum=1.25, clearance=0), or use
kit.ring_gear / kit.planetary_stage. All gear and thread helpers are safe inside BuildPart/BuildSketch blocks.
Scripts have a pre-imported kit of ready-made standard components, and the agent a kit tool to search and read
them alongside design guides. 63 components (ISO fasteners with optional real threads, ball bearings by designation
with balls and cages, circlips, keys and couplings, gears and planetary stages, GT2 pulleys and lead screws, NEMA
steppers with full internals, servos, boards with exact mounting holes, fans, T-slot extrusions, printable enclosures,
real helical springs, hinges, knobs, O-rings with groove cutters, fittings) and 27 guides (gears, bearings, fits,
materials, threads, motors, boards, frames, enclosures, springs, linkages, O-rings, FDM/resin printing, CNC, sheet
metal, laser cutting, moulding, build123d pitfalls). Each workspace has its own kit layer (<workspace>/kit/) where
the agent adds notes, guides and components; its TOC.md is generated and lists what that workspace uses most.
motor = kit.nema_stepper(17, 40, detail="full") # dict of parts, front face at z = 0
stage = kit.planetary_stage(0.6, 18, 18, 3, face_width=8) # sun, phased planets, ring
result = {"Motor": motor, "Stage": kit.place(stage, Pos(0, 0, 13)),
"Bearing": kit.place(kit.ball_bearing("688ZZ"), Pos(0, 0, 29))}
version.py holds the version (shown next to the AgenticCAD name at the left of the top bar; click it for the changelog). Every
feature or fix gets a line in CHANGELOG.md under the next version, and a release is a bump in
version.py plus a git tag:
# after editing version.py and CHANGELOG.md
git commit -am "Release 0.10.0" && git tag v0.10.0
Minor bump for features, patch bump for fixes, major bump when scripts or workspaces need changing. /api/version serves both to the UI.
.venv/bin/python -m pytest -q # 123 unit/API tests, ~20 s, no Claude calls
.venv/bin/python evals/run.py # agent evals, 34 cases (the 3 showcase assemblies are long: run them with --filter showcase); needs a Claude login or API key
.venv/bin/python evals/run.py --filter cam --model claude-sonnet-5 --repeat 3
AGENTICCAD_LEGACY_BUILD=1 .venv/bin/python evals/run.py --ids cad_long_script_edit --repeat 3 --label legacy
vs the same without the variable measures whole-script rebuilds against edit_model (cost and wall time per case are
in the result JSON/MD under evals/results/)..github/workflows/smoke-windows.yml, after every release build or by hand): installs the
released .msi on a clean Windows runner, installs Claude Code with Anthropic's installer, starts the installed app
and checks it through its API (tools/ci/smoke.py: build with inch threads and a gear, STEP/STL, inch drawings), then
gives it the ANTHROPIC_API_KEY repo secret through Settings and has the real agent build a part
(tools/ci/agent_smoke.py, a few cents per run). Forks never get the secret and skip the agent step.tests/): kernel (bodies, ids, exact normals, exports, measurement), script editing
(params, rename/delete/add), CAM (holes, sections, every op, arc post, machine checks, feeds, and an
independent fine-grid engagement audit of the adaptive op), drawings, library, and the FastAPI/WebSocket
API with the Claude session disabled (AGENTICCAD_NO_AGENT=1, AGENTICCAD_WORKSPACE=<tmp>).evals/): the real agent runs headless in a throwaway workspace (the screenshot tool is an
offscreen matplotlib render), one fresh session per case, graded by deterministic geometry / program /
answer checks (harness.py helpers: bbox, bodies, holes, volume, params, script contents, op params,
G-code validity, tools used, answer regex). Each run writes evals/results/<stamp>.json|.md with pass
rate, cost, steps and time per case. Exit code 1 on any failure (--allow-fail to override).
Add a case by appending a Case(...) to evals/cases.py.Status (v1.1): working end to end on a single machine for a single user — design by chat, by hand, or both; multi-body designs and assemblies; constrained sketches; threads; measurement; drawings; part library; extensions; CAM with GRBL, Makera, Haas and LinuxCNC posts; Makera Z1 control; unit tests and graded agent evals. The modelling kernel is exact and the toolpaths are checked and simulated, but CAM and machine control are experimental: they have been run on a real machine only by the author so far.
Contributions and issues welcome at https://github.com/agenticcad/agenticcad.
AgenticCAD is free for non-commercial use under the PolyForm Noncommercial License 1.0.0: personal projects, research, education, hobby making, and use by charities, schools and public institutions. See the LICENSE file for the exact terms.
Commercial use (using it in a business, in products or services, or as part of paid work) needs a commercial licence: A$99 per user per year, one licence per person who uses it, same software. Buy online for the number of users you need; the receipt is your licence record and there is nothing to enter in the app. Volume or site licences: [email protected]. Details: https://agenticcad.github.io/agenticcad/#licence
section(part, z)silhouettestock_minus(setup, part, z)holes(part)face_polygon(model.get_face(id))face, contour (outside/inside/on, depth passes, tabs, climb), pocket (concentric,
islands), drill (peck), parallel3d (heightmap raster finishing, any tool profile), chamfer_edges, vcarve,
and on the 4th axis rotary_rough, rotary_finish, rotary_wrap, rotary_profile and gear_teeth (below).
All in model coords.parallel3d(..., pattern="parallel" | "radial" | "spiral", one_way=True): straight passes at angle, rays from center (the region's centre by default) or one continuous
spiral out from it; one_way cuts every pass in the same direction (climb throughout, one uniform lay) and rapids
back just over the surface instead of zig-zagging. face(..., angle=45, one_way=True) honours its pass angle, takes
one_way the same way, and continues across depth levels instead of returning to the first row. Both are in
Add operation and the Inspector (Pattern, Angle, One way).Program.gcode() → GRBL dialect (G21 G90 G94 G17, G54, M3 S, G0/G1, F on change, M6/M0 tool
change pauses, M30). WCS origin choices: stock-top-left / stock-top-center / stock-bottom-left /
stock-bottom-center / model-origin / (x, y, z); on a rotary setup rotary-axis-left (default) or rotary-axis-center.
Checks: machine travel, spindle range, feeds clamped, cutting below stock bottom, multi-tool with
tool_change=none, and for 4th-axis programs the ones listed under 4th axis below. Post tolerance per kind:
roughing operations are posted at the machine's arc_tolerance; finishing operations (parallel3d, vcarve,
chamfer, rotary_finish, rotary_profile, gear_teeth, and anything cut with a V or chamfer tool) at
FINE_POST_TOL = 0.005 mm, so the roughing tolerance never becomes the surface accuracy of a facet edge. Every
point the collinear merge drops is tested against the chord that replaces it, and straight runs with rounding noise
are never fitted as huge-radius arcs.cam.py is in Code (right column).
Send to machine → opens Machine ▸ Run with the setup's operations.machine.arcs, machine.arc_tolerance) in the form the machine's controller takes: machine.arc_format "ij" (centre offsets, every controller) or "r" (radius word; GRBL, Haas, LinuxCNC, not the Makera firmware), machine.helical_arcs for ramps. cam.CONTROLLERS lists what each post's controller accepts (arc forms, helical arcs, line length) with its source; collinear moves are merged. Lead-in/out: tangential quarter arcs on contours
(lead=), start mid-way along the longest straight edge. Adaptive: see above. Rest machining:
rest_from= on pocket / adaptive (2.5D, morphological opening) and parallel3d (3D tip-map difference).
Feeds & speeds: feeds(tool, material, machine) / apply_feeds(...), 13 materials, spindle and feed
clamping, radial chip thinning; calculator in Settings ▸ Tools ("Apply to Tn") and per operation in the CAM Inspector; agent tool feeds_speeds.Setups (orient="top" | "bottom" | "front" | "back" | "left" | "right"), each with its own WCS (G54, G55, …). Moves are in the setup frame; setup.view(part) gives the part
as the machine sees it for section/holes. Between setups the post stops the spindle, parks and pauses for the
operator to flip or re-fixture (Makera M600, GRBL M0), then restarts the spindle.Setup(..., rotary=True) on a machine with machine.rotary.
Indexed 3+1 (Setup(..., rotary=True, a=90); setups on the same mounting share one WCS and just turn A, and every
3+1 operation indexes A to its setup's angle by the short way, even after a continuous one) and
continuous 4-axis: rotary_rough (rings or lines, from a radial height map, leaving stock), rotary_finish
(lines, rings or a continuous spiral, ball or flat), rotary_wrap (pocket/contour a 2D pattern wrapped onto a
cylinder), rotary_profile and gear_teeth. Stock.cylinder(d, length) for bar stock. Rings alternate direction
so A stays within one turn, and after a spiral the Makera post drops the whole turns (G92.4 A0 S0, what
MakeraStudio does) so the next index is a short move.
rotary_profile(setup, tool, profile): a hex, square, cam, splines or any cross-section from round stock.
profile is a shapely polygon in the setup's Y–Z plane (the section perpendicular to the axis, the same at every
X in x_range); for every A the tool tip sits at the lowest Z that keeps the whole tool (its real profile) clear of
the turned section, cut as rings from the stock radius down stepdown per level. Concave corners tighter than the
tool and slots narrower than it are reported as unreachable. regular_polygon(sides, across_flats, rotation=0)
gives the polygon (a flat faces up at A = 0).
bar = Setup(machines["Makera Z1 + 4th axis"], Stock.cylinder(20, 60), rotary=True)
program.add(rotary_profile(bar, tools[1], regular_polygon(6, 16), x_range=(5, 45), stepdown=1.0))
gear_teeth(setup, tool, module, teeth, width=None, helix=0, ...): involute spur or helical teeth with a
gear cutter tool (type: "gear_cutter", whose module must match). The cutter works from the side of the
blank, where its form lines up with the tooth space: the cutter's axis is vertical, its disc is centred on the axis
height, and it cuts at the 3 o'clock side of the blank (side="+y" or "-y"). Y is the depth (passes of
stepdown mm of radial depth towards the axis, the last to the root radius plus leave minus backlash), X is the
feed across the face width (one direction only, climb, with a lead-in/out of the cutter radius + 2 mm beyond both
faces and the return made clear of the blank), and A indexes 360/N between spaces from a0. Helical teeth are A
interpolated along X (helix in degrees, + = right hand). Warnings: wrong pressure angle or cutter number for the
tooth count, a disc narrower than the space at the tip line, a blank bigger or smaller than the tip diameter, HSS
surface speed over 80 m/min, lead-in reaching the chuck jaws or the tailstock. The simulation cuts it exactly: a
20-tooth module-1 gear comes out within 0.02 mm of the model.
blank = Setup(machines["Makera Z1 + 4th axis"], Stock.cylinder(22, 12), rotary=True) # Ø22 = tip Ø of m1 z20
program.add(gear_teeth(blank, tools[8], module=1.0, teeth=20, stepdown=0.75)) # T8: a module-1 gear cutter
makera post
written against the controller firmware: lines ≤ 63 characters, no N numbers, M6 Tn on one line (the machine
measures the tool after each manual change), M3 only after a tool is active, G28 park, M600 between setups,
9 work offsets, no arcs while A moves, and A-axis feeds computed the way the firmware interprets F on mixed
linear + rotary moves. Collet size is checked (1/8" standard).ramp=, 0 to plunge) — pockets along the
innermost ring that is long enough (a helix on a small ring), contours along the profile with one entry per ring
and no retract between passes — and face passes plunge clear of the stock; helical ramps post as G2/G3 arcs.simulate_cam (agent) or Simulate on the CAM timeline runs a material-removal simulation of all
visible ops or one: a two-sided height map for top/bottom setups, a radial map for 4th-axis setups. The viewer
shows the stock being cut in step with the slider, with gouges in red and material left in amber on the last
frame; the summary reports removed volume, gouges (depth, area, which op), material left on the part, stock
left outside it, and rapids through material. Side setups (front/back/left/right) are not simulated yet.
On the 4th axis the cutter cuts with its real profile up to its flute length only: the body above the flutes, a
neck, the shank and the holder stack are checked against the remaining bar (contact, not cutting); the chuck body,
the jaw ring and the tailstock (cone, barrel, block) are solids about the axis, placed from the machine's model and
the setup's stock (the tailstock follows the stock length), and the whole tool stack is checked against them in 3D
for every move, rapids included; gouges are attributed to the operation that made them; and operations on
different 4th-axis mountings (a different stock, axis, origin or machine) are refused in one simulation: simulate
the setups separately. Not checked yet: the module's housing and rail, and the gaps between the jaws (the jaws are
a full ring at their tips, the safe side).A) or the viewer's Machine toggle shows the
program's machine around the stock and plays the simulation on it: head, table and chuck move as they would, the
tool hangs from the spindle, and any part the simulator saw collide flashes red at that move. The simulation itself
checks the whole stack above the flutes (shank, collet nut, nose, collar, head) against the remaining stock, the
tool tip against the bed under the spoilboard, and on 4th-axis jobs the whole stack against the chuck body, the
jaws and the tailstock in 3D for every move; contacts are listed in the summary with the op, move, position and
depth. Every number carries its source and
accuracy tier (verified / reference / estimated) in Settings ▸ Machines ▸ Model, where measured values
(nose profile, stickout, spoilboard, clearance) can be entered per machine. On a machine with a 4th axis the same
pane has a Rotary axis row: Y (mm from the bed centre, + towards the back), Z (mm above the bed), chuck gap
(the stock's end face this far in front of the chuck body face) and tailstock fitted, with a source line next to
them: reference model — probe it on the Machine page or measure and type it here, from the machine's config
(with the machine Y/Z it was converted from), probed on , or set here. The JSON overrides take the same
keys (rotary: axis_y, axis_z in the bed frame, or axis_my, axis_mz in machine coordinates converted with
the anchor, chuck_gap, jaw_reach, tail, tail_gap, stock_length); Machine ▸ Zero's Zero on the 4th axis
writes the position it finds into this model for the simulation.workspace/machines/<slug>.machine.py, in the design-script namespace plus node(name, parent, axis, mode, stock, pivot), part(name, node, shape, material, collision) and machine(home, travel, clearance, nose, table, rotary, spoilboard, sources, notes), with helpers (box, cyl_z/x/y, shell, bed_holes, import_step for vendor CAD).
The built-ins live in machine_scripts/ and are the fallback for any machine without its own
script, so Code ▸ machine.py always has a starting point: edit, Run, and the machine has its own model.
The Machine view section of a setup's Inspector (CAM workspace) picks any library machine, shows or hides it and
opens or closes its doors; door nodes (node(..., door="hinge"|"slide", ...)) animate in the viewer. Settings ▸ Machines ▸
Model has Show in viewer and Edit as script…; the agent's
get_machine_code / build_machine do the same from chat ("add my Shapeoko 5 Pro to the library and model it"), and
photos dropped into the chat are used as references: the built-in Z1, Carvera Air and Haas VF-4 models were made that
way from the makers' product photos, every number tagged verified / reference / estimated.Setup(..., fixture="6\" Kurt-style vise", fixture_params={"parallel": 20})
holds the stock in a vise, on clamps or on a plate; the stock sits at the fixture's work origin, the viewer draws
it around the stock and on the machine, and the simulator reports the tool or holder touching any fixture part as a
collision. Built-ins: Makera low-profile vise, Carvera Air vise, 4" screwless vise, 6" Kurt-style vise, tooling
plate, step clamps. Fixtures are scripts (fixture_scripts/, workspace/fixtures/<slug>.fixture.py) like machine
models: part(...) + fixture(name, work_origin, clamp_axis, ...), editable in Code ▸ fixture.py, listed in
Settings ▸ Fixtures, picked per setup in the setup Inspector's Workholding, and written by the agent with build_fixture. Several
fixtures can be on the table at once (fixtures=[{"name", "at", "rot", "params"}] or the setup Inspector's
Workholding: pick, move, turn, add, remove); every one is drawn and collision-checked, and overlaps are warned about. The
Makera Z1 low-profile vise is modelled from Makera's dimensions (226 × 103 mm, jaws along X to 130 mm, thin stock
on 20 mm ledges) and bolts to the bed with the spoilboard removed. The Z1's spindle head comes from Makera's own
simplified CAD, and a machine's nose stack can describe an off-centre head as boxes about the spindle axis, so the
collision check sees it where it really is.G10 L2 P0 Y at machine Y = anchor1_y + rotation_offset_y, the controller's own sum
(Set Y0 on the axis), or a typed machine Y when the offsets are unknown. Nothing moves.G92.4 A0, then G10 L20 P0 A0 so work A reads 0); turn the
chuck with A+ / A− first if you want a feature up. Shrink A (G92.4 A0 S0) drops whole turns from the A count.
Nothing moves.With Z0 on the centreline the stock top sits at Z = the stock radius, so the card offers a Z floor above it before you jog. The same three commands are on Machine ▸ Manual's 4th axis row (A0 here, Shrink A, 4th-axis Z probe). Before a run, the CAM program's warnings cover the rotary checks (origin off the centreline, Y/Z reach, A speed, swing and length): read them in the CAM tree before Send to machine →.
Machine-specific values come from the machine's live config (MakeraStudio's downloaded ControllerConfig.txt):
this Z1's Anchor 1 was -191.28, -194.45 against the firmware default -192.4, -194.3; the rotary module's offsets
(coordinate.rotation_offset_x/y/z) are read from the controller the same way.
{color, metalness, roughness}screenshotposeexploderendercheck_interference, which the agent is told to run on
assemblies.