Skip to content

CAM Planning

From STEP file to G-code in five POSTs: create a project, upload the workpiece, set stock, fire the planner, and postprocess each setup.

Concepts

Term What it is
Project The container for one machining job, owning a workpiece, stock, machine, material, equipment library, and the resulting CAM tree. Auto-creates a sibling quote job (see Quotation).
Workpiece The target geometry. STEP, STL, or native BREP. Tessellation runs synchronously on upload; the response includes presigned URLs for the resulting mesh.
Stock The blank you machine the workpiece out of. Defined by per-axis bounding-box offsets in mm.
Automated CAM planning Async Celery task that auto-detects machinable features, picks tools from the project's equipment library, and computes toolpaths. Returns a task_id; observe via webhook (task.succeeded) or poll GET /tasks/{id}.
Setup One physical fixturing of the part. Each setup yields its own G-code program. The automated planner emits at least one setup per project.
Postprocessing Translate the per-setup CAM tree into vendor-specific G-code (.nc) using the machine's postprocessor.

Endpoints

Project lifecycle

Method Path Scope
POST /projects projects:write
GET /projects projects:read
GET /projects/{project_id} projects:read
PATCH /projects/{project_id} projects:write
DELETE /projects/{project_id} projects:write

Inputs

Method Path Scope
POST /projects/{project_id}/workpiece cam:write
POST /projects/{project_id}/stock/bounding-box cam:write

Plan and postprocess

Method Path Scope
POST /projects/{project_id}/automation/cam-planning automation:run, cam:write
POST /projects/{project_id}/post/run?setup_index=N cam:write

Task status

Method Path Scope
GET /tasks/{task_id} projects:read
GET /tasks/by-project/{project_id} projects:read

The full request/response schemas live in the Scalar reference. Webhook delivery and signing are documented under Webhooks.

Flow: STEP file to G-code

import requests
import time

API = "https://app.autonomiq.de/backend-api/api/public/v1"
H = {"X-API-Key": "smk_live_..."}

# 1. Create a project
#    - machine: auto-picked from user's first assigned machine
#    - equipment: auto-picked from user's default library (is_default=true)
#    Pass product_id / library_id to override.
project = requests.post(
    f"{API}/projects",
    json={"name": "Bracket v3", "customer": "Acme"},
    headers=H,
).json()
PID = project["id"]

# 2. Upload the workpiece (STEP / STL / BREP)
with open("bracket.step", "rb") as f:
    requests.post(
        f"{API}/projects/{PID}/workpiece",
        files={"file": f},
        headers=H,
    ).raise_for_status()

# 3. Define stock from per-axis offsets (mm)
requests.post(
    f"{API}/projects/{PID}/stock/bounding-box",
    json={
        "minus_x": 0, "plus_x": 0,
        "minus_y": 0, "plus_y": 0,
        "minus_z": 0, "plus_z": 5,
    },
    headers=H,
).raise_for_status()

# 4. Kick off automated CAM planning (async, returns 202 + task_id)
task = requests.post(
    f"{API}/projects/{PID}/automation/cam-planning",
    json={"include_face_milling": True, "sort_by_height": True},
    headers=H,
).json()
task_id = task["task_id"]

# 5. Poll until the task finishes (or register a webhook; see /webhooks)
while True:
    snap = requests.get(f"{API}/tasks/{task_id}", headers=H).json()
    if snap["status"] in ("success", "failed", "cancelled"):
        break
    time.sleep(2)

# 6. Generate G-code for setup 0
nc = requests.post(
    f"{API}/projects/{PID}/post/run",
    params={"setup_index": 0},
    headers={**H, "Accept-Encoding": "gzip"},
).content
open("program_setup0.nc", "wb").write(nc)

Flow: pick a specific machine and material

By default the planner auto-selects the user's first machine and default equipment library. Override at creation time via product_id / library_id, or reassign afterward with PATCH /projects/{id}:

# Discover what the user has access to
machines = requests.get(f"{API}/libraries/machines", headers=H).json()
materials = requests.get(f"{API}/libraries/materials", headers=H).json()

# Find a specific machine by name
datron_neo = next(m for m in machines if "Neo" in m.get("name", ""))

# Option A: pin at creation time
project = requests.post(
    f"{API}/projects",
    json={
        "name": "Bracket v3 / Datron Neo",
        "product_id": datron_neo["product_id"],
        "material_id": materials[0]["id"],
    },
    headers=H,
).json()
PID = project["id"]

# Option B: reassign machine/material on an existing project
requests.patch(
    f"{API}/projects/{PID}",
    json={
        "product_id": datron_neo["product_id"],
        "material_id": materials[0]["id"],
    },
    headers=H,
).raise_for_status()

PATCH also accepts name, customer, library_id, and the other contact fields. Only provided fields are updated.

Flow: webhook-driven instead of polling

Register an endpoint once, then drive everything off task.succeeded / task.failed:

requests.post(
    f"{API}/webhooks",
    json={
        "url": "https://your-app.example/sm",
        "event_types": ["task.succeeded", "task.failed", "quote.ready"],
    },
    headers=H,
).raise_for_status()

Inside your receiver, dedupe on X-SimplyMill-Delivery-Id and verify the HMAC signature; see Webhooks.

Concurrency rules

  • One automation task per user runs at a time. Concurrent requests from the same user queue (the response carries queued: true and a queue_position).
  • Same project, second request → rejected immediately with 409 Conflict, code=automation_slot_busy.
  • For parallel automation across projects, mint API keys for separate users.

Idempotency

POST /projects, POST /projects/{id}/workpiece, and POST /projects/{id}/automation/cam-planning honour the Idempotency-Key header. Subsequent requests with the same key replay the original response with Idempotency-Replayed: true for 24h.

Cleanup

requests.delete(f"{API}/projects/{PID}", headers=H).raise_for_status()

Soft-delete returns 204 immediately; a Celery task drops the BOSS-stored meshes/toolpaths, the MongoDB job, and the Postgres rows in the background.