brep-app — the BREP CAD application
The BREP CAD application (on crates.io as BREP_app, live at 0.1.0, this
tree 0.2.0; 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_kernel,
BREP_gizmos, BREP_reconstruction }.
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— the Settings window: one tab per section (Display, the schema-driven display settings;Assemblies, the BOM columns;Per-Solid Colors), each drawn as atree.rstree, 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 model-document modal: New / Open / Save / Save As, plus Import, Export, flat-pattern export, and Insert-Component (oneFileActionper mode).file_explorer.rs— the reusable, embeddable file explorer shared by Open, Save As, Import, and Insert Component. A single click SELECTS a file (highlight + footer preview); a double-click, Enter, or the footer confirm button opens it; ↑/↓ move the selection; folders enter on a single click. Everything is driven through theModelStoreseam, identical on web and desktop.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 unified persistence seam, the one platform exception:ModelStorehandles settings, dock layout, and model documents through the sameread/writeAPI, with a native filesystem implementation and a wasm in-memory mirror backed asynchronously by IndexedDB.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
Open, Save As, import, and export use the same in-app file explorer on desktop
and web; no OS-native file-dialog dependency is used. On desktop the explorer
can move to parent, home, and filesystem-root directories and browse the real
filesystem. In the browser it navigates the application's persistent virtual
folder tree under /models (stored in IndexedDB), because browser sandboxing
does not expose the host filesystem. Both implementations support creating and
navigating folders through the same explorer API.
The explorer follows a conventional file-dialog interaction model: a single
click SELECTS a file (highlight + a footer selection preview), and a
double-click, the Enter key, or the footer confirm button (Open / Import /
Insert) commits it — so the destructive "open" gesture is deliberate, which
matters most for Save As (pick a name to overwrite without immediately
committing). Folders enter on a single click. ↑/↓ move the selection whenever a
confirm action is offered; Save As leaves Enter to its own name field.
A left sidebar offers quick-access places (Home / Documents / Downloads /
Models / root on desktop; the /models root on web) plus user-pinned folders
(persisted under the reserved @pinned key); the top bar is a clickable
breadcrumb with an editable path field and back/forward history; and the whole
browse area is a drag-resizable region capped to the viewport. Files list in a
sortable Name / Type / Size / Date table (the primary layout) whose body
scrolls; hidden dotfiles are off by default with a toggle to reveal them —
which never blocks typing a hidden path directly. Size/date come from the
filesystem on desktop; the browser reports size from stored length and leaves
date blank (the IndexedDB records store no timestamps).
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 --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
those user-facing docs into BREP_app/web/help/ (gitignored, wiped each run);
docs/developer/ is not included. ./build.sh app regenerates the help site
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/). - Live on crates.io:
BREP_app0.1.0; this tree is 0.2.0. See the repository-root publishing guide for the crate-family order and validation.