Documentation site and CI workflows
An mdBook under docs/book: a user guide (getting started, UI tour, sketching, features, parameters, components and joints, sheet metal, plastic, files, shortcuts, MCP) and a developer guide (CONTRIBUTING, architecture, command engine, kernel boundary, persistent naming, test harnesses, and the existing docs/*.md pages). `cargo xtask book` generates the command reference from `solvecraft-cli commands` and renders the screenshots with `solvecraft-cli snapshot`, then runs mdbook. .github/workflows/pages.yml publishes the book at /solvecraft/ and the web app at /solvecraft/app/ (skipped while the repository is private). .github/workflows/ci.yml runs `cargo xtask ci` on pull requests. README and Help → Documentation link to the book. Fixes #28 Fixes #8 Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
2c3fec087e
commit
0d7e456bc1
35 files changed
+792
-5
No files matched your search
@@ -0,0 +1,22 @@
|
||||
# Architecture
|
||||
|
||||
SolveCraft is a Cargo workspace in layers. A crate only depends on crates in lower layers, and
|
||||
`cargo xtask layers` fails the build otherwise.
|
||||
|
||||
{{#include ../../../../README.md:architecture}}
|
||||
|
||||
The web app, `apps/solvecraft-web`, is the desktop front end compiled to wasm with
|
||||
[trunk](https://trunkrs.dev). `xtask` holds the repository's own tooling: `cargo xtask ci`,
|
||||
`layers`, `assets`, `oracle`, `parity`, `step-corpus` and `book`.
|
||||
|
||||
A few rules follow from the layering:
|
||||
|
||||
- **Only `crates/kernel` touches truck.** Everything above it sees SolveCraft's own types:
|
||||
bodies, faces, edges, meshes and measures. See [The kernel boundary](kernel.md).
|
||||
- **The document knows nothing about commands or the UI.** `crates/doc` holds the parameters,
|
||||
the feature timeline and the component tree, and evaluates them into a model.
|
||||
- **Everything the user can do is a command** in `crates/engine`. See
|
||||
[The command engine](commands.md).
|
||||
- **The front end is swappable.** Nothing below `ui-egui` depends on egui. The CLI and the MCP
|
||||
server run the same engine without a window, and `crates/render` has a CPU rasterizer so
|
||||
headless screenshots work anywhere.
|
||||
@@ -0,0 +1,38 @@
|
||||
# The command engine
|
||||
|
||||
Every user-visible action is a **command**: a `CommandSpec` registered in
|
||||
`crates/engine/src/cmd/`, with a dotted, lower-case id (`solid.extrude`, `sketch.create`,
|
||||
`timeline.rollback`). The toolbar, dialogs, marking menu, S box, scripts, the CLI, the control
|
||||
channel and MCP all run commands through `Session::execute(id, params)`. Commands take JSON
|
||||
parameters and never open dialogs; a dialog is a front-end form that builds the parameters.
|
||||
|
||||
```rust
|
||||
CommandSpec::new("solid.fillet", "Fillet", fillet)
|
||||
.at("SOLID", "MODIFY") // toolbar tab and panel ("" = not on the toolbar)
|
||||
.icon("fillet") // drawn in code, crates/ui-egui/src/icons.rs
|
||||
.key("F") // default shortcut
|
||||
.params("edges: [[x,y,z] | {body, index}]; radius: expr; …"),
|
||||
```
|
||||
|
||||
`run` is `fn(&mut Session, &Value) -> Result<Value>`, and `enabled` is
|
||||
`fn(&Session) -> Result<(), String>`; the error string is the disabled-button tooltip. The
|
||||
`params` string is the one-line documentation shown by `solvecraft-cli commands`, MCP's
|
||||
`list_commands` and the [command reference](../reference/commands.md).
|
||||
|
||||
A command that changes the design is undoable by default: the session snapshots the document
|
||||
before it runs, and a failing command leaves the document unchanged. Feature commands add a
|
||||
feature to the timeline and let the document evaluate it; they validate their inputs first
|
||||
(units, ranges, references) and return `BadParams` instead of building a broken feature.
|
||||
|
||||
Ids used before the 2026-10 rename still resolve through `crates/engine/src/legacy_ids.rs`, so
|
||||
old scripts, recipes, saved shortcuts and toolbar layouts keep working.
|
||||
|
||||
## Adding a command
|
||||
|
||||
1. Add a `CommandSpec` to the right file in `crates/engine/src/cmd/` and implement `run`.
|
||||
2. Write engine tests that measure the result: volume, area, and face/edge/vertex counts
|
||||
against values you can derive by hand.
|
||||
3. The hostile-input test (`hostile_params_never_panic` in `crates/engine/src/tests.rs`) runs
|
||||
your command automatically with every documented parameter set to junk. It must return
|
||||
errors, never panic.
|
||||
4. For UI work, add a scenario (see [Test harnesses](testing.md)).
|
||||
@@ -0,0 +1 @@
|
||||
{{#include ../../../../CONTRIBUTING.md}}
|
||||
@@ -0,0 +1 @@
|
||||
{{#include ../../../control-protocol.md}}
|
||||
@@ -0,0 +1,37 @@
|
||||
# The kernel boundary and truck
|
||||
|
||||
`crates/kernel` is SolveCraft's B-rep kernel. It wraps
|
||||
[truck](https://github.com/ricosjp/truck) (Apache-2.0) for topology, NURBS geometry, booleans,
|
||||
tessellation and STEP parsing, and it is the only crate allowed to use truck. Callers get
|
||||
SolveCraft types (`Body`, face and edge indices, meshes, measures) and a
|
||||
`KernelError` instead of truck types and panics.
|
||||
|
||||
## `guard`
|
||||
|
||||
truck can panic on geometry it doesn't handle. Every call into it goes through
|
||||
`solvecraft_kernel::guard`, which catches the panic and turns it into
|
||||
`KernelError::Internal`, leaving the model unchanged:
|
||||
|
||||
```rust
|
||||
pub fn guard<T>(what: &str, f: impl FnOnce() -> Result<T>) -> Result<T>
|
||||
```
|
||||
|
||||
A failing feature shows an error on the timeline; it never takes the app down.
|
||||
|
||||
## What is our own
|
||||
|
||||
truck has no fillets, chamfers, shells or face edits, so these are SolveCraft code in the
|
||||
kernel: constant, chord and variable-radius fillets with corner blends, chamfers, shell and
|
||||
offset, Delete Face, Split Face, Replace Face, sewing, STEP and IGES reading and writing beyond
|
||||
truck's parser, and face provenance (which input face each output face came from, for
|
||||
[persistent naming](naming.md)).
|
||||
|
||||
## Vendored truck crates
|
||||
|
||||
Patched copies of `truck-shapeops`, `truck-stepio` and `truck-meshalgo` live in `vendor/`,
|
||||
wired in with `[patch.crates-io]`. The patches fix robustness bugs found by our tests and the
|
||||
STEP corpus. `vendor/` is excluded from our fmt and clippy rules, and each patch should go
|
||||
upstream when it can.
|
||||
|
||||
Some truck assertions only run in debug builds and can fail on valid geometry, so tests and CI
|
||||
run with `--release`.
|
||||
@@ -0,0 +1,22 @@
|
||||
# Persistent naming
|
||||
|
||||
A fillet stores the edges it rounds, and a sketch stores the face it sits on. If those were face
|
||||
and edge *indices*, an upstream edit that adds a face would move the fillet to the wrong edge.
|
||||
SolveCraft stores **names** instead (`crates/doc/src/naming.rs`):
|
||||
|
||||
- A face of an evaluated body is named after how it was made. A face lying on a face of the
|
||||
feature's input keeps that face's name; a face new to the feature is named by its role:
|
||||
`F<feature>:side:<sketch curve>`, `F<feature>:start` and `:end`, `F<feature>:blend:<k>` and
|
||||
so on.
|
||||
- Pieces of a split face get `#1`, `#2`, … in order along the longest axis.
|
||||
- An edge is named by the two faces it separates.
|
||||
- Pattern and mirror copies are named after their sources plus the instance.
|
||||
|
||||
Names are computed lazily, the first time something asks, from the body, the feature that made
|
||||
it and the model just before that feature. Face provenance from the kernel (which input face an
|
||||
output face came from) does most of the work.
|
||||
|
||||
When a feature evaluates, each stored reference is resolved by name. If no face has the name
|
||||
any more, the reference falls back to the geometry it recorded (a point on the face, a normal),
|
||||
and the feature shows a warning so the user can check it. A reference that matches nothing is
|
||||
an error, never a guess.
|
||||
@@ -0,0 +1 @@
|
||||
{{#include ../../../oracle.md}}
|
||||
@@ -0,0 +1 @@
|
||||
{{#include ../../../parity.md}}
|
||||
@@ -0,0 +1 @@
|
||||
{{#include ../../../step-import.md}}
|
||||
@@ -0,0 +1,40 @@
|
||||
# Test harnesses
|
||||
|
||||
`cargo xtask ci` runs everything a pull request must pass: fmt, clippy with `-D warnings`, the
|
||||
tests in release mode, `xtask layers`, `xtask assets` and the wasm build check.
|
||||
|
||||
| Harness | Where | What it catches |
|
||||
|---|---|---|
|
||||
| Unit and engine tests | next to the code, `*_tests.rs` | measured results: volume, area, topology counts against derived values |
|
||||
| Hostile input | `hostile_params_never_panic`, `crates/engine/src/tests.rs` | every command × every documented parameter × junk values: errors, never panics |
|
||||
| Sketch fuzz | `crates/engine/src/cmd/sketch_fuzz_tests.rs` | random sketches through the solver and profile finder |
|
||||
| UI scenarios | `crates/ui-egui/tests/scenarios/*.json` | the real app, headless: clicks, keys, drags, assertions |
|
||||
| Layout tests | `crates/ui-egui/tests/ui_layout.rs`, `window_widths.rs` | panels and dialogs at every window width |
|
||||
| Design-file corpus | `apps/solvecraft-cli/tests/corpus` | every saved file version still opens and evaluates the same |
|
||||
| Kill mid-save | `apps/solvecraft-cli/tests/kill_mid_save.rs` | a save killed at any moment leaves a readable file |
|
||||
| Fusion oracle | `cargo xtask oracle` → [docs/oracle.md](oracle.md) | parts rebuilt from recipes, compared with Fusion's measurements |
|
||||
| STEP corpus | `cargo xtask step-corpus` → [docs/step-import.md](step-import.md) | STEP files from Fusion opened and measured |
|
||||
|
||||
## UI scenarios
|
||||
|
||||
A scenario is a JSON list of steps run against the real app with a headless egui context:
|
||||
|
||||
```json
|
||||
[
|
||||
{"start": "sketch.create"},
|
||||
{"click": {"plane": "XY"}},
|
||||
{"start": "sketch.rectangle.two_point"},
|
||||
{"click": {"sketch": [0, 0]}},
|
||||
{"click": {"sketch": [40, 30]}},
|
||||
{"expect": {"errors": 0}}
|
||||
]
|
||||
```
|
||||
|
||||
A new file in `crates/ui-egui/tests/scenarios/` is picked up automatically. To record a bug
|
||||
before it's fixed, add a `{"pending": "why"}` step; its test is then ignored with that reason, and
|
||||
the fix removes the step.
|
||||
|
||||
## Release mode
|
||||
|
||||
CI runs the tests with `--release`, because some truck assertions only exist in debug builds
|
||||
and fail on valid geometry. If a test fails only in debug, say so in the PR.
|
||||
Reference in new issue
Block a user