# 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 `*_.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.