# Phase 0 — Containerize the CLI

> **Status: ✅ DONE (2026-06-17).** `step-parser:dev` builds for linux/amd64 and all
> three sample assemblies (MR16/MR27/MR28) pass the full pipeline — load, BOM .xlsx,
> 6-view OSMesa thumbnails, geometry query, and dimensional-diagram SVG. Validated on
> a Mac (arm64) via QEMU emulation; image arch confirmed x86_64. Deps frozen in
> `requirements.lock.txt`. Two fixes were required (see **Fixes applied** below).

**Goal:** prove the heavy native stack runs on **linux/amd64** (the Unraid target) before any web code is written. This is the highest-risk part of the [step-parser](projects/active/step-parser.md) roadmap; everything after it is conventional React-on-FastAPI.

What's being de-risked:

| Path | Library | Container concern |
|------|---------|-------------------|
| STEP load + geometry | build123d → cadquery-ocp (OpenCASCADE) | manylinux x86_64 wheels; OCC needs GL/X11/OpenMP runtime libs |
| 6-view thumbnails | trimesh + pyrender | **offscreen GL with no GPU** → forced onto OSMesa software rasterizer |
| Dimensional diagram | cairosvg | cairo / pango / gdk-pixbuf system libs |
| BOM | pandas + openpyxl | none |
| Translation | anthropic (Claude API) | network + `ANTHROPIC_API_KEY` (skipped in this phase) |

## Files (all in repo root, `skill.src/` untouched)

- `Dockerfile` — `python:3.11-slim-bookworm`, amd64-pinned, OSMesa + cairo system libs
- `requirements.txt` — Python deps (floor pins; freeze to a lockfile after first green build)
- `.dockerignore` — keeps `.step` samples, drops caches and pre-generated outputs
- `.env.example` — `ANTHROPIC_API_KEY` (translation only)
- `build.sh` — buildx build for linux/amd64 (+ registry push hints)
- `smoke-test.sh` — runs MR16/MR27/MR28 through BOM + query + diagram; checks artifacts

## Run it (on a Docker host)

> Docker is **not** installed on the dev Mac this was authored on, and it's arm64.
> The build runs anywhere with Docker + buildx: Unraid itself, or a Mac/PC with
> Docker Desktop. From arm64 the amd64 build uses QEMU emulation (slow first time;
> the OpenCASCADE wheel is large).

```bash
./build.sh          # → step-parser:dev (linux/amd64)
./smoke-test.sh     # → _phase0_out/ with xlsx, pngs, svg per sample
```

Ad-hoc single file:

```bash
docker run --rm -v "$PWD/_phase0_out:/data" step-parser:dev "/data/MyPart.step" --no-translate
```

## Pass criteria

For each of the three samples the smoke test must produce, with a clean exit:

- `*_bom.xlsx` (kernel loaded + assembly traversed + Excel written)
- at least one `*_<view>.png` (pyrender succeeded under OSMesa — the riskiest item)
- `*__external-diagram.svg` (cairosvg succeeded)

All three green ⇒ Phase 0 done. Freeze the lockfile and move to Phase 1 (FastAPI wrapper).

## Fixes applied during Phase 0

1. **PyOpenGL / OSMesa version clash (packaging).** `pyrender 0.1.45` hard-pins
   `PyOpenGL==3.1.0`, but the OSMesa offscreen backend needs `OSMesaCreateContextAttribs`
   (added in PyOpenGL ≥3.1.5). The Dockerfile installs pyrender's pin, then force-upgrades
   with `pip install --no-deps --upgrade "PyOpenGL==3.1.7"` (leaves pyrender untouched,
   avoids the resolver conflict). Without this, all 6 renders fail with an ImportError.
2. **`external_diagram.py` line 318 `KeyError: 'position'` (source bug).** `_arrange_views()`
   returns view dicts keyed `ox`/`oy`, but the layout map read a non-existent `position` key —
   crashed the diagram on any platform. Fixed to `{v["name"]: (v["ox"], v["oy"]) ...}`.

## Known follow-ups (deferred, out of Phase 0 scope)

- **Active-area detection is a no-op on Linux.** `external_diagram.py` imports `OCC.Core.*`
  (pythonocc-core) for active-area detection, but the loader uses build123d's `OCP` kernel.
  The import fails and is caught (logged as a warning) — the diagram still renders, just
  without the auto-detected active-area dimension. Porting `OCC` → `OCP` is a Phase 1 task.
- **Pin to the lockfile for reproducible Unraid builds.** `requirements.lock.txt` is frozen;
  switch the Dockerfile's `COPY requirements.txt` / install line to it when convenient.

- **FreeCAD fallback is macOS-only right now.** `skill.src/modules/loader.py` hardcodes
  `/Applications/FreeCAD.app/...`, so on Linux the fallback no-ops and returns `None`.
  build123d is the primary path and should handle the samples; wiring a Linux
  `freecadcmd` fallback means editing `loader.py` and is left for later.
- **Lockfile.** Replace the floor pins with `requirements.lock.txt` once the build is green.
- **OSMesa is CPU rendering.** Fine for correctness validation; thumbnail speed on the
  ~25 MB sample assemblies is a Phase 1+ concern. The roadmap's recommended pivot —
  export GLB and render client-side with three.js — sidesteps server GL for the live viewer.
