ferrocad_theme 0.1.4

FerroCAD's visual-language tokens: linear at the draw contract, packed sRGB at the gpui boundary
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# FerroCAD

![The FerroCAD shell: 3D viewport, the Dev Tools command/model/property/console panes, and the modeless Grid settings card](screenshot.png)

**FerroCAD** is a Rust reimplementation of FreeCAD's
C++ `App` core, designed to be a **drop-in replacement for the `FreeCAD` Python package**. The
Python import namespace stays `FreeCAD`; the project/distribution is `ferrocad`.

## Status

| Track | What it is | State |
| --- | --- | --- |
| **Engine** | the pure-Rust document model behind the `FreeCAD` API | `A1`/`B1`/`A2`/`B2` done; conformance **160 passing** (`Document.py` 87/137, ~18 real gaps). Persistence (`C`) and the MVP surface (`D`) outstanding. |
| **App shell** | the `bite-gpui` window driving the engine through embedded CPython | `S1` done: decorated window, inspector, property editor, Python console, status bar. `S2`–`S6` planned. |
| **Distribution** | how it ships | done: `v0.1.4`, twenty-one crates on crates.io, four self-contained artifacts (base and Parametric AppImage/zip pairs) plus `cargo install`. |
| **Workbenches** | reusing upstream workbenches | `cargo xtask mods` fetches `Draft`; loading is best-effort and stops on the `FreeCADGui` stub (`Workbench`, `addWorkbench`, …) and the missing `FreeCAD.addImportType`. |

This is a proof of concept, not a product. It exists to validate the single
riskiest assumption of the rewrite plan: *that a Python script written against
FreeCAD's public `App` API can be served by a Rust implementation instead of the
C++ one, without changing the script.*

**Roadmap.** The headless engine MVP is defined in [docs/direction/mvp-path.md](docs/direction/mvp-path.md), the
interactive window in [docs/direction/app-shell-vision.md](docs/direction/app-shell-vision.md), and the full milestone
record in [docs/record/milestones.md](docs/record/milestones.md). Next: lock the MVP demo, then build the
`FreeCADGui` workbench host (`Workbench`, commands, workbench lifecycle) so an unmodified upstream
workbench can load.

## Install

### Download a prebuilt app

