Every capability is an endpoint before it is a button

A CAD modeler your programs can drive as well as your hands.

There is no operation in Lola3d that exists only in the interface. The tool rail, the REST API, the MCP tools and the in-app assistant all call the same 228 actions, validated by the same JSON schemas, committed through the same revisions and undo steps.

One catalogue, four callers

The same action, whoever asks.

GET /api/studio/actions is the authoritative runtime schema — not a document that drifts from the code, but the catalogue the application itself is built on. Anything in it can be called with a session token or an API token.

Mutations carry expected_revision. A stale one is refused with 409 rather than merged by luck, and a kernel error leaves the document byte-for-byte unchanged.

REST
# Everything the modeler can do, with its schema
curl -H "Authorization: Bearer $TOKEN" \
     https://lola3d.app/api/studio/actions

# One edit, on the revision you hold
curl -X POST https://lola3d.app/api/studio/actions/push_pull \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"model_id":"…","expected_revision":12,
       "args":{"target":"body1","face_id":"f7","distance":40}}'

# A hundred commands as one revision and one undo step
curl -X POST https://lola3d.app/api/studio/actions/batch …
Generative design

Python, inside the kernel.

A batch of commands cannot read the model between its own commands. That one limitation is what separates automating a click from generating a design — and it is what the Python console removes.

What cad gives you

  • cad.create_box(width=100, …) — any catalogue action, by name, validated by its own schema
  • cad.actions() / cad.help(name) — the catalogue, from inside the script
  • cad.document, cad.features, cad.find(name) — the model as it stands
  • cad.objects(), cad.bounds() — the visible bodies, their volume, area and extents
  • params — the variables you passed; print(…) captured; result returned

What it commits

  • One script is one edit and one undo step.
  • A script that raises commits nothing — and says which line failed.
  • A script that changes nothing is not a revision: measuring is free.
  • dry_run runs the whole thing and keeps nothing.

What the sandbox is worth — honestly

The restricted namespace — no open, no eval, no exec, no dunder attributes, and only the numeric and structural modules — is a guard rail, not a security boundary. CPython has never offered a real in-process sandbox and we will not pretend otherwise.

What actually holds: the script runs in a geometry worker process with its own memory and CPU ceilings, under a deadline the worker enforces on itself (a busy loop stops itself rather than costing a worker), and behind a scopeadmin by default, because running a script is running code on the geometry host. One setting widens it to every editor; another removes the action from the catalogue entirely.

A lattice whose size follows its own arithmetic
n = params["cells"]
pitch = params["pitch"]
strut = params["strut"]

for i in range(n + 1):
    for j in range(n + 1):
        cad.create_box(width=strut, depth=strut,
                       height=n * pitch,
                       position=[i * pitch, j * pitch, 0])

print("struts:", len(cad.features))
result = {"struts": len(cad.features),
          "extent_mm": n * pitch}
Read the model back, then decide
# Nothing applied — so this costs no revision at all.
total = 0.0
for o in cad.objects():
    print(f"{o['name']:<24} {o['volume']:>14,.1f} mm3")
    total += o["volume"]

box = cad.bounds()
size = [round(box["max"][i] - box["min"][i], 2) for i in range(3)]
result = {"bodies": len(cad.objects()), "bbox_mm": size}
The Python console in the workspace, with a script, its output and the actions it applied.
Inspector → ⋯ → Python console. Tab indents, Ctrl+Enter runs, Dry run keeps nothing.
Agentic modeling

An agent gets the same modeler you do — and the same brakes.

The MCP endpoint exposes the catalogue as tools, held to the same read and write scopes as any other caller. An agent with a read token can measure, inspect and export; one with a write token can model.

It can also work the way a colleague does: preview a change without writing anything, or propose one. A proposal draws a ghost of itself in your viewport with the two buttons that answer it, and an approval applies every command as one edit with one timeline group over what it created — or rejects it leaving nothing behind.

  • Read tools are free; write tools are approval-gated in the app.
  • A requirement stated in words becomes a checked annotation, re-checked on every rebuild.
  • Long calls carry your abort signal: a client that goes away frees the worker.
MCP
POST /mcp
X-API-Key: ign_…          # scope: ai

{"jsonrpc": "2.0", "id": 1,
 "method": "tools/call",
 "params": {
   "name": "propose_changes",
   "arguments": {
     "model_id": "…",
     "title": "Lighten the web by 18%",
     "commands": [
       {"action": "shell",
        "args": {"target": "web", "thickness": 2.4}}
     ]
   }}}
Interchange

Your geometry leaves the way it came in.

FormatImportExportNotes
STEP (.step)YesYesExact B-rep. Assemblies arrive as components.
IGES (.iges)YesYesSurfaces sewn where watertight.
BREP (.brep)YesYesThe kernel's own exact form, with names and colours.
STL (.stl)YesYesSewn into a solid when watertight, kept as faces when not.
OBJ (.obj)YesYesExported with its MTL, in a zip.
3MF (.3mf)YesYesColours preserved.
glTF / GLBYesYesFor the web and for renderers.
Collada (.dae)YesYesThe way in and out of SketchUp, which has no open .skp format.
IFC (.ifc)YesYesIFC4; bodies as proxies, components as assemblies.
DXF / SVGYesYesIn as sketch entities; out from a sketch, a section cut or a drawing.
PDFYesDrawing sheets.
JSONYesYesThe editable document itself, frozen geometry included.
G-codeYesGRBL, LinuxCNC, Fanuc or a post you describe yourself.

Get a token and make something.

Sign in, create an API token in Settings, and the whole modeler answers to curl. Then write the script that would have taken you an afternoon of clicking.