# brep-app — the BREP CAD application
The BREP CAD application (publishes on crates.io as **`BREP_app`**, lib
`brep_app`): an [`eframe`](https://github.com/emilk/egui) (egui + wgpu) app
hosting the `brep-render` engine on the `BREP` kernel — one codebase for
**web and desktop**, no DOM, the 3D drawn by the engine and the UI drawn by
egui, sharing one wgpu frame. Build the wasm app with `./build.sh app` at the
repository root; the generated docs site lands in `web/help/`.
Dependency chain: `BREP_app` → `BREP_render` → { `BREP` (the kernel),
`BREP_gizmos` }.
## Architecture
* `EngineState` (`brep-render`) stays the windowing-agnostic brain — scene,
camera, controls, settings, selection, sketch mode, history, widgets,
pointer/wheel/ViewCube/pick. Not forked; panels borrow `&mut EngineState`.
* `RenderCore::render_to_view` draws the 3D into an **app-owned offscreen
texture** (its own 4× MSAA + full-target clear + submit) on **eframe's shared
wgpu device/queue** — the render core is built via `RenderCore::new(device,
queue, format)` from eframe's `RenderState`.
* That offscreen texture is composited into egui's own frame by an
**`egui_wgpu` paint callback** — a fullscreen-triangle blit into egui's render
pass at the viewport rect egui scissors for the callback. The engine's native
`Rgba8Unorm` output is linearized in the blit when egui's target is sRGB so
colors round-trip exactly.
* The toolbar + 3D ViewCube drive `EngineState` live (zoom-to-fit, projection
toggle, standard views); `camera_state_json()` is published for the
verifiers. Pointer/wheel/ViewCube over the viewport route into `EngineState`
(mirroring the engine's own desktop shell).
### Module layout (one module per panel)
`src/app.rs` is a **thin shell**: it owns `EngineState` (the single brain), the
`Viewport`, the `Store`, and one small state value per panel, and its
`eframe::App::ui` just lays the panels out. The heavy code lives in focused
modules:
* `src/viewport.rs` + `src/viewport/` — the 3D viewport: the engine render
core, the app-owned offscreen texture, the `egui_wgpu` blit callback +
shader, viewport input routing, and in-viewport labels.
* `src/panels/` — one file per UI surface, all following one pattern:
* `toolbar.rs` — the top action strip (undo/redo, wireframe, zoom-to-fit,
standard views, the file-action seam); `toolbar_button.rs` is the single
source of truth for toolbar-button styling, shared by every toolbar.
* `history.rs` — the history feature tree + the schema-driven feature
dialogs.
* `scene.rs` — the Scene tree; `tree.rs` is the reusable custom-painted
tree-node widget both trees are built on.
* `settings.rs` — schema-driven display settings (persisted via the
`Store`).
* `selection.rs` — the selection filter (which entity kinds a click picks).
* `sketch.rs` — the 2D sketcher's side-panel entry point + in-sketch bars.
* `expressions.rs` — the variable sheet (`name = expr;`) feature params
resolve against.
* `file.rs` — the New / Open / Save / Save As modal for model documents.
* `info_windows.rs` — pinned per-entity inspector windows.
* `context_bar.rs` / `action_rail.rs` / `mode_bar.rs` — the
selection-driven context actions, the shared right-side action rail that
renders them, and the pinned Finish/Cancel controls for special modes.
* `toasts.rs` — transient notices queued by the engine.
* `src/form.rs` — the generic, schema-driven form engine: one `field_input`
renderer per field kind (`Color`, `Bool`, `Enum`, `Number`/`Range`, `Text`,
`Vec3`, references, and the expression-capable `Scalar`), used by **both**
the settings tree and the feature dialogs — the structure of the schema
drives the UI; no hand-coded widget per field.
* `src/store.rs` — the storage seam, the one platform exception: `trait
Store` (and `ModelStore`) with a native impl (config dir) and a wasm impl
(`localStorage`).
* `src/palette.rs` — a generic searchable command-palette modal (used for
"pick one of N named things", e.g. add-feature).
* `src/worker.rs` — the wasm history runner: executes the history in a
dedicated web worker behind the same `HistoryRunner` trait as the engine's
native thread runner, so the UI stays responsive during a run.
* `src/fonts.rs` — the bundled UI fonts (`assets/`, embedded at compile time).
### Adding a panel
`EngineState` (brep-render) is the single brain; a panel is just a small state
struct + a `show` method that borrows `&mut EngineState`. To add one — **nothing
else changes**:
1. Add `src/panels/<name>.rs` with a state struct and a `show` method:
```rust
use brep_render::engine_state::EngineState;
use eframe::egui;
#[derive(Default)]
pub struct MyPanel { }
impl MyPanel {
pub fn new() -> Self { Self::default() }
pub fn show(&mut self, ui: &mut egui::Ui, state: &mut EngineState) {
egui::CollapsingHeader::new("My panel")
.id_salt("my-panel")
.default_open(false)
.show(ui, |ui| { });
}
}
```
2. Register it: add `pub mod <name>;` to `src/panels/mod.rs`.
3. Add one field to `BrepApp` in `src/app.rs` (`<name>: MyPanel`) and construct it
in `BrepApp::new` (`<name>: MyPanel::new()`).
4. Add one `self.<name>.show(ui, &mut self.state);` call in `eframe::App::ui`,
where you want it in the left column.
Keep the model state in `EngineState` — panels own only transient editing
buffers. That keeps parallel panel work conflict-free (different files, one field
+ one call each in the shell).
## Schema-driven UI
There is deliberately no hand-written dialog code per feature or setting:
* **Display settings** come from the engine:
`brep_render::style::settings_schema()` returns the ordered field list and
`RenderSettings::to_json()`/`apply_json()` are a round-trip identity, so
adding a field to the engine schema grows a widget with zero UI code
changes. Edits are applied to `EngineState` (which marks the GPU state
dirty) and persisted through the `Store`.
* **Feature dialogs** come from the kernel:
`brep_render::features::feature_form_fields` maps each feature's
`inputParamsSchema` (the kernel's `feature_schema_catalogue`) to the same
`FormField` shape, rendered by the same `src/form.rs` engine. Editing a
param calls `EngineState::update_feature_params` → the engine mutates its
history and re-runs → the viewport updates live. Numeric fields accept
expressions resolved against the expressions panel's variable sheet.
* **History** is engine-owned (`brep_render::history::History`, held by
`EngineState`): the ordered features + rollback index are the single source
of truth with undo/redo; the UI keeps no copy and mutates only through
`EngineState` methods.
## Version alignment (the crux)
`brep-render` is on **wgpu 29**. `eframe 0.35 → egui 0.35 → egui-wgpu 0.35 →
wgpu ^29.0`, so all three unify on **wgpu 29** — the paint callback can hand
eframe's device/queue/renderpass straight to the wgpu-29 `RenderCore`. No wgpu
bump to `brep-render`.
`eframe 0.35` requires **Rust ≥ 1.92**. The repo's root `rust-toolchain.toml`
pins the whole crate family — kernel, render engine, and this crate — to 1.92,
so plain `cargo` and `wasm-pack` already use it; no `+toolchain` needed.
## Build & run
`./build.sh` at the repository root is the front door — see the [root
README](../README.md) for prerequisites and the full matrix.
### Web (wasm)
```sh
./build.sh # wasm-pack build BREP_app --target web --out-dir pkg --release
./build.sh serve # serve; open http://localhost:8080/web/
```
or manually, from `BREP_app/`:
```sh
wasm-pack build --target web --out-dir pkg --release # add --no-opt for a faster dev build
python3 -m http.server 8099 --bind 127.0.0.1 # serve the crate root
# open http://127.0.0.1:8099/web/index.html
```
`web/index.html` loads `pkg/brep_app.js` and calls the exported
`start("brep_canvas")`. eframe uses WebGPU when available and falls back to
WebGL2.
### Desktop (native)
```sh
cargo build --release --features native-dialog
WGPU_POWER_PREF=high ./target/release/brep-app
```
The `native-dialog` feature enables native (rfd) Open/Save file dialogs;
without it, file management falls back to the models-dir listing + name field.
`WGPU_POWER_PREF=high` asks wgpu for the discrete GPU. `cargo build` is the
compile-only gate on headless boxes.
### Windows (cross-compiled from Linux)
See the [root README's "Windows (cross-compiled from
Linux)"](../README.md#windows-cross-compiled-from-linux) section — the
toolchain setup and linker config live there; the build itself is
`cargo build --release --features native-dialog --target x86_64-pc-windows-gnu`
from this directory.
## Verification
Browser-driven UI verification scripts live in `web/verify_*.mjs`
(Playwright + system Chrome, resolved from the repo-root `node_modules`;
override with `PLAYWRIGHT_DIR`). After building the wasm app:
```sh
node web/verify.mjs # baseline: shaded render, camera controls, 0 console errors
```
plus the focused scripts (`verify_settings.mjs`, `verify_history.mjs`,
`verify_selection.mjs`, `verify_ref_select.mjs`, `verify_scene.mjs`, …), each
driving the real egui widgets and asserting on screenshots + the published
camera/hit state.
## Documentation
User docs live in the repo-root `docs/` tree (`docs/features/`,
`docs/panels/`). The `brep-docs` crate generates the in-app help site from it
into `BREP_app/web/help/` (gitignored, wiped each run) — `./build.sh app`
regenerates it when the generator crate is present. The feature-dialog
screenshots embedded in the docs are captured headlessly from the real
schema-driven dialogs:
```sh
cargo run --example capture_dialogs # writes docs/features/<name>_dialog.png (change-detected)
```
## License and links
- License: the repository's Autodrop3d [`LICENSE.md`](LICENSE.md)
(`license-file` in the manifest).
- Repository: <https://github.com/mmiscool/NURBS_BREP_kernel> (this crate
lives in `BREP_app/`).
- The crate family is ready to publish but deliberately unpublished — see
`/PUBLISHING.md` at the repository root for the order and validation.