Documentation · v1.4

AgenticCAD documentation

Everything the app does, how it stores it, and how to drive it by chat, by hand, or from the agent's tools. For a guided walk-through, start with the tutorials.

Overview

AgenticCAD is a desktop CAD app with a Claude agent built in. You describe the part you want, the agent models it, and you watch it appear in a 3D viewer. From there you steer it however is quickest: keep talking, point at faces and bodies in the viewer, tweak dimensions in a parameters panel, or use a Fusion-style toolbar to press, pull, hole, fillet, revolve, loft, sweep, mirror, pattern and sketch (with constraints) by hand. Multi-body designs are assemblies: section them, check them for interference, give them joints and watch them move, explode them, render them.

Every design is exact solid geometry, so what you export is production-ready: STEP for other CAD tools and suppliers, STL at the resolution you choose for 3D printing, shop drawings with hole tables, and, if you have OrcaSlicer installed, a sliced print preview with time and filament estimates. An experimental CAM workspace generates toolpaths for small mills and routers (GRBL, or the Makera Z1 and Carvera Air), with multiple setups, a 4th axis and a material-removal simulation. And when the app lacks a tool, the agent can write one: extensions add helpers, agent tools, build checks and panels from a single Python file.

You need a Mac or Windows PC, the app (or the source), and a Claude sign-in: either Claude Code logged into your Anthropic account or an API key. Nothing else is required; the slicer is optional and only used if it is already on your machine.

One design and one agent conversation open at a time, on your own machine. For how the pieces fit together, see Architecture.

Desktop app

The desktop app is the same program in a native window with Python bundled. Two things to know:

Your data lives in the OS user-data folder: ~/Library/Application Support/AgenticCAD on macOS, %LOCALAPPDATA%\AgenticCAD\AgenticCAD on Windows (designs, library, machines, tools, history, exports, settings). Set AGENTICCAD_WORKSPACE to move it. The app picks a free localhost port; the UI is the same page you would see from a source install, so everything else in these docs applies. Intel Macs and Linux: run from source below.

Install from source

