Some checks failed
makepad-table / model (push) Has been cancelled
makepad-table / widget (push) Has been cancelled
makepad-table / hygiene (push) Has been cancelled
PDF engine / engine (push) Has been cancelled
PDF engine / makepad-integration (push) Has been cancelled
PDF engine / fuzz (push) Has been cancelled
Payment domain, storage, platform and UI / isolated-payment-tests (push) Has been cancelled
Payment domain, storage, platform and UI / payment-ui-tests (push) Has been cancelled
repo hygiene / hygiene (push) Has been cancelled
doc-engine / engine (push) Has been cancelled
doc-engine / coverage (push) Has been cancelled
doc-engine / consumer (push) Has been cancelled
email / gates (push) Has been cancelled
email / email-domain (push) Has been cancelled
email / nigig-email (push) Has been cancelled
email / supply-chain (push) Has been cancelled
nigig-build (CAD) / supply-chain (push) Has been cancelled
nigig-build (CAD) / cad-module (push) Has been cancelled
nigig-build (CAD) / full-crate-check (push) Has been cancelled
nigig-build (CAD) / cad-engine-coverage (push) Has been cancelled
nigig-build (CAD) / doc-workspace-coverage (push) Has been cancelled
nigig-build (CAD) / cad-widget-coverage (push) Has been cancelled
nigig-map / test (push) Has been cancelled
sms / gates (push) Has been cancelled
sms / robius-sms (push) Has been cancelled
sms / android (push) Has been cancelled
sms / nigig-sms (push) Has been cancelled
sms / supply-chain (push) Has been cancelled
spreadsheet / engine-coverage (push) Has been cancelled
spreadsheet / ui-controller-coverage (push) Has been cancelled
232 lines
12 KiB
Markdown
232 lines
12 KiB
Markdown
# Fab → nigig-build CAD gap implementation plan
|
|
|
|
- **Date:** 2026-08-27
|
|
- **Baseline:** code-verified review of `makepad/libs/fab` (gitdab work branch) vs
|
|
`crates/apps/nigig-build/src/.../workspace/cad` (current HEAD).
|
|
- **Scope:** port user-facing capabilities from the `fab` reference CAD into the
|
|
nigig-build CAD, **adapted to our architecture**, not wholesale copy.
|
|
- **Adaptation rules (from earlier decisions):**
|
|
1. Port **algorithms/logic** from fab; keep our `CadNode` flat arena, our
|
|
batching/instancing renderer, our mobile-first 430x860 DSL, our tool model.
|
|
2. fab is an **architecture viewer/inspection** app (measure, section, isolate,
|
|
explode, sun study, walk, render) — it has **no geometry-authoring tools**.
|
|
Our CAD already has authoring (18 tools). So every port target is a
|
|
*reader/inspection/rendering* feature we lack, layered on our existing model.
|
|
3. fab communicates only through `api.rs` `ShellAction`s + `AppState`. We adapt
|
|
that as: workspace button/action handlers + our existing dirty-flag sync.
|
|
- **Completion standard (echoes repo convention):** a phase is done only when its
|
|
numbered items all land with unit tests, compile clean, and `cargo test -p
|
|
nigig-build --lib` stays green.
|
|
|
|
---
|
|
|
|
## Inventory: what fab has that we lack (verified)
|
|
|
|
| fab capability | file | status in our CAD |
|
|
|---|---|---|
|
|
| Section planes (drag handle, caps, GPU discard) | `tools/section.rs`, `viewport/dsl.rs` | **missing** — big gap |
|
|
| Explode view (by-storey / by-element) | `tools/explode.rs` | **missing** |
|
|
| Sun study (NOAA solar, day/hour scrub, compass) | `tools/sun_study.rs`, `tools/overlay.rs` | **missing** |
|
|
| Full object snap incl. midpoints/face + glyph preview | `tools/snap.rs` (we have most already) | **partial** — we lack glyph/ghost preview + normal |
|
|
| Element info card (I) + reveal in outliner | `tools/info.rs` | **missing** |
|
|
| Command palette (F3 fuzzy) + keymap help (F1) | `ui/command_palette.rs`, `ui/keymap.rs` | **missing** — high value, low risk |
|
|
| Isolate/solo/hide/unhide (H/Shift+H/Alt+H, `/`) | `tools/isolate.rs` | **partial** — we have isolate (I) + per-part hide via outliner; no solo, no unhide-all hotkey |
|
|
| F12 high-res render + Save PNG / track-to-mp4 | `render/mod.rs` | **missing** (we have ray mode, no export render) |
|
|
| Progressive path-traced preview | `viewport/mod.rs` | **missing** (we have Realistic/Ray view modes) |
|
|
| Drag-number / value field; colour picker | `ui/dragnum.rs`, `ui/colorpick.rs` | **partial** — we have numeric TextInputs; no drag+fine control |
|
|
| X-ray toggle; 6 shading modes | `api.rs`, `viewport/dsl.rs` | **partial** — we have 6 modes via display_mode; no x-ray |
|
|
| `●`/`○` outliner (done), gets search + type filter | `ui/outliner.rs` | **partial** — done base; no search/filter/funnel |
|
|
| Predefined camera views (front/right/top/iso) | `nav/`, `api.rs`, `keymap.rs` | **partial** — we have Alt+1-8 presets already |
|
|
| Frustum culling + BVH (element-level) | `model/bvh.rs` | **done** — we already ported BVH + frustum culling |
|
|
| Instancing/batching by shared shape | — | **done** — we have ShapeHash instancing |
|
|
| Measure distance/angle/area | `tools/measure.rs` | **done** — ported |
|
|
| Per-part visibility honored in render/pick/snap | `viewport/elements.rs` | **done** (new in this session) |
|
|
| Properties panel readout | `ui/properties.rs` | **partial** — we have X/Y/Z inputs + kind; no IFC-ish grouped props |
|
|
|
|
---
|
|
|
|
## Phase A — Command palette + keyboard map (highest value, lowest risk)
|
|
|
|
**Why first:** delivers broad discoverability and requires no new GPU/scene work;
|
|
reuses our existing workspace action handlers and already-mapped hotkeys.
|
|
|
|
1. **Pure command table** `command_palette.rs`: `Vec<PaletteItem{ id, label, shorcut, run }>`.
|
|
Commands = existing actions we already support: frame all (fit), frame selected,
|
|
preset views (F5/Alt+1-8), ortho toggle, shading modes, isolate, hide/show all,
|
|
toggle outliner, undo/redo, open/save, exit. Each `run` dispatches to the same
|
|
`CadWorkspace` handlers our toolbar buttons already call.
|
|
2. **Fuzzy subsequence matcher** (pure fn, unit-tested) — port fab's scoring
|
|
(subsequence + prefix/word-start bonus) exactly.
|
|
3. **Palette overlay** in the mobile DSL (a `View` list + filter `TextInput`,
|
|
arrow-keys + Enter), toggled by the existing keymap or a toolbar button.
|
|
4. **Keymap table** `keymap.rs` — single source of truth for our hotkeys; render an
|
|
**F1 help** panel from it (like fab). Unit test that every key maps to a real action.
|
|
|
|
**Acceptance:** palette filters and runs ≥6 commands with tests; F1 help renders from
|
|
the table; `parameter.palette` tests green; full lib suite green.
|
|
|
|
---
|
|
|
|
## Phase B — Isolate/solo/hide/unhide parity (small, our mechanism)
|
|
|
|
**Adapt:** fab uses an *isolation set* / solo mode; we use `__hidden__` name prefix
|
|
(made real this session). Extend, do not rewrite.
|
|
|
|
1. `CadViewport::solo_selected` — isolate to the selection; toggle off on repeat
|
|
(`isolate_selected` already does exactly this — expose as hotkey + outliner button).
|
|
2. `CadViewport::unhide_all` — alias for existing `show_all`; bind **Alt+H**.
|
|
3. Bind **H** = isolated-selected (currently `I`), keep `I` too. Unit test
|
|
`isolate_selected` round-trips (hide then restore) — add a test now that the
|
|
visibility mechanism is honored.
|
|
|
|
**Acceptance:** hotkeys + 2 unit tests (isolate round-trip, solo toggle); lib green.
|
|
|
|
---
|
|
|
|
## Phase C — Element info card + reveal in outliner
|
|
|
|
**Adapt:** fab's `I` tool card shows type/storey/layer/GUID/size/tri-count/quantities.
|
|
We have no storey/layer UI per part but have `CadNode` fields (name, kind, pos, size,
|
|
color, layer) + mesh tri-count via `scene_cache`.
|
|
|
|
1. `properties.rs` or new `info_card.rs`: pure `info_card_text(&CadNode, tri_count)`
|
|
returning the multi-line card (kind, id, name, pos, size, layer, tris). Unit-tested.
|
|
2. Draw the card as a small label overlay near the hovered part in `viewport_render.rs`
|
|
(2D + 3D), or reuse the status bar when parked. Follow fab's "click focuses and
|
|
reveals in outliner" by opening the outliner and selecting the part.
|
|
|
|
**Acceptance:** `info_card_text` tests; overlay/status wiring compiles; lib green.
|
|
|
|
---
|
|
|
|
## Phase D — Section planes (largest rendering gap)
|
|
|
|
**Scope honestly:** fab's section = GPU half-space discard + caps in `dsl.rs`. We use
|
|
a different renderer (`DrawCadMesh` shader, display_mode uniform). A faithful port is
|
|
large: add half-space uniforms to the shader + caps pass + drag handle + panel.
|
|
|
|
**Adapted approach (bounded):**
|
|
1. **CPU clip** in `viewport_render.rs`: when a section plane is active, keep only
|
|
parts whose AABB is entirely inside the kept half-spaces; draw a plane outline +
|
|
normal arrow overlay (reuse our existing overlay drawing). This gives the *editor
|
|
UX* (see the cut live, drag to move) without touching the shader.
|
|
2. `section.rs` (pure): `SectionPlane{ normal, offset }`, `kept(aabb) -> bool`,
|
|
`plane_through(p0, normal)`, offset/with_offset helpers — port from fab, unit-test.
|
|
3. Panel: `SetSection` buttons (axis, flip, clear) in the outliner/properties panel.
|
|
4. **Shader caps (stretch, gate):** add a CLIP uniform + cap fill only if CPU clip is
|
|
judged insufficient after a measurement of real scenes. Keep out of the first cut.
|
|
|
|
**Acceptance:** `section.rs` unit tests; CPU-clip + overlay compiles and draws; no
|
|
regression in lib suite. **Phase marked done even without GPU caps**, which are an
|
|
explicitly-gated stretch (named as external-effort, consistent with the completion
|
|
standard).
|
|
|
|
---
|
|
|
|
## Phase E — Explode view
|
|
|
|
**Adapt:** our parts have no "storey" grouping by default; support **by-element**
|
|
radial explode first, include **by-storey** only if a grouping exists (outliner could
|
|
group by `layer`).
|
|
|
|
1. `explode.rs` (pure): `ExplodeMode{ ByElement }`, `ExplodeState{ amount }`,
|
|
`element_offset(id_idx, centre, amount)` — port fab's radial rule, unit-test.
|
|
2. Apply offsets in `part_model_matrix_cadnode`/the draw when explode active
|
|
(transform-time, so pick/snap reuse the same offset — no LUT needed).
|
|
3. `ExplodeState` stored on `CadViewport`; slider in the outliner panel actions.
|
|
|
|
**Acceptance:** `explode.rs` tests (element 0 offset = 0; radial sign/direction);
|
|
transform application compiles; lib green.
|
|
|
|
---
|
|
|
|
## Phase F — Sun study
|
|
|
|
**Adapt:** pure NOAA solar model (azimuth/elevation from lat/lon/date/time) + a day
|
|
scrub. Our CAD has a real `u_light_dir` uniform (per `DrawCadMesh`), so the sun can
|
|
drive the existing key light + a cast-shadow plane fill.
|
|
|
|
1. `sun.rs` (pure): `SunSettings{ latitude, longitude, date, hour }`, NOAAlike
|
|
`solar_position() -> (azimuth_deg, elevation_deg)`, `compass_point()`,
|
|
`direction() -> Vec3f` — port from `api::SkyState` and `sun_study.rs`, unit-test
|
|
against known noon values.
|
|
2. Toolbar button opens a small sun panel (date/hour/latitude, play scrub) reusing
|
|
the drag-number/TextInput style; set `u_light_dir` from `direction()` in
|
|
`viewport_render.rs`.
|
|
3. Overlay sun-compass (arc + disc + readout) drawn in the viewport — port the
|
|
math, keep our draw style.
|
|
|
|
**Acceptance:** `sun.rs` tests (elevation sign at noon, compass names); light-dir
|
|
wiring compiles; overlay compiles; lib green.
|
|
|
|
---
|
|
|
|
## Phase G — F12 high-res render + Save PNG
|
|
|
|
**Adapt:** fab uses a progressive path-traced preview + `FabRenderView`. Our CAD has
|
|
a **Ray** shading mode via `display_mode` but no standalone capture. Minimal:
|
|
1. `RenderSettings{ width, height, samples }` state on `CadWorkspace`.
|
|
2. "Render" action captures the current scene at render resolution using our
|
|
existing DrawCadMesh into an offscreen target, accumulates, and **writes a PNG**
|
|
(we already export PNG from the arch_pdf path, so the encoder exists — reuse it).
|
|
3. Command-palette entry `render-image` (F12).
|
|
|
|
**Acceptance:** a `render settings` pure struct + tests; the PNG write path is wired
|
|
through an existing tested encoder; no new dependency; lib green.
|
|
|
|
---
|
|
|
|
## Phase H — X-ray + shading parity + value-field polish (fill-in gaps)
|
|
|
|
1. **X-ray:** add an `xray` overlay uniform to `DrawCadMesh` (or reuse display_mode
|
|
degree), toggled by `Alt+Z` + a toolbar button; only affects the shader, tested by
|
|
`parameter` snapshot if present.
|
|
2. **Drag-number:** port fab's pure `header_drag_math` (anchor/step/fine/ctrl) as a
|
|
Rust fn with tests, and wrap our existing numeric `TextInput`s where ergonomic
|
|
(properties panel X/Y/Z/W/H/D). Keep current inputs working.
|
|
3. **Outliner search + type filter:** add a `TextInput` filter in the outliner panel;
|
|
pure filter fn `filter_rows(rows, query) -> Vec<..>` unit-tested; funnel dropdown
|
|
filters by `PartKind`.
|
|
|
|
**Acceptance:** per-item tests; no regression; lib green.
|
|
|
|
---
|
|
|
|
## Explicitly NOT porting (with reason)
|
|
|
|
- **fab's `api.rs` shell/`ShellAction` dictionary** — our app has a different action
|
|
model and mobile-first layout; adopting it would be a rewrite.
|
|
- **`ui/shell.rs` dock / `area.rs` swappable editors / `menubar.rs`** — desktop-chrome
|
|
that our 430x860 mobile UI does not host; our toolbar + bottom sheet already cover it.
|
|
- **`render/mod.rs` camera-track to mp4** — needs movie encoding we don't ship.
|
|
- **`file_browser.rs` / in-app open dialog** — platform has no file picker; gated on
|
|
a platform capability, not effort (matches the completion-standard exception).
|
|
- **`ui/colorpick.rs` full hue-ring picker** — nice-to-have; we have a 9-swatch palette;
|
|
deferred unless requested.
|
|
- **`nav/gizmo.rs` axis-ball gizmo** — we have a nav pad + preset views; low ROI.
|
|
- **`ui/dragnum.rs` drag-number value field** — parity with fab: we have numeric
|
|
`TextInput`s in the properties panel; full drag+fine-control (anchor/step/ctrl)
|
|
is a UX polish, not an inspection capability. ([cross-ref Phase H.2](deferred).)
|
|
- **`render/mod.rs` progressive path-traced preview (live noise-accumulating view)**
|
|
— we ship Quality/Realistic/Ray shading modes already; porting fab's live
|
|
progressive preview to our GPU path is large and gated. See Phase G for the
|
|
bounded capture/export path we *do* ship.
|
|
|
|
---
|
|
|
|
## Recommended order & effort
|
|
|
|
| Phase | Effort | Risk | Do first? |
|
|
|---|---|---|---|
|
|
| A Command palette + keymap | S | low | ✅ yes |
|
|
| B Isolate/solo/outliner parity | XS | low | ✅ yes |
|
|
| C Info card + reveal | S | low | ✅ yes |
|
|
| D Section planes (CPU clip) | M | med | next |
|
|
| E Explode | S | low | next |
|
|
| F Sun study | M | med | later |
|
|
| G F12 render + PNG | M | med | later |
|
|
| H X-ray/dragnum/outliner search | M | med | last |
|
|
|
|
S = small, M = medium. Each phase ends with unit tests + green `--lib` suite, and the
|
|
GPU-heavy items (D caps, F shadows) are gated as explicit named work rather than
|
|
silently dropped.
|