Self-contained builds from the
[latest release](https://github.com/ferrocad/ferrocad/releases/latest). Each one
bundles CPython, the `FreeCAD` facade and the `Draft` workbench, so no system
Python is needed:

| Platform | Download |
| --- | --- |
| Linux (x86_64) | [`ferrocad-x86_64.AppImage`]https://github.com/ferrocad/ferrocad/releases/latest/download/ferrocad-x86_64.AppImage |
| macOS (arm64) | [`FerroCAD.dmg`]https://github.com/ferrocad/ferrocad/releases/latest/download/FerroCAD.dmg |
| Windows (x86_64) | [`ferrocad-windows-x86_64.zip`]https://github.com/ferrocad/ferrocad/releases/latest/download/ferrocad-windows-x86_64.zip |

The Linux AppImage is a single executable:

```sh
chmod +x ferrocad-x86_64.AppImage && ./ferrocad-x86_64.AppImage
```

### Install from crates.io

`cargo install ferrocad` builds the same self-contained app from source. It needs a
Rust toolchain; install one with [rustup](https://rustup.rs) if you do not have it:

```sh
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
cargo install ferrocad
ferrocad
```

The binary embeds the Python payload and unpacks it on first run (see
[Embedded Python](#embedded-python-running-scripts-from-rust)). The Python package
(`pip install ferrocad`, the `FreeCAD` API without a GUI) is not on PyPI yet.

> **macOS**: the `.app` is ad-hoc signed, so a downloaded DMG needs
> `xattr -dr com.apple.quarantine /Applications/FerroCAD.app` (or right-click →
> Open) on first launch. **Windows**: the zip is unsigned, so SmartScreen may warn
> on first run.

## The idea

```mermaid
flowchart TD
    S["hello_freecad.py<br/>(unchanged FreeCAD API)"]
    M["python/FreeCAD<br/>facade"]
    P["crates/ferrocad_py<br/>ferrocad (PyO3)"]
    K["crates/ferrocad_core<br/>pure-Rust core"]
    S -->|"import FreeCAD"| M
    M --> P
    P --> K
```

* **`hello_freecad.py`** uses only the public FreeCAD API. The same file is
  intended to run against upstream FreeCAD.
* **`python/FreeCAD/`** is a drop-in module that speaks that API; it knows nothing
  about C++ or Coin. Since `ferrocad` is just a document-object model (no "application"),
  the facade adds the small App-level layer it lacks: a document registry,
  `ActiveDocument`, unique-name allocation, and `Version`.
* **`crates/ferrocad_py/`** exposes the model as real `#[pyclass]` types via PyO3
  (importable as `ferrocad`).
* **`crates/ferrocad_core/`** is the pure-Rust implementation behind `ferrocad_py`: no Python,
  no UI, just the object model.

## Workspace layout

FerroCAD is a Cargo workspace (edition 2024). Crate status:

| Crate | Role | Status |
| --- | --- | --- |
| `crates/ferrocad` | the base app: runs the general edition (`cargo run -p ferrocad`) | published (app binary) |
| `crates/ferrocad_types` | base value types: quantities, units, geometry (`Vector3`, `Placement`, …) | primary |
| `crates/ferrocad_theme` | the visual-language token table: linear draw tokens and packed-sRGB UI tokens, with the round-trip helpers | primary |
| `crates/ferrocad_graphics` | the neutral graphics primitives: the draw contract (`DrawPacket`/`RenderQueue`/`Geometry`/…), camera/viewport, and CPU picking | primary |
| `crates/ferrocad_core` | pure-Rust model: properties, documents, recompute DAG (re-exports `ferrocad_types`) | primary |
| `crates/ferrocad_geom` | the geometry seam: opaque `Shape`, `History`/`ElementMap`, the `GeometryBackend` trait | new |
| `crates/ferrocad_occt` | the OCCT backend: implements `GeometryBackend` (box/fuse/cut/fillet/place) | new — needs OCCT 7.8+ |
| `crates/ferrocad_part` | the Part workbench: registers `Part::Feature` and `Part::PropertyPartShape` | new — needs OCCT 7.8+ |
| `crates/ferrocad_part_py` | the `Part` Python module (`makeBox`, `Shape`); the composition root that links OCCT | new — needs OCCT 7.8+ |
| `crates/ferrocad_py` | PyO3 extension over `ferrocad_core` (module `ferrocad`) | primary |
| `crates/ferrocad_inventor` | the Coin/Inventor scene core: node AST, state accumulator, mesh compiler, events (re-exports `ferrocad_graphics`) | primary |
| `crates/ferrocad_stage` | the modern declarative CAD presentation: `ShapeItem`s on a `Stage`, tools and a `TransformGizmo`, compiled straight to `ferrocad_graphics` (no scene graph) | primary |
| `crates/ferrocad_wgpu` | the wgpu backend: pipelines, dynamic uniforms, MSAA, offscreen target, readback | primary |
| `crates/ferrocad_inventor_py` | the pivy-compatible `coin` facade (scaffolding, surfaced as `pivy.coin`) | new |
| `crates/ferrocad_stage_py` | the modern-stack Python surface (surfaced as `ferrocad.stage`) | new |
| `crates/ferrocad_gen` | generated PyO3 skeleton bindings from the `.pyi` model (M3c) | useful — drives the codegen tests |
| `crates/ferrocad_widgets` | reusable `bite-gpui` widgets (window chrome, field, console) | published |
| `crates/ferrocad_viewport` | the `bite-gpui` viewport: `InventorViewport` (image path), the navigation cube (`NaviCube`), the sample scene | primary |
| `crates/ferrocad_gpui` | library: reusable app shell (`run`/`run_with` + `HostConfig`), boots CPython | primary |
| `crates/ferrocad_parametric` | edition binary: the base app + the Part workbench (`cargo run -p ferrocad_parametric`) | new — needs OCCT 7.8+ |
| `crates/xtask` | build tasks: `cargo xtask bundle` stages a distribution | tool |

The GUI crates (`ferrocad_viewport`, `ferrocad_widgets`, `ferrocad_gpui`, `ferrocad`) are excluded from
`default-members` so `cargo build`/`cargo test` stay fast; `ferrocad_gpui` additionally carries the PyO3
`auto-initialize` feature, which conflicts with the `extension-module` feature of the extension crates.

## Build & run

```sh
./build.sh     # cargo build --release, then package the native libs into python/
./run.sh       # PYTHONPATH=python python3 hello_freecad.py
cargo run -p ferrocad   # the base app window (needs a display)
cargo run -p ferrocad_parametric -- --open target/demo/headless.FCStd  # basic .FCStd viewer
cargo run -p ferrocad_toy -- --render target/demo/toy.png          # headless PNG (no display, no OCCT)
cargo run -p ferrocad_parametric -- --render target/demo/part.png  # headless PNG with Part (needs OCCT)
cargo run -p ferrocad_parametric -- --script examples/headless_part.py \
    --render target/demo/part.png --size 1200x800             # box, bore, fillet
# full demo: headless script → .FCStd + PNG + STL
cargo run -p ferrocad_parametric -- --script examples/headless_gear.py \
    --render target/demo/gear.png --stl target/demo/gear.stl --size 1400x900
# open the saved file in the viewer
cargo run -p ferrocad_parametric -- --open target/demo/gear.FCStd
# or reopen and render/export it without a window
cargo run -p ferrocad_parametric -- --open target/demo/gear.FCStd \
    --render target/demo/gear_reopened.png --stl target/demo/gear_from_file.stl
cargo install ferrocad  # a self-contained binary: payload embedded, unpacked on first run
cargo xtask mods               # fetch upstream workbench scripts (default: Draft) into mods/
cargo xtask bundle             # stage the payload into target/dist/ferrocad (--debug for speed)
packaging/linux/appimage.sh    # wrap it into an AppImage (needs appimagetool)
```

`--render <png>` boots the edition's app script with `GuiUp == 0`, asks its display
hook for the tessellated document shapes, and draws one offscreen frame with the
same `Stage` → `WgpuRenderer` path the window uses. It needs a wgpu adapter (a
software Vulkan ICD is enough) but no display server; add `--size WxH` to choose
the framebuffer, and `--script <py>` to build the model first (the script runs after
the edition's sample document is created). `--open <file>` loads a saved document
at boot instead of the sample: with a window it is the basic `.FCStd` viewer (model
tree, properties and 3D view follow the file), and with `--render` it renders the
file. `--stl <path>` writes the same tessellation as a binary STL (it needs no GPU
and can be combined with `--render`). The generated `examples/toy_headless.png` is
the Toy edition's slab rendered this way; `examples/headless_part.py` is the Part
demo script, and `examples/headless_gear.py` builds the two-gear train shown in
`examples/gear_headless.png`.

The Parametric edition needs OCCT 7.8. Fetch the 7.8.1 prefix CI uses into
`.occt/prefix` once (recipe in
[docs/architecture/occt-bundling.md](docs/architecture/occt-bundling.md#31-local-development-no-exports-needed));
`.cargo/config.toml` then supplies `OpenCASCADE_DIR`, `OCCT_INCLUDE_DIR` and
`LD_LIBRARY_PATH` automatically, so no exports or `.env` file are needed.

Expected output:

```
FreeCAD version : 0.1.4
Active document : HelloWorld
Document label  : HelloWorld
Object count    : 1
  - Box (App::FeaturePython) label='Hello, FreeCAD'
obj.Description : Created by the Rust core
Properties      : ['Description']
Documents       : ['HelloWorld']
Active after close: None
OK
```

Tests:

```sh
. ../.toolchain/env.sh && cargo test --manifest-path Cargo.toml   # ferrocad_core unit tests
PYTHONPATH=python python3 -m unittest discover -s tests -v        # facade parity (M0/M1) + M4
PYTHONPATH=python python3 tests/test_fc_core.py                    # ferrocad bindings (M3b)
PYTHONPATH=python python3 tests/test_codegen.py                    # generated skeleton surface (M3c)
python3 tools/test_inventory.py                                    # .pyi parser (M3a)
python3 tools/test_codegen.py                                      # codegen logic (M3c)
python3 tools/test_conformance.py                                  # harness helpers (M3d)
PYTHONPATH=python python3 examples/file_roundtrip.py               # save/open a document (file load)
PYTHONPATH=python python3 examples/mvp_workflow.py                # the MVP acceptance demo (docs/direction/mvp-path.md)
. ../.toolchain/env.sh && cargo test -p ferrocad_gpui              # shell library tests (4)
```

Regenerate the M3c skeleton (`crates/ferrocad_gen/src/lib.rs`, committed):

```sh
python3 tools/codegen.py --root ../freecad-upstream --out crates/ferrocad_gen/src/lib.rs
```

Run upstream tests against our `FreeCAD` (conformance harness, M3d):

```sh
python3 tools/conformance.py --root ../freecad-upstream            # default curated files
python3 tools/conformance.py --root ../freecad-upstream --list     # list candidates
```

## Documentation

The Rust crates carry **Diátaxis-structured rustdoc** — a tutorial, how-to guides, an explanation of
the design, and the generated item reference:

```sh
cargo doc --no-deps --open                    # ferrocad_core + ferrocad_py
cargo doc --no-deps -p ferrocad_core          # one crate
cargo test --doc -p ferrocad_core             # the tutorial is a doc test
```

The crate page of `ferrocad_core` (`crates/ferrocad_core/src/lib.rs`) reads as a tutorial and an
explanation with `How-to`/`Reference` sections; it is rendered on docs.rs when published. The
per-module pages are the reference.

The **product surface is Python** (`import FreeCAD`) and deliberately mirrors upstream FreeCAD, so
the *behaviour* reference is FreeCAD's own. What FerroCAD adds is generated and verified:
[`docs/user/`](docs/user/README.md) renders one page per implemented workflow by **running** it
against the facade, and [`docs/python-api/`](docs/python-api/index.md) is a signature reference read
from the live modules. CI runs `tools/userdocs.py --check` and `tools/api_docs.py --check`, so neither
can drift from the code. Narrative notes live in the repository `docs/` ([index](docs/README.md)).
An mdBook edition of both — tutorials, how-to guides, reference, explanation — is
assembled by `tools/build_book.py` (`--build` renders it with
[mdBook](https://rust-lang.github.io/mdBook/)) and published at
<https://ferrocad.github.io/docs/> from the
[`ferrocad/docs`](https://github.com/ferrocad/docs) repository, whose Pages workflow
renders a shallow copy of `docs/` and `tools/`.
A Sphinx/mkdocstrings site is the natural next step once a Python doc build can run in CI; until
then rustdoc is the verifiable source of truth for the Rust side.

## Packaging

`pyproject.toml` builds the distribution **`ferrocad`** with
[`maturin`](https://www.maturin.rs/): it compiles `crates/ferrocad_py` into the top-level module
`ferrocad` and ships the pure-Python `FreeCAD`/`FreeCADGui` packages from `python/`. A user still
writes `import FreeCAD`.

The Rust library crates (`ferrocad_core`, `ferrocad_py`, `ferrocad_widgets`, `ferrocad_gpui`) and the
app (`ferrocad`) publish to **crates.io**, and the wheel publishes to **PyPI**. A `v*` tag drives both
[`.github/workflows/crates.yml`](.github/workflows/crates.yml) (publish order, payload staging) and
[`.github/workflows/release.yml`](.github/workflows/release.yml) (the platform artifacts). The
`abi3`/`extension-module` notes, the publish order, and the staging commands are in
[`docs/architecture/releasing.md`](docs/architecture/releasing.md) and [`docs/architecture/distribution.md`](docs/architecture/distribution.md).

`cargo install ferrocad` produces a **self-contained** binary: `crates/ferrocad` embeds the workspace
`python/` (and, when published with workbenches, `mods/`) and unpacks it on first run, because Cargo has
no data-file install step. The publish staging (`cargo xtask stage-payload`) keeps those directories out
of the crate source; they are copied in only for `cargo publish` and removed after.

Application artifacts (AppImage, `.dmg`, Windows zip) are built by
[`packaging`](packaging) from the payload staged by `cargo xtask bundle`.
Tagging `v*` runs [`.github/workflows/release.yml`](.github/workflows/release.yml),
which verifies the tag against the workspace version, runs the tests, builds the
three artifacts with the pinned Python runtime, and publishes a GitHub Release.
The same tag runs [`.github/workflows/crates.yml`](.github/workflows/crates.yml),
which publishes the library crates to crates.io (needs a `CARGO_REGISTRY_TOKEN`
secret).

## Embedded Python (running scripts from Rust)

The **shell library** (`crates/ferrocad_gpui`) embeds CPython and drives it from Rust. It passes
four headless `#[gpui::test]`s, one of which embeds the interpreter, imports a Python module, and
round-trips a click through a Python callback back into a `bite-gpui` element
(`cargo test -p ferrocad_gpui`). The base app (`crates/ferrocad`) is a thin binary over it
(`cargo run -p ferrocad`). This is the Blender-style model the M5 UI track builds on: Python
declares the UI/workbench, Rust owns the process and renders.

Note the layering: `ferrocad_core` is **pure Rust and does not embed Python**; the embedding lives
in the shell library. Workbench Python is driven by the shell, while the `FreeCAD` facade talks to
the `ferrocad` extension the ordinary ways (import).

## Python-only scripts & file loading

A plain workbench-style script that uses only the public `FreeCAD` API runs today —
`hello_freecad.py` is one, and `examples/file_roundtrip.py` demonstrates the **file** path:
create a document, `doc.saveAs(path)`, `FreeCAD.open(path)`, and read the objects/`PropertyLength`
back. The on-disk format is FerroCAD's own JSON under a `.FCStd` name; it is not yet upstream's
`.FCStd`/`zip` and importing real `Part` geometry is still out of scope.

```sh
PYTHONPATH=python python3 examples/file_roundtrip.py
```

## The Python bridge

`crates/ferrocad_py` builds the PyO3 extension `ferrocad` (M3b). `ferrocad.Document` /
`ferrocad.DocumentObject` are native `#[pyclass]` types over
`Arc<Mutex<ferrocad_core::Document>>`, built with `abi3` and
`PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1`, because this sandbox runs CPython 3.14, which is
newer than PyO3 0.25 officially targets. `FreeCAD/__init__.py` imports it unconditionally
and reports `FreeCAD.backend == "ferrocad"`.

PyO3 is a **hard build dependency on the CPython headers** (CI installs `python3-dev`).
An earlier M0 `ctypes`/C-ABI fallback was removed: it was not a second transport for the
same core but a second, narrower model, and every capability it had is available through
PyO3. See [`docs/architecture.md`](docs/architecture.md) for the call-path diagrams
and the decision.

See [`docs/direction/rewrite-strategy.md`](docs/direction/rewrite-strategy.md) for the direction and
[`docs/viewport/coin-bridge-reuse-assessment.md`](docs/viewport/coin-bridge-reuse-assessment.md)
for the underlying analysis.

## What is implemented vs. not

Implemented in **`ferrocad_core` / `ferrocad_py`** (the real model, not just a stub):

* a **full `Quantity`/`Unit` system**: 8-dimension signatures, an internal unit table
  (SI base + derived + imperial + prefixes), an expression parser (fractions, scientific
  notation, compound units, `pi`/`sin`/`cos`/`tan`, feet-inches `N'(expr)"`), arithmetic,
  `Value`/`UserString`/`getValueAs`/`toNumber`.
* `Property`/`PropertyContainer`, `StringHasher`/`StringID`.
* `Document`/`DocumentObject` with default naming, a `petgraph` dependency graph,
  topological recompute order, and `removeObject`.
* transactions (`open`/`commit`/`abort` + `undo`/`redo`), an `Observer` trait, and an
  expression parser/evaluator with `Object.Property` names and `recompute()`.

Exposed through the **`FreeCAD` facade** (enough for the hello world and a workbench-style script):

* documents: `newDocument`, `closeDocument`, `getDocument`, `listDocuments`,
  `ActiveDocument`, unique-name allocation, `Name`/`Label`.
* objects: `addObject` (default naming), `getObject`, `removeObject`, `Objects`,
  `CountObjects`, `Name`/`Label`/`TypeId`/`Document`, `recompute`.
* properties: `addProperty`, `get`/`setPropertyByName`, dynamic attribute
  mapping (`obj.Foo`), `PropertiesList`, `getTypeIdOfProperty`.
* a single Python bridge, reported as `FreeCAD.backend` (`"ferrocad"`).

Not implemented (deliberately out of scope for this milestone):

* the geometry kernel (`Part::Box` etc. produce no shape),
* upstream's `.FCStd` (zip) format — persistence is FerroCAD's own JSON,
* `App`/`Gui` split, Coin3D, Qt,
* `FeaturePython` scripting callbacks,
* thread-safety guarantees beyond a coarse global mutex.

## Next: the MVP app shell

The headless engine is the foundation; the next chunk is a **single interactive window**
that drives it — a developer tool with five pillars: **open/close/load/save**, **undo/redo**,
a **DOM-like inspector**, a **property editor**, and a **Python console**. It is one
`bite-gpui` window embedding CPython, calling the engine only through the public `FreeCAD`
API, so commands, console input and property edits all take the same path (and every edit is
one more undoable transaction). The `ferrocad_gpui` spike already proves the
Python-declared-UI ↔ `bite-gpui` round-trip headlessly.

The plan, architecture and slices (S1 shell skeleton → S2 lifecycle + commands → S3 inspector
→ S4 property editor → S5 console → S6 polish) are in
[`docs/direction/app-shell-vision.md`](docs/direction/app-shell-vision.md). The 3D viewport now docks into
the shell's pane as an image-path presenter (readback → `RenderImage`) drawing a sample
Inventor scene; the full `scenix`/`wgpu` viewport and the overlay/NaviCube work are in
[`docs/viewport/inventor-renderer.md`](docs/viewport/inventor-renderer.md).

**S1 is implemented** (`crates/ferrocad_gpui`): a titled `bite-gpui` window that boots an
embedded CPython interpreter, creates a sample document, and shows a model inspector, a
read-only property editor, a 3D viewport (the sample Inventor scene, image path), a Python
console and a status bar — all
driven live through the `FreeCAD` API (`python/ferrocad_shell`). Run it with
`cargo run -p ferrocad` (needs a display); the headless `#[gpui::test]` is the CI-proof path.

## Repository layout

```
Cargo.toml                Cargo workspace (edition 2024)
LICENSE                   LGPL-2.1-or-later (the `license` field is the SPDX id)
screenshot.png            the shell screenshot above
pyproject.toml            maturin packaging (distribution `ferrocad`)
hello_freecad.py          the milestone script (public FreeCAD API only)
examples/file_roundtrip.py  saveAs / open a document (file loading)
run.sh / build.sh         convenience wrappers
python/FreeCAD/           drop-in module: __init__.py (facade)
    Base.py               core data types re-exported from ferrocad (Vector/Matrix/Rotation/…)
    Units.py              units facade (Quantity)
    Console.py            minimal Print* logging facade
python/ferrocad.abi3.so   built PyO3 bindings (module `ferrocad`, gitignored; for the wheel/`build.sh`, not the app)
python/ferrocad_gen.abi3.so generated skeleton bindings, M3c (gitignored)
python/FreeCADGui/        console-mode Gui stub
python/ferrocad_spike/    Python declarative UI spike module
crates/ferrocad_core/     pure Rust core: quantities, properties, DAG, tx/observers/expr
crates/ferrocad_py/       PyO3 bindings over ferrocad_core (module `ferrocad`)
crates/ferrocad_gen/      generated PyO3 skeleton bindings (module `ferrocad_gen`)
    src/lib.rs            GENERATED by tools/codegen.py (committed)
crates/ferrocad_geom/     geometry seam: Shape, History, GeometryBackend trait
crates/ferrocad_occt/     OCCT backend (needs OCCT 7.8+)
crates/ferrocad_part/     Part workbench: Part::Feature, Part::PropertyPartShape
crates/ferrocad_part_py/  the `Part` Python module (makeBox, Shape); links OCCT
crates/ferrocad/          the base app binary (cargo run -p ferrocad)
    src/main.rs           entry: calls ferrocad::run()
    src/lib.rs            run/run_as + register_core_module (edition entry point)
    src/payload.rs        finds/unpacks the embedded Python payload (cargo install)
    build.rs              embeds python/ + mods/ when staged (embedded-payload feature)
crates/ferrocad_parametric/ edition binary: base app + Part (cargo run -p ferrocad_parametric)
    src/main.rs           registers the Part module, then ferrocad::run_as
crates/ferrocad_viewport/ the bite-gpui viewport: InventorViewport (image path), NaviCube, sample scene
    src/lib.rs            the crate surface (InventorViewport, NaviCube)
    src/inventor_view.rs  the gpui element + pointer→pick→listener interaction
    src/navicube.rs       the navigation-cube controller (placement, sync, hit-test, snap)
    src/scene.rs          the sample Inventor scene + hover listener
crates/ferrocad_gpui/     library: FerroCAD's bite-gpui app shell (boots CPython)
    src/lib.rs            run/run_with + HostConfig + window options
    src/python.rs         embedded-CPython bridge (boot, model_tree, properties, evaluate)
    src/shell.rs          the Shell view: inspector / property editor / console / status bar
    src/spike.rs          kept feasibility spike (Python-declared UI, headless)
crates/ferrocad_widgets/  reusable bite-gpui widgets (window chrome, field, console)
crates/xtask/             cargo xtask tasks (bundle: stage a distribution)
    src/main.rs           `cargo xtask bundle [--debug]`
.cargo/config.toml        the `cargo xtask` alias
mods/                     workbenches (FreeCAD Mod/ equivalent); packaged into the dist
packaging/                per-platform packagers wrapping the staged payload
    linux/appimage.sh      AppImage (needs appimagetool)
    macos/app.sh           .app + .dmg
    windows/portable.ps1   portable folder + .zip
    icon/ferrocad.svg      icon source
python/ferrocad_shell/    Python side of the shell (sample document, console, model queries)
tools/inventory.py        M3a: parse upstream .pyi stubs into an API model (Python ast)
tools/test_inventory.py   M3a tests (hermetic fixtures + upstream integration guard)
tools/codegen.py          M3c: emit PyO3 skeleton bindings from the API model
tools/test_codegen.py     M3c tests (hermetic generator logic)
tools/conformance.py      M3d: run upstream Mod/Test files against our FreeCAD
tools/test_conformance.py M3d tests (hermetic harness helpers)
tests/test_parity.py      behavioural checks (M0/M1)
tests/test_base_surface.py  end-to-end FreeCAD surface smoke tests (M4)
tests/test_fc_core.py     ferrocad_core via Python (M3b)
tests/test_codegen.py     generated skeleton surface (M3c)
```