BREP_app 0.1.0

The BREP CAD application: an eframe (egui + wgpu) host that draws the brep-render 3D engine into an egui frame — native + wasm from one codebase.
Documentation

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_appBREP_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:

    use brep_render::engine_state::EngineState;
    use eframe::egui;
    
    #[derive(Default)]
    pub struct MyPanel { /* the panel's OWN editing buffers only */ }
    
    impl MyPanel {
        pub fn new() -> Self { Self::default() }
    
        // Pass the `Store` too if the panel persists: (…, store: &dyn Store).
        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| { /* widgets; read/write via `state.*` */ });
        }
    }
    
  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 for prerequisites and the full matrix.

Web (wasm)

./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/:

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)

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)" 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:

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:

cargo run --example capture_dialogs    # writes docs/features/<name>_dialog.png (change-detected)

License and links

  • License: the repository's Autodrop3d 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.