brep-app — the BREP CAD application
The BREP CAD application (publishes on crates.io as BREP_app, lib
brep_app): an eframe (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_viewdraws 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 viaRenderCore::new(device, queue, format)from eframe'sRenderState.- That offscreen texture is composited into egui's own frame by an
egui_wgpupaint callback — a fullscreen-triangle blit into egui's render pass at the viewport rect egui scissors for the callback. The engine's nativeRgba8Unormoutput is linearized in the blit when egui's target is sRGB so colors round-trip exactly. - The toolbar + 3D ViewCube drive
EngineStatelive (zoom-to-fit, projection toggle, standard views);camera_state_json()is published for the verifiers. Pointer/wheel/ViewCube over the viewport route intoEngineState(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, theegui_wgpublit 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.rsis 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.rsis the reusable custom-painted tree-node widget both trees are built on.settings.rs— schema-driven display settings (persisted via theStore).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: onefield_inputrenderer per field kind (Color,Bool,Enum,Number/Range,Text,Vec3, references, and the expression-capableScalar), 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(andModelStore) 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 sameHistoryRunnertrait 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:
-
Add
src/panels/<name>.rswith a state struct and ashowmethod:use EngineState; use egui; -
Register it: add
pub mod <name>;tosrc/panels/mod.rs. -
Add one field to
BrepAppinsrc/app.rs(<name>: MyPanel) and construct it inBrepApp::new(<name>: MyPanel::new()). -
Add one
self.<name>.show(ui, &mut self.state);call ineframe::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 andRenderSettings::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 toEngineState(which marks the GPU state dirty) and persisted through theStore. - Feature dialogs come from the kernel:
brep_render::features::feature_form_fieldsmaps each feature'sinputParamsSchema(the kernel'sfeature_schema_catalogue) to the sameFormFieldshape, rendered by the samesrc/form.rsengine. Editing a param callsEngineState::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 byEngineState): the ordered features + rollback index are the single source of truth with undo/redo; the UI keeps no copy and mutates only throughEngineStatemethods.
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 for prerequisites and the full matrix.
Web (wasm)
or manually, from BREP_app/:
# 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)
WGPU_POWER_PREF=high
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)" 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:
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:
License and links
- License: the repository's Autodrop3d
LICENSE.md(license-filein 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.mdat the repository root for the order and validation.