# FerroCAD

**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
| **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:
| 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
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:
| `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)
```