Requirements: Python 3.12, a Claude Code login (the Agent SDK's bundled binary uses it) or an ANTHROPIC_API_KEY, and a modern browser.

# unzip the release (or git clone https://github.com/agenticcad/agenticcad.git), then inside the folder:
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
.venv/bin/python server.py            # http://127.0.0.1:8765
VariableEffect
PORTListen port (default 8765).
ANTHROPIC_API_KEYUse an API key instead of the Claude Code login.
AGENTICCAD_MODELModel id override (the Settings dialog is the usual way).
AGENTICCAD_WORKSPACEWorkspace directory (default ./workspace).
AGENTICCAD_NO_AGENT=1Run the server without a Claude session (used by the tests).

The workspace

The AgenticCAD workspace in a real session
The workspace: the top bar (File menu, Design · CAM · Machine, Assistant, Settings), the Design toolbar, the Browser and Parameters on the left, the viewer with the ViewCube, and the right column (Inspector · Assistant · Code).

Several designs at once

Since 1.4 every open design is a tab. Each tab is a whole design: its model, its own CAM program and simulation, its own assistant (a separate Claude Code session working in that design's own folder) and its own view, so you can lay out a fixture in one tab while another design's assistant writes its CAM.

Coming from the AgenticCAD Tabs test build (1.4.0a1–a2)? On its first start 1.4 copies the designs saved only in the test build's folder, and the files they import, into its own, without overwriting anything; the test app and its folder can then be deleted.

Design as script

A design is one build123d script that assigns result. The kernel executes it in a namespace that also provides import_step, from_library and the thread helpers.

# Mounting plate with a bossed bore
plate_l, plate_w, plate_t = 60, 40, 8
boss_d, boss_h, bore_d = 20, 15, 8
with BuildPart() as plate:
    Box(plate_l, plate_w, plate_t)
    with Locations((0, 0, plate_t / 2)):
        Cylinder(boss_d / 2, boss_h, align=(Align.CENTER, Align.CENTER, Align.MIN))
    Hole(bore_d / 2)                 # note: Hole takes a radius
result = {"Plate": plate.part}

Chat and selection

Click a face, an edge or a body in the viewer (or a row in the Browser) and it becomes a chip on the composer. ⇧-click for several. The sent message keeps its chips and the server expands them into a [Selected geometry] block: face id, body, analytic type, centre, normal, size, radius and the clicked point. Because face ids are renumbered on every rebuild, the agent is told to reference faces by geometry (faces().sort_by_distance((x, y, z))[0]) and to re-inspect after building. Hover a chip later to see a marker where that face was; click it to reselect the matching face by type and centre.

Selected faces stay lit (dimmer orange) while the agent works, until the next rebuild or click. Sketches are selectable as chips too.

Reference images

Attach photos, napkin sketches or old drawings with 📎, by dragging onto the composer or viewer, or by pasting. Images are downscaled in the browser (≤1600 px JPEG), sent inline so the agent sees them directly, kept as thumbnails in the bubble and saved to workspace/images/. Dimensions you type override what it estimates from the picture.

Bodies and the Browser

Toolbar tools

The toolbar above the viewer (Design workspace) gives you Fusion-style manual modelling without an agent turn; less-used tools sit under each group's More. A tool opens its command in the Inspector, 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.

ToolKeyPicksWritten to the script
Box / Cylinder / SphereB C Oa face (or the grid)a new body placed with its base on the face along its normal
SketchKa planar face, or none for a base planea sketch block (see Sketches)
Press/PullQa planar face; drag the handle or typebody + extrude(face_at(pt), amount, dir), or - to cut
HoleHa point on a facehole(body, d, at, depth|through) with counterbore/countersink, or tap(body, "M4", …)
Fillet / ChamferF Xedges, or a face for all its edgesbody.fillet(r, [...]) / body.chamfer(l, None, [...])
ShellLfaces to openoffset(body, amount=-t, openings=[...])
MoveVa bodyPos(...) * Rot(...) * (body)
RevolveRa sketch; the axis is the sketch's X/Y, a world axis, or a straight edge you clickrevolve(sketch, axis=Axis(...), revolution_arc=360) as a new body, or joined to / cut from one
LoftGtwo to four sketches, in orderloft([sk1, sk2], ruled=…)
SweepWconnected edges of one body, or one face (its outline)sweep(sketch, path=path_wire([...])), or a round pipe(path, diameter, wall)
MirrorMa body; a base plane (with offset) or a flat facebody + mirror_about(body, origin, normal), or a separate mirrored body
PatternNa body; optionally an edge for the direction or a circular edge for the axispattern_linear(...) / pattern_circular(...): copies joined, a new body, or cut from another body (hole patterns)
JointTa circular edge (turns) or a straight edge (slides) on the moving partrevolute(name, bodies, axis, parent=, limits=) / slider(...), optionally couple(...); see Motion, explode and render
MeasureIvertices, edges, facesnothing; see Measure
SectionSa base plane, or a flat face to cut parallel tonothing; see Section and interference
InterferenceJall bodies, or those selected in the Browsernothing; see Section and interference

Face references use the point you clicked (faces().sort_by_distance((x, y, z))[0]), never face centres, so they survive most later edits. Every operation is undoable and the agent is notified.

Sketches

Select a planar face (or none for XY/XZ/YZ plus an offset) and press K. The camera squares up to the plane. Tools:

Toggle Subtract per item, edit numbers in the item list, then Finish. The sketch, with its constraints, is stored in the script as a block:

# sketch:sketch1 {"plane": {...}, "items": [...], "constraints": [...]}
with BuildSketch(Plane(origin=(0, 0, 4), x_dir=(1, 0, 0), z_dir=(0, 0, 1))) as _sketch1:
    with Locations((-18, 0)):
        Rectangle(14, 20)
    with Locations((-18, 0)):
        Circle(3, mode=Mode.SUBTRACT)
sketch1 = _sketch1.sketch
# /sketch:sketch1

So sketch1 is a normal build123d Sketch that you or the agent can extrude, cut with (part - extrude(sketch1, amount=-h)) or revolve. The JSON header lets the editor reopen it (Browser ▸ Sketches ▸ double-click). Sketches render as cyan outlines. The agent edits sketches only through its sketch tool, in the same item format, so anything it draws stays editable by you.

Units

Everything is stored in millimetres, but you work in whichever unit you like. Settings ▸ Units is automatic by default: a US timezone means inches, anywhere else millimetres; or force mm or in. That choice sets how the viewer stats, measurements, parameters and shop drawings read, and how the agent interprets a number you give without a unit. In scripts inch (= 25.4), IN, ft, thou and mm are pre-imported, so plate_l = 2.5 * inch can sit next to plate_t = 6; the Parameters card shows each one in its own unit and writes edits back the same way. make_drawings(units="in") dimensions one sheet differently from the default.

Threads

threads.py carries ISO metric tables (pitch, tap drill, clearance fine/medium/coarse, hex and socket head sizes, nuts, washers) and helpers available in every script:

Inch threads too. Unified sizes work everywhere a size is accepted: 1/4-20, #10-32, 3/8-16 (a bare 3/8 means the coarse series). thread("1/4-20") gives major, pitch, tpi, tap drill and ASME close/normal/loose clearance holes in mm; tap, bolt, nut and washer use ASME B18 hardware tables, and drawings call out 1/4-20 UNC THRU alongside M4×0.7.

HelperWhat it does
iso("M4"), tap_drill, clearance_diatable lookups
tap(part, "M4", at=(x, y, z), depth=6 | through=True, real=False, axis=(0, 0, -1))cuts the tap-drill hole from the surface point along the axis and registers the thread; real=True models the helix via bd_warehouse
hole(part, d, at, depth | through, counterbore=(D, h), countersink=D)plain hole with optional counterbore or 90° countersink
tapped_hole(size, depth, at)a cosmetic cutter you subtract yourself
bolt(size, length, head="hex"|"socket"|"none"), nut, washerfasteners as bodies

Registered threads appear in the model summary and in shop drawings as callouts such as M4×0.7 ↧6 or M4×0.7 THRU instead of Ø3.3.

Gears

Scripts can call spur_gear(module, teeth, thickness, bore=0, pressure_angle=20, hub_d=0, hub_h=0, keyway=(w, depth)) for a true involute spur gear (Z up, tooth 0 on +X), involute_gear_profile(module, teeth) for the closed 2D outline, and gear_dims(...) / gear_centre_distance(module, za, zb) to lay out a train. Meshing gears share module and pressure angle; rotate one by half a tooth pitch so the teeth interleave. Ask for it in plain words: “a 20-tooth module 2 gear, 10 thick, 8 mm bore, with a 20 mm hub”.

For complex assemblies the agent builds one body at a time, checking each build before adding the next, so a slip in one part does not cost the whole attempt.

Planetary sets. planetary_layout(module, sun, planet, n) gives the ring tooth count, ratio, planet positions and the rotation each planet needs to mesh; it raises if the planets can't be equally spaced or would collide. Sketch a ring gear as the housing outline minus involute_gear_profile(m, z_ring, addendum=1.25, clearance=0) and extrude it, or use kit.ring_gear and kit.planetary_stage.

Design Kit

Every script has a pre-imported kit of ready-made standard components, and the agent has a kit tool to search them together with design guides. It checks the kit before hand-modelling a standard part, then calls it in the script:

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)),
          "Screw": Pos(15.5, 15.5, 37) * kit.socket_screw("M3", 40, real=True)}
CategoryComponents
FastenersISO socket, button and countersunk screws, set screws, hex bolts, nuts, washers (optional real threads), heat-set inserts, standoffs
Bearings, shaftsball bearings by designation (608, 688ZZ, 6204-2RS, MR…) with rings, balls, cage and shields; circlips, E-clips, keys, collars, dowels and their cutters; rigid and jaw couplings
Transmissionspur and ring gears, complete planetary stages, GT2 pulleys, T8 lead screw and nut
MotorsNEMA 8 to 23 steppers (full internals for 14/17/23), hobby servos, 28BYJ-48, N20 gear motors
ElectronicsArduino Uno, Raspberry Pi 4 and Pico with exact mounting holes, fans with grille cutters, 18650 cells
Structure, enclosuresT-slot extrusions, corner brackets, T-nuts; a printable screw-lid box, bosses, vent slots, snap fits
Mechanisms, fluidhinges, compression and extension springs on their real helix, knobs, pull handles, cams, links; O-rings with groove cutters, tube, push-fit fittings, hose barbs

27 guides cover gears, bearings, shafts, springs, linkages, O-rings, fasteners, motors, boards, frames, enclosures, fits, materials, metric threads and stock sizes, FDM and resin printing, CNC, sheet metal, laser cutting, moulding and build123d pitfalls. Each workspace has its own kit layer in workspace/kit/: the agent adds notes to entries, saves guides and components there, and its TOC.md is regenerated with what the workspace uses most. Agent tool: kit(action, ...) with toc, search, read, note, save_guide, save_part.

Draft threads. While the agent works, real=True threads build as plain cylinders so rebuilds stay fast; when its turn ends the app builds the design once with the real helices (⚙ Settings key draft_threads).

Extensions

The agent can extend the app itself. An extension is one Python file in workspace/extensions/, written by the agent with its extension tool (a file that doesn't load, or registers nothing, is refused). 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
def hole_report(ctx, body: str = None): ...                          # schema 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): ...

Built-in examples, also what the agent reads for reference: 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). Ask for your own: "Write an extension with a panel that estimates mass and cost per body", "Add a build check that warns when a wall is under 1.2 mm".

Parameters

Every top-level name = number in the script is a field in the Parameters card under the Browser (↑↓ ±1, ⇧ ±10, ⌥ ±0.1). Changing one rewrites that literal and rebuilds. Library parts saved as designs keep their parameters, so from_library("bracket", width=30) works. Agent tools: get_parameters, set_parameters. API: GET/POST /api/params.

Configurations. Named variants are parameter overrides declared in the script: configurations = {"Small": {"plate_l": 40, "plate_w": 30}, "Large": {"plate_l": 120}}. 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's configurations tool lists, sets, deletes, activates and exports them, and it can write the dict directly. The active variant is remembered per workspace. API: GET/POST /api/configs.

Measure

Press I (or Inspect ▸ Measure in the toolbar); a measure panel appears over the viewer. The cursor snaps vertex > edge > face point with a live readout: line length, arc radius and sweep, circle Ø. Two picks give the distance with the closest points drawn and labelled, Δxyz, and the angle (line–line, line–plane, plane–plane) with parallel and perpendicular flags; two circles give centre spacing; two planes give the gap. Click bodies in the Browser for volume, mass and centre of mass (material names or g/cm³), two bodies for clearance or interference volume. ⌫ or right-click removes the last pick, Esc clears. Agent tools: measure (faces, edges, points, bodies), mass_properties.

Section and interference

Section (S) cuts the model with a plane: XY, XZ or YZ with an offset slider, or parallel to a flat face you click. The half facing you is removed, and the cut faces are drawn solid and hatched in each body's colour, so the inside of a gearbox or housing reads clearly. The section stays on until you remove it (a chip in the bottom bar). The agent's screenshot tool takes a section too, so it can look inside assemblies.

Interference (J) checks every pair of bodies, or the ones selected in the Browser, with exact solid booleans after a bounding-box filter. It reports the overlap volume per pair; click one to see the overlap in red with the rest ghosted. Touching faces don't count. A bolt in a tapped hole is listed separately as thread engagement, and designs with real helical threads are checked on a quick draft-thread rebuild. The 113-body planetary gearhead takes about 20 s. The agent has the same check as check_interference and is told to run it on assemblies.

Motion, explode and render

Joints live in the script, so they are saved with the design and the agent can write them: revolute("sun", ["Gearhead/Sun", "Motor/Shaft"], axis="Z"), slider(...), couple("carrier", "sun", ratio=0.25) for gear trains and rack and pinion, parent="carrier" for parts that ride on another joint (planets), and drive("sun", 0, 1440, seconds=8). ▶ Animate in the bottom bar plays the drive and gives each free joint a slider. Check collisions runs the drive through its range and lists parts that hit each other because of the motion; the agent has the same check as check_motion.

Exploded view: a slider spreads sub-assemblies apart and then their parts (along Z, X, Y or radially) with dashed trail lines; explode({"Lid": (0, 0, 40)}, axis="Z") sets explicit offsets. Studio render (Output ▸ Render, E, or the Animate panel) switches to physically based materials with environment lighting and soft shadows: set them with appearance({"*Screw*": "black oxide", "Housing": "black anodised"}) (about 30 materials, or your own colour, metalness and roughness), with sensible defaults from part names. Save image writes a high-resolution PNG; Record video captures a turntable spin, an explode-and-return and/or the motion. Both go to exports/renders.

Shop drawings

Output ▸ Drawings (D) or File ▸ Shop drawings…, with the material and sheet size from ⚙ Settings ▸ Shop drawings: third-angle front, top and right views plus an isometric, with hidden lines (OCCT HLR projection), overall dimensions, hole callouts (n× Ø, THRU or depth, thread labels), lettered holes with a coordinate table, and a title block with size, mass, scale and date. One SVG per body plus an assembly sheet, and a DXF of the view geometry, written to workspace/drawings/. Agent tool: make_drawings.

Generated drawing
A generated sheet.

Part library

Parts live in workspace/library/<part>/ as either a parametric script (a whole design saved with its parameters) or an exact STEP body, with description, tags and a thumbnail.

STEP import and export

File ▸ Import STEP… (or drop a .step on the viewer) adds it as a new body (import_step("file.step") is added to the script, the file is copied to workspace/imports/), as a new design, or straight into the library. Export: File ▸ Export STEP (exact, bodies stay separate solids) and Export STL (dialog: resolution and all-or-one body). Agent: export_model(name, formats, tolerance, angular_tolerance, body). API: /api/export/{step|stl}.

3D printing

If OrcaSlicer (or Bambu Studio) is installed on your computer, the toolbar's Output menu gains Slice (P), ⚙ Settings gains a 3D printing section and the agent gains the slice_for_printing tool. AgenticCAD does not bundle a slicer; without one none of these exist.

This is a hand-off, not a slicer UI: for per-object settings, modifiers, paint-on supports or multi-plate layouts, open the exported 3MF in the slicer.

Fidelity

CAM overview experimental

Experimental. The CAM toolpaths are geometrically correct and machine-checked, but not optimised: paths are long and conservative with many retracts, the machine models are representative outside the work area, and nothing here has been cut on a real machine by anyone but the author. Inspect every program before running it. The modelling, export and drawing features are the stable part of AgenticCAD today.

CAM is a second script per design, cam.py (saved as <name>.cam.py), written against cam_kernel.py and built with the build_cam tool or in Code. It must assign program. The namespace provides model, part, bodies, tools and machines.

part = bodies["Plate"]
stock = Stock.from_model(part, margin=3, top=1.0)
setup = Setup(machines["Generic 3018"], stock, origin="stock-top-left")
t6 = apply_feeds(tools[1], "aluminium", setup.machine)
program = Program(setup, name="plate")
program.add(face(setup, t6, z_top=stock.top, z_bottom=19))
program.add(adaptive(setup, t6, stock_minus(setup, part, 0.0, expand=6), z_top=19, z_bottom=4, stepover=0.15))
program.add(drill(setup, tools[5], [h for h in holes(part) if h.diameter < 6], peck="auto"))
program.add(contour(setup, t6, section(part, 0.0), z_top=4, z_bottom=-4.5, tabs=4))

Geometry comes from the exact B-rep: section(part, z), silhouette(part), stock_minus(setup, part, z, expand), free_region(part, z_top, z_bottom, within) (where a pocket can clear every level of a band without touching the part: a pocket cuts its region at every level, so a region taken from one section would cut away an island or boss rising into the band, or bore a countersink straight down), holes(part) (vertical round holes from concave cylindrical faces, with through detection), face_polygon(model.get_face(id)) for a clicked face, plus circle and rect. The CAM workspace shows the program, its setups, operations (colour and visibility) and the tools they use as a tree on the left, with warnings, Export G-code and the G-code preview; the Inspector edits the selected operation (Tool & feeds · Cut · Display) or setup (stock and origin, workholding, machine view); the machine and tool libraries live in Settings. Toolpaths, stock and the WCS origin are drawn in the viewer (rapids red) with Selected op / All and Toolpath / Stock / Machine toggles, and a timeline of op-coloured blocks with a tool marker. Send to machine → opens Machine ▸ Run with the setup's operations (experimental).

Tools. The CAM tree lists every tool the program uses, with its photo (hover for a large view with the packet's details): on a program with several setups, the tool order of each setup and which setups use each tool.

Build speed. Most of a build is usually slicing the part (section, outline, stock_minus: an exact OCCT boolean each, 0.3–1 s on a part of a few hundred faces). Slices are kept per shape and height for the session, and a setup's view of the part is the same from build to build, so when the assistant rebuilds a script against an unchanged model it reuses them: a 15-operation, two-setup sheet builds in 23 s the first time and 0.4 s after that (plus a few seconds of G-code). The slices never change the model they cut (1.4 fixed a case where an earlier slice made a later one of the same body come back empty). The assistant's Stop also stops a CAM build in progress, at its next operation or slice.

Entries. Pocket and contour passes ramp into the material at 3° (ramp=, 0 to plunge): a pocket descends along the innermost ring that is long enough to ramp on, which on a small ring is a helix, and the rings inside it follow at depth; a contour gets one entry per ring, with the lead-in just above the stock and every pass ramping down along the profile from where the previous pass ended, so there is no retract between passes and tabs still lift the tool. Face passes start and end with the tool clear of the stock. Helical ramps post as G2/G3 arcs with Z.

Simulation

Simulate on the CAM timeline (or the agent's simulate_cam tool) removes material from the stock along every move of the visible operations: all of them, or just one. The viewer shows the stock being cut in step with the slider; on the last frame gouges are red and material left on the part amber. The summary reports the volume removed, gouges (depth, area and the operation that caused them), material left on the part, stock still standing outside it, and rapids that pass through material. A GOUGES line means the program would cut the part: fix the operation's region before sending it (the assistant does). Top and bottom setups use a two-sided height map, 4th-axis setups a radial map around the axis. Side setups are not simulated yet.

On the 4th axis the cutter cuts with its real profile only up to its flute length: the body above the flutes, a neck, the shank and the holder stack are checked against the remaining bar as contacts, not cuts. The chuck body, the jaw ring and the tailstock (cone, barrel and 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 those 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).

Playback runs the program the way the machine would: each move takes its real time (distance at the programmed feed, rapids at the machine's rapid rate, A moves at the rotary's speed) and the tool moves continuously along it. A speed control runs from ¼× to 100× real time, ⏮ / ⏭ jump between operations, the slider is program time, and the readout shows setup · operation · tool · move · elapsed / total. The CAM tree has setup headers (WCS, stock and how it is held); ▶ in the Inspector plays from any operation or setup. On the Machine view a flipped (bottom) setup turns the work over on the table.

Rendered on the CAM timeline plays the program as a studio render: the stock is a voxel solid machined live in front of you (every move stamped into a signed-distance field in a Web Worker, meshed with smooth walls and exact floors), the part stays hidden until the stock becomes it, a chrome holder with a spinning fluted tool hangs from the spindle, the camera follows the cut with depth of field, and a HUD shows the step, tool changes and the speed factor. On a 4th-axis setup the stock is the simulator's radial map drawn as a closed bar about the axis in the same metallic material, updated at the simulation's keyframes rather than stamped live. Settings ▸ General has two switches for slower PCs: Simulation stock (voxel or the lighter height map) and Rendered simulation depth of field, which also turns itself off when a GPU takes too long per frame.

Removal rate (MRR). The simulation measures every pass against the stock as the passes before it left it: how deep the tool is buried (a ball in a slot reads the slot's depth, not the step), the volume it removes and its removal rate over one tool radius of travel. Per operation the summary gives the deepest burial against the stepdown (BITE), the highest and mean MRR against the tool's rating (stepdown × stepover width × feed) and, for finishing passes, against the operation's own average: a skim that suddenly removes several times its average has found stock the roughing left (MRR). The operations' volumes add up to the stock removed. Then limit_feeds_from_sim (the assistant's tool; also POST /api/cam/limit_feeds) slows the moves that remain over their reference, never below 25 % of the planned feed; moves that would need less are listed as path problems, because there the toolpath must change (rough first, approach from above), not the feed. Run it last, after the program is otherwise final: rebuilding cam.py resets the feeds.

Machine view and collision checks

Every built-in machine has a kinematic 3D model: the Makera Z1 and Carvera Air (bed with the real hole grid, moving bed under the rear bridge, head in X and Z, 4th-axis chuck and tailstock, enclosure), the Haas VF-2, VF-2SS, VF-4, Mini Mill and TM-1 (table with the real T-slots, saddle, column, CAT40 spindle with an ER32 holder, verified nose-to-table range) and a parametric gantry router for anything else. Machine in the CAM toolbar (A), the viewer's Machine toggle or a setup's Machine view section in the Inspector shows the program's machine around the stock and plays the simulation on it: the head, table and chuck move as they would on the real machine, the tool hangs from the spindle, and any part the simulator saw collide flashes red at that move.

The Z1's spindle head comes from Makera's own simplified CAD: a Ø16 × 20 nose under a 65 × 70 head that sits 52 mm in front of the spindle axis and only 18 mm behind it. A machine's nose stack can describe such an off-centre head as boxes about the spindle axis ({"name": "head", "box": [x0, x1, y0, y1], "h": 150}), so the collision check sees the head where it really is rather than as a circle.

The simulation 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 operation, move, position and depth, and the agent sees them in simulate_cam. Settings ▸ Machines ▸ Model shows every number with its source and accuracy tier (verified, reference, estimated) and takes measured overrides per machine: nose profile, tool stickout, spoilboard thickness and gantry clearance. 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 line saying where the position came from: the reference model, the machine's config (with the machine Y and Z it was converted from), probed on a date, 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, chuck_gap, jaw_reach, tail, tail_gap, stock_length), and Zero on the 4th axis on the Machine page writes the position it finds here for the simulation. The work-area numbers are verified; castings and enclosures are representative, so treat the view as a check on the tool, holder and table, not a rendering of your exact machine.

Model your own machine

A machine's model is a script, in the same spirit as design.py and cam.py: workspace/machines/<slug>.machine.py, written in the design-script namespace plus three calls. node(name, parent, axis, mode, stock, pivot) declares the moving axes (head nodes move with the tool, table nodes carry the work the opposite way, one node carries the stock), part(name, node, shape, material, collision) attaches any build123d shape (so vendor CAD can come in through import_step), and machine(home, travel, clearance, nose, table, rotary, spoilboard, sources, notes) gives the numbers the simulator uses. The built-in Makera Z1, Carvera Air, Haas and gantry-router models are the same kind of script and are what a machine falls back to, 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 (the current design sits on the table) and opens or closes its doors: a script declares door nodes (node(..., door="hinge"|"slide", pivot, direction, open)) and the viewer animates them. Settings ▸ Machines ▸ Model has Show in viewer and Edit as script….

Or ask the agent. "Add my Shapeoko 5 Pro to the library and model it" is a chat request: the agent writes the script with build_machine, screenshots it, and tells you which numbers it verified and which it estimated. Drop photos of your machine into the chat and it models against them; the built-in Z1, Carvera Air and Haas VF-4 were made that way from the makers' product photos, with every number tagged verified, reference or estimated.

Fixtures: vises, clamps, plates

How the work is held is part of a setup: Setup(..., fixture="Makera low-profile vise") seats the stock's bottom-centre at the fixture's work origin (thin stock is raised on parallels automatically), the viewer draws the vise, clamps or plate around the stock and on the machine's bed, and the simulator treats every fixture part as uncuttable, so the tool or holder touching a jaw or a clamp bar is reported as a collision with that fixture. Built-ins: Makera low-profile vise, Carvera Air vise, 4" screwless vise, 6" Kurt-style vise, a parametric tooling plate and step clamps. A setup's Inspector has a fixture picker under Workholding (the choice lands in a fixtures({...}) line in cam.py), Settings ▸ Fixtures lists them with Show in viewer and Edit as script…, and Code ▸ fixture.py edits and builds them.

Several fixtures at once. Besides the fixture that holds the stock, a setup can have more on the table, each placed and turned on its own: Setup(..., fixtures=[{"name": "Step clamps", "at": (x, y), "rot": 90}]), or the setup Inspector's Workholding, where you pick, move (X / Y from the table centre), turn 90°, add and remove them. All of them are drawn on the machine and checked against the tool and holder; the simulation names the fixture it hit, and the program check warns when fixtures overlap the stock or each other.

Makera Z1 low-profile vise. Modelled from Makera's dimensioned photos and quick-start guide: a 226 × 103 mm T-slot extrusion that bolts to the bed with the MDF wasteboard removed, a fixed jaw at −X and a moving jaw on a carrier closing along X up to 130 mm. Its slotted soft jaws hold thin stock on ledges 20 mm above the rails; stock taller than the jaws sits on the rails (fixture_params={"seat": "rails"}). Heights not labelled by Makera are measured from its photos to about ±0.5 mm and err on the tall side; enter your own caliper readings with base_h, jaw_h or ledge.

Like machines, a fixture is a script: part(name, shape, material, collision) in the fixture frame plus fixture(name, work_origin, clamp_axis, max_opening, mount, sources, notes), with the stock size and parameters available. Ask the agent ("add my 3-inch screwless vise", or hand it photos) and it writes one with build_fixture; ask for a CAM job and it uses the fixture you name, or recommends one and says so.

Setups and the 4th axis

A program can have several Setups. orient="bottom" (or front, back, left, right) turns the part so that side faces the spindle; setup.view(part) gives the part as the machine sees it for section and holes. Each setup gets its own work offset (G54, G55, …); between setups the post stops the spindle, parks and pauses for you to flip or re-fixture the part, then restarts the spindle.

top = Setup(machines["Makera Z1"], stock, name="Top")
bot = Setup(machines["Makera Z1"], stock, orient="bottom", name="Bottom")
program = Program(top)
program.add(pocket(top, tools[3], rect(-15, -10, 15, 10), z_top=12, z_bottom=8))
program.add(pocket(bot, tools[3], circle(10, 0, 12), z_top=0, z_bottom=-3))   # after the flip

Tiles. Setup(..., tiles=tiles_of(model, "badge")) runs every operation at each copy of a pattern: the operation is computed once, on the part, and the post runs it at every tile, all tiles with one tool before the next tool change. program.add(op, tiles=False) is for an operation computed over the whole plate (facing, roughing the block). Time, bounds, the simulation (every tile is cut, and its statistics are folded back onto the operation's moves), the viewer and the G-code all include the tiles. On a Makera the tiles run in G54 with offsets; tile_wcs=True puts each tile in its own work offset (G54 … G59.3, with the offsets to set listed in the header) on other controllers, and is refused on a Makera, whose firmware makes the fitted tool the length reference whenever a work offset's Z is set and keeps only G54 across a restart. An 8-badge sheet: the same cutting lines as the copied-bodies program to 0.001 mm, from 635,021 moves instead of 2,108,565, posted in 25 s instead of 89 s.

sheet = Setup(machines["Makera Z1"], stock, origin="stock-top-left", tiles=tiles_of(model, "badge"))
program.add(face(sheet, t2, z_top=1, z_bottom=0), tiles=False)     # the whole plate, once
program.add(contour(sheet, t2, outline(bodies["badge"]), z_top=0, z_bottom=-2.9, tabs=3))   # at every tile

On a machine with a 4th axis (rotation about X, A in degrees), Setup(..., rotary=True) puts the part on the axis; Stock.cylinder(d, length) describes bar stock, and the work origin is on the centreline (rotary-axis-left by default, or rotary-axis-center). Indexed 3+1 work uses ordinary operations on rotary setups at fixed A angles (a=90); setups on the same mounting share one work offset and the post just turns A, and every 3+1 operation indexes A to its setup's angle by the short way, even after a continuous one. Continuous 4-axis: rotary_rough (rings or lines from a radial height map, leaving stock), rotary_finish (lines, rings or one continuous spiral, ball or flat), rotary_wrap (pocket or contour a 2D pattern wrapped onto the cylinder), rotary_profile and gear_teeth. Rings alternate direction so A stays within one turn, and after a spiral the Makera post drops the whole turns (G92.4 A0 S0, as MakeraStudio does) so the next index is a short move.

Rotary profile. rotary_profile(setup, tool, profile) turns a hex, square, cam, splines or any cross-section out of round stock: profile is a 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, with 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, with a flat facing 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. gear_teeth(setup, tool, module, teeth, width=None, helix=0, ...) cuts involute spur or helical teeth with a gear cutter tool (Settings ▸ Tools, type Gear cutter: module, pressure angle, cutter number as the lowest tooth count of its range, disc Ø and width, arbor shank Ø and nut; it is drawn to scale with a legend, and its module must match the gear's). 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 and lead-out of the cutter radius plus 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, positive for right hand). It warns about a 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, an HSS surface speed over 80 m/min, and a lead-in that reaches the chuck jaws or the tailstock. The simulation cuts the teeth 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 a module-1, 20-tooth gear
program.add(gear_teeth(blank, tools[8], module=1.0, teeth=20, stepdown=0.75))          # T8: a module-1 gear cutter

Checks for 4th-axis programs, shown as warnings in the CAM tree before you run: swing diameter and length against the module, a missing or uninstalled axis, a work origin off the rotary centreline in Y or Z (4th-axis programs must be zeroed on the axis), Y and Z reach from the axis (the Z1 with the module fitted reaches 159 mm in Y), toolpaths below the bed, A speed against the module's maximum (the controller would slow those moves and the time estimate would be short), an operation that moves A on a setup that is not rotary, and stacked roughing bands that leave different skins on their walls. In the CAM tree, + Add operation on a rotary setup offers Rotary rough, Rotary finish, Rotary profile and Gear teeth alongside the flat operations.

Makera Carvera

The Makera Z1 and Carvera Air are built-in machines, with and without the 4th axis, using a makera post written against the controller firmware: lines of at most 63 characters, no line numbers, M6 Tn on one line (the machine runs the manual tool change and measures the new tool), M3 only once a tool is active, G28 to park, M600 between setups, up to nine work offsets, no arcs while A moves, and A-axis feed rates converted the way the firmware interprets F on mixed linear and rotary moves. Tools larger than the 1/8" collet are flagged.

Haas and LinuxCNC

Two Fanuc-style posts join GRBL and Makera. haas (NGC and Classic controls): % wrapper and an O-number line, ( ) comments, every number with a decimal point (Haas reads X10 as ten least-increments), Tn M06 then G43 Hn on the first Z move, G53 G0 Z0. retracts, M00 stops between setups (and before each tool change on machines without a changer), G93 inverse-time feed for moves with the rotary axis, M30. linuxcnc (rs274ngc): G64 P0.01 path blending, Tn M6 G43 Hn (with hal_manualtoolchange LinuxCNC prompts at M6; with an ATC it changes), (MSG, …) operator messages, G53 G0 Z0, G93, M2.

Built-in machines: Haas VF-2 (30 × 16 × 20 in, 8,100 rpm, 1,000 ipm, 20-pocket changer), VF-2SS (12,000 rpm, 1,400 ipm, 24+1 side-mount), VF-4 (50 × 20 × 25 in), Mini Mill (16 × 12 × 10 in, 6,000 rpm, 10 tools) and TM-1 (30 × 12 × 16 in, 4,000 rpm, 200 ipm, no changer as standard, so the post stops before each tool change), all CAT 40 with flood coolant and the standard-configuration numbers from Haas's spec sheets; and a generic LinuxCNC mill (3-axis, and + 4th axis) whose envelope you edit to match yours. Tool offsets (H) and the work offset (G54) must exist on the control. CAM remains experimental: simulate, then prove out slowly.

Machines and tools

workspace/machines/*.json: travel, max feeds per axis, rapid, spindle range, safe and clearance Z, tool-change policy (pause, atc, manual, none, split), post (grbl, makera, haas, linuxcnc), collet, 4th axis, max_stepdown per material for the feeds calculator (the Makera Z1 caps aluminium at 0.8 mm), arcs on/off and arc tolerance, start and end G-code. workspace/tools.json: flat, ball, bull nose, V-bit, chamfer, drill, tapered ball and gear cutter tools with diameter, flutes, flute length, feed, plunge, rpm, stepdown, stepover (a gear cutter adds module, pressure angle, cutter number, disc width, arbor shank and nut). Defaults: a Generic 3018, a Shapeoko-class router, the Makera Z1 and Carvera Air with and without the 4th axis, the Haas VF-2 / VF-2SS / VF-4 / Mini Mill / TM-1, and a LinuxCNC mill with and without a 4th axis; tools T1–T7. Edit them in Settings ▸ Machines (a form per machine, or JSON) and Settings ▸ Tools (a list and an editor that describes each tool the way its packet does), or ask the agent (save_machine, save_tool). feeds(tool, material, machine) and apply_feeds(...) compute feeds for 13 materials with spindle and feed clamping and radial chip thinning; Settings ▸ Tools has a calculator with “Apply to Tn”.

Per-operation tool settings. Click an operation in the CAM tree; the Inspector's Tool & feeds shows the spindle speed, feed, plunge, stepdown and stepover it runs with. Blank fields use the tool's values; anything you type overrides them for that operation only, and a calculator fills them for a material on the program's machine. Overrides are one line at the top of 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; an override that no longer matches an operation is flagged.

Operations

OpPurposeNotable parameters
facesurface the stock top down to a level with passes at an angle, zigzag or one way, continuing across depth levelsregion, stepover, angle, one_way (every row the same direction, one uniform lay)
contourprofile around polygons, outside / inside / ondepth passes, tabs, tab_height, climb, lead (tangential quarter arcs), stock_to_leave
pocketconcentric clearing with islandsrest_from (previous tool, 2.5D rest)
adaptiveconstant-engagement HSM roughingstepover (fraction of Ø), helix_diameter, rest_from, chip_thinning
drilldrill at Hole objects or pointspeck (depth or "auto"), retract
parallel3d3D finishing following the surface with the tool's whole profile; the lay is part of the finishregion, stepover, pattern (parallel at angle, radial or spiral from center), one_way (climb throughout, rapids back just over the surface), rest_from (3D tip-map difference)
chamfer_edgeschamfer the top edges of picked faces or the part outline with a V-bit or chamfer toolwidth, side, tip_below (the tip size is part of the offset)
vcarveletters or a logo along their centrelines, as deep as each stroke is widemax_depth (flat bottom for wide strokes), stepdown levels
rest_regionmaterial a previous tool could not reachmorphological opening by the tool radius
rotary_rough4th axis: rough the bar down to the part from a radial height map, leaving stockmode (rings turn A continuously, lines index it), stepdown (radial), leave, x_range
rotary_finish4th axis: finish the part surface following the radial map, ball or flatmode (spiral = one continuous pass, rings, lines), stepover, leave
rotary_wrap4th axis: pocket or contour a flat 2D pattern wrapped onto the cylinderradius, depth, kind (pocket / contour), side
rotary_profile4th axis: a hex, square, cam or any cross-section from round stock, Z following the real tool profileprofile (a Y–Z polygon; regular_polygon(sides, across_flats)), x_range, stepdown, leave, res_a
gear_teeth4th axis: involute spur or helical teeth with a module cutter from the side of the blankmodule, teeth, width, helix, side, stepdown, leave, backlash, a0

All operations work in model coordinates; the post translates to the WCS origin you chose: stock-top-left, stock-top-center, stock-bottom-left, stock-bottom-center, model-origin, or an (x, y, z); on a rotary setup rotary-axis-left or rotary-axis-center.

Adaptive clearing

The adaptive op is constant-engagement by construction. It keeps a polygon of the region cleared so far; each pass is the boundary of that region offset inward by (radius − stepover), intersected with the cut region, so the tool's leading arc never exceeds the target engagement angle in open material. It starts with a helix entry, morphs spirals outward, links passes with short retracts, and slows the feed where geometry forces a higher engagement (concave corners, which the summary and G-code warn about). A fine-grid sweep audit in the test suite measures the leading-half contact angle along every pass. Rest machining removes what a larger previous tool could not reach.

Post and G-code

Program.gcode() writes the GRBL dialect: header comments (machine, stock, origin, time, warnings), G21 G90 G94 G17, G54, M3 S, G0/G1 with F on change, G2/G3 arcs fitted to circular runs (chord-sagitta checked, ≤180° per arc, collinear moves merged), M6/M0 tool-change pauses, M30. Makera machines use the Makera post. Checks: machine travel, spindle range, feeds clamped, cutting below the stock bottom, multiple tools with tool_change: none, and the 4th-axis checks on rotary programs. 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 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. Download with Export G-code in the CAM workspace or /api/gcode; the agent's export_gcode writes to workspace/exports/ and reports line, move, arc and tool-change counts. Not yet: canned drilling cycles (GRBL has none), thread milling.

Makera Z1 control experimental

Experimental. Running programs on a machine is new. Air-cut every program first and stay at the machine with a hand near the emergency stop, especially through the tool changes.

The Machine workspace connects to a Makera Z1 on your network (type its address or Find, then Connect). The status strip shows the state, position, tool and Control; nothing moves until Control is on, and it turns itself off on disconnect, alarm, lost status or after five minutes unused. Hold, Pause, Stop program and STOP are always in the strip. Below it, the machine's camera with the jog pad, and four steps in the Inspector. 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 gets an A+ / A− column with its own step (1°, 10°, 45°, 90°), under the same guards as the other axes; A appears only when the machine is known to have a 4th axis (the machine record has it, or the status reports a non-zero A).

The assistant can work the machine too: hold, pause, stop program and STOP act at once; everything else becomes an approval card in the chat and on the Machine page, and runs only when you click Approve. Settings ▸ Phone control adds a phone page with the camera, STOP and tool changes, and (1.2) notifications on the phone when the machine waits for a tool, finishes or stops — Web Push done in the app: the app makes its own certificate (trusted once on the phone) and push keys, and sends each message, encrypted, straight to the phone's push service; tapping opens the page with Confirm. Machine ▸ Manual has a Cleaning position (Z to the top, head over the front-middle of the bed, table fully back) so what Home covers can be brushed out; Home again when done.

Settings and MCP

The Settings button (the gear at the right of the top bar) opens a window with tabs: General, MCP servers, Extensions, Drawings & printing, Machines, Fixtures, Tools and Phone control. General and MCP: Claude model (default Claude Opus 5.5; any model id, or the Claude Code default), effort (low … max), max steps per turn, thinking summaries, web tools, extra standing instructions, and MCP servers (stdio, http or sse configs as JSON with enable toggles and live connection status from the session). Saved to workspace/settings.json; “Save & restart agent” starts a fresh session with the new options and tells the agent its earlier history is gone. API: GET/POST /api/settings, GET /api/agent/status, POST /api/agent/restart. Settings also holds the Shop drawings defaults (material, sheet) and, when a slicer is installed, the 3D printing defaults (printer, quality, filament, layer height, infill, walls, supports, brim) used by Output ▸ Drawings and Slice and by the agent.

Agent tools reference

The agent runs in a Claude Agent SDK session with an in-process MCP server named cad. It has no shell; everything it knows about the model comes back from these tools.

ToolPurpose
build_model(code)replace the script, rebuild, return the summary (bodies, bbox, volume, sketches, threads) or the traceback
edit_model(edits?, append?)change the existing script in place: exact-text replacements plus code appended before result, then rebuild; a failed build leaves the previous design untouched
inspect_model(kind?, near?, body?, limit?)faces with id, type, area, centre, normal, size, radius
screenshot(view, highlight_faces?, show_edges?)the browser renders a view and returns it as an image
export_model(name?, formats?, tolerance?, body?)STEP / STL into workspace/exports/
get_code(), save_design(name?)read the script; save the design (only when asked)
get_parameters(), set_parameters({...})read and rewrite top-level numeric parameters
measure(...), mass_properties(...)distances, angles, hole spacing; volume, mass, centre of mass, clearance
make_drawings(material?)shop drawings (SVG + DXF)
library(action, ...)list, search, get, insert, save_design, save_body, delete
kit(action, ...)the Design Kit: toc, search and read components and guides; note, save_guide, save_part into the workspace's kit
sketch(action, ...)list, get, set, delete sketch blocks in the editor's item format
slicer_info(machine?, search?), slice_for_printing(machine?, process?, filament?, body?, layer_height?, ...)only when a slicer is installed: printers and compatible profiles; slice for 3D printing and show the layers in the viewer
cam_context()machines, tools, model facts, holes, current program
build_cam(code), get_cam_code()build the CAM program from a script; read it
simulate_cam(ops?), limit_feeds_from_sim(floor?, margin?, finishing_factor?)simulate the program (removal, gouges, collisions, BITE and MRR per operation); slow the moves whose removal rate is over their reference
export_gcode(name?)write the .nc file and report counts
feeds_speeds(tool, material, ...)the feeds calculator
save_machine(...), save_tool(...)edit the CAM libraries
Tool results are size-capped (screenshots are JPEG) because the SDK drops results over 1 MB. If you add a tool, return the numbers the agent needs rather than raw data.

Keyboard shortcuts

KeysAction
left-dragorbit the view
right-drag, or ⌘/Ctrl/⇧ + left-dragpan (move the view sideways); trackpads: hold the modifier and drag
wheel / two-finger scroll, middle-dragzoom
ViewCube face, edge or corner · ⌂snap to that view · home / fit
B C O KBox, Cylinder, Sphere, Sketch
Q H F X L VPress/Pull, Hole, Fillet, Chamfer, Shell, Move
IMeasure
D PShop drawings, Slice for 3D printing (with the options in Settings)
↵ / Esc / ⌫OK / cancel / drop last pick in a command dialog
↑↓, ⇧, ⌥nudge a numeric field by 1, 10, 0.1
⇧-clickadd to the selection
⌘Bhide / show the right column
⌘↩run the script in Code
⏎ / ⇧⏎send / newline in chat

Architecture

A local server runs the kernel and the agent; the browser (or the desktop window) is the UI. There is no cloud component other than Claude itself, and the slicer is whatever you already have installed.

Your browser / desktop window 3D viewerthree.js · face, edge and body picking Toolbar and sketch editorpress/pull, holes, fillets, sketches Assistantdescribe, point, attach photos Design, CAM, Machine, Codescript, parameters, measure, drawings WebSocket · HTTP localhost only Local server server.py · FastAPI agent.pyClaude Agent SDK session · the agent's CAD tools (MCP) cad_kernel.pyruns the script · build123d on the exact OCCT B-rep · STEP, STL cam_kernel.py2.5D / 3D toolpaths · GRBL post (experimental) slicer.pyhands an STL to your slicer · reads the G-code back as layers drawing · threads · libraryshop drawings · ISO threads · part library Claude via Claude Code sign-in or API key workspace/designs · library · machines · tools · history · exports · slicing OrcaSliceroptional · your own install

The design itself is one Python script written against build123d. The agent edits it through its tools; chat, the toolbar, the parameters panel and the sketch editor all edit the same file, so every route stays reproducible. One server process holds one design and one agent session.

Files and API

PathContents
workspace/designs/*.py, *.cam.pydesigns and their CAM scripts; state.json remembers the open one
workspace/history/every successful build (Undo)
workspace/library/<part>/library parts (script or STEP, metadata, thumbnail)
workspace/machines/*.json, workspace/tools.jsonCAM libraries
workspace/exports/, drawings/, imports/, images/, screenshots/outputs and inputs
workspace/settings.jsonagent settings and MCP servers

HTTP endpoints: /api/designs, /api/design/{save,open,new,import,download}, /api/export/{fmt}, /api/import/step, /api/params, /api/measure, /api/drawings, /api/library/*, /api/sketches, /api/cam/{library,machine,tool}, /api/gcode, /api/gcode/preview, /api/settings, /api/agent/{status,restart}, /api/version. The WebSocket carries chat, builds, undo, quality, screenshots, and the manual operations (op, rename_body, set_sketch, add_primitive, transform_body, …).

Versioning

version.py holds the version (next to the AgenticCAD name in the top bar, click for the changelog) and CHANGELOG.md follows Keep a Changelog. Pre-1.0: minor bump for features, patch bump for fixes; a release is a bump plus a git tag. /api/version serves both.

Tests and evals

.venv/bin/python -m pytest -q            # unit / API tests, seconds, no Claude calls
.venv/bin/python evals/run.py            # agent evals: real agent, headless, graded; ~$5 per run
.venv/bin/python evals/run.py --filter cam --model claude-sonnet-5 --repeat 3

Known gaps and roadmap

Roadmap, roughly in order: projects and sessions; CAM holder and shank collision checks; mate-based assembly positioning; drag handles everywhere; a WebSerial G-code sender; drawing and import upgrades; deployment (Docker, auth, multi-user); other posts and 3D-printing exports. Issues and contributions: github.com/agenticcad/agenticcad.

Licence

AgenticCAD is free for non-commercial use under the PolyForm Noncommercial License 1.0.0: personal projects, research, education, hobby making, charities, schools and public institutions. Commercial use (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. Buy online for the number of users you need; the Stripe receipt showing users and period is your licence record, with nothing to enter in the software. Volume or site licences: agenticcad@prodevelop.com.au.