Metadata-Version: 2.5
Name: ottocam
Version: 0.2.8
Summary: Automatic 2.5D CAM for hobbyists — STEP to GRBL G-code
License-File: LICENSE-EULA.md
Requires-Python: <3.15,>=3.12
Requires-Dist: cadquery-ocp<7.9,>=7.8.1
Requires-Dist: defusedxml>=0.7.1
Requires-Dist: numpy>=1.26
Requires-Dist: pyclipper==1.3.0.post6
Requires-Dist: pyvoronoi>=1.2.8; sys_platform != 'darwin' or platform_machine != 'x86_64'
Requires-Dist: svg-path>=7.1
Provides-Extra: engine
Requires-Dist: fastapi>=0.110; extra == 'engine'
Requires-Dist: httpx>=0.27; extra == 'engine'
Requires-Dist: python-multipart>=0.0.9; extra == 'engine'
Requires-Dist: uvicorn>=0.27; extra == 'engine'
Provides-Extra: test
Requires-Dist: cadquery==2.7.0; extra == 'test'
Requires-Dist: hypothesis>=6.150; extra == 'test'
Requires-Dist: pytest-xdist>=3.5; extra == 'test'
Requires-Dist: pytest>=8.0; extra == 'test'
Description-Content-Type: text/markdown

# ottocam

Automatic 2.5D CAM for hobbyists — turn a prismatic **STEP** file into
verified **GRBL G-code** with one command.

## Quickstart

```sh
./ottocam part.step          # writes part.nc next to the input, iff the program verifies clean
./ottocam part.step -o out.nc
./ottocam --help             # all options
```

`./ottocam` is a thin wrapper that runs the CLI in the project's `uv` environment, so
there's nothing to install or remember. Step/output paths are resolved relative to your
current directory, so it works from anywhere:

```sh
cd ~/my-parts && /path/to/ottocam bracket.step
```

**ottocam verifies what it writes.** It defaults to the full verification tier
and writes the `.nc` **only if** the emitted program passes the whole pyramid (static
checks, invariants, the section oracle, collision, and the real GRBL firmware). A feature
no available tool can machine — a hole too small, a slot too narrow — is **skipped** (left
as solid stock) and reported rather than blocking the rest of the part, so **exit `0` can
mean a partial machining**: a `PARTIAL: N feature(s) skipped` line and per-skip warnings
name what was left, and the `*.result.json` carries a `skipped` list. It refuses (exit 1,
**no** G-code) only when it can't understand the part, when nothing is machinable, or when
a produced program fails verification — always writing a `*.result.json` so the outcome is
inspectable. Exit codes: `0` ship (full or partial) · `1` refuse · `2` usage/IO error.

Common flags: `--tool-diameter D`, `--feed/--plunge/--rpm N`, `--max-doc N`,
`--z-origin top|bottom` (default `top` — Z0 at the top of stock), `--verify-only`.

## Cut many copies: `--grid`

Replicate one part into an N×M grid on a larger sheet and ship a single verified `.nc`:

```sh
./ottocam part.step --grid 3x2                          # 6 copies; minimal sheet auto-sized
./ottocam part.step --grid-fit --sheet 300x300          # pack as many as fit the sheet
./ottocam part.step --grid 3x2 --sheet 300x300 --grid-gap 15 --grid-margin 10
./ottocam part.step --grid 3x2 --sheet 300x300 --grid-spacing tool   # shared-cut raft
```

Spacing defaults to **auto** (a safe gap of 2× the tool diameter, so neighbouring cuts
never interact). `--grid-gap`/`--grid-pitch` set it explicitly; `--grid-spacing tool`
abuts parts exactly one tool diameter apart and clears the shared walls with common-line
slot passes, holding the whole grid as a tabbed raft. `--grid-margin` insets the cells
from every sheet edge; `--grid-rotate` permits the 90° orientation that fits more parts.

## Cut several different parts: `ottocam job`

Place multiple STEP files on one sheet and ship one verified program:

```sh
./ottocam job cutlist.json -o sheet.nc
```

```json
{
  "stock": { "x": 300, "y": 300, "z": 18 },
  "sequence": "part_major",
  "parts": [
    { "step": "bracket.step", "at": [10, 10] },
    { "step": "plate.step", "at": [140, 10], "rot": 90 }
  ]
}
```

Each part is recognized and verified independently, then rigidly placed; a fail-closed
gate enforces clearance between parts (≥ 2× tool Ø), in-sheet bounds, and thickness.
`"sequence": "op_major"` batches work by stage across parts (all pockets, then every
freeing profile last). One tool serves the whole job — the first part's sweep pins it,
or pin explicitly with `--tool-diameter`. Multi-solid assemblies split into per-part
STEPs with `./ottocam unpack assembly.step -o parts/`.

## Scope

2.5D parts: **vertical walls** (straight, arc, cylindrical, or freeform — the gate is
on the face's normal field, not the surface type), **flat floors/tops**, and
**upward-facing sloped walls** (drafts, cones, bowls — waterline-roughed to an accepted
stair-step finish), cut from mm STEP. Profiling, pocketing of any boundary shape (bores,
slots, stepped/islanded pockets, through-cutouts), drilling, and surfacing are supported;
undercut geometry and true 3D surfacing hard-fail rather than producing an unverified
path. `AGENTS.md` § "Scope: 2.5D parts + sloped walls" is the authoritative scope and
op-kind list; `CONCEPTS.md` is the domain vocabulary.
