nigig-org/REVIEWS/FAB_CAD_GAP_IMPLEMENTATION_PLAN.md
andodeki 1d8b3a6053
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
merge(local): reapply local WIP onto merged main -- cad dashboard/explode/script_parts/xray merged with remote Phase-5 LOD, plus doc-ui extraction, spreadsheet xls-import, android ussd, camera and test work
2026-09-01 21:13:52 +03:00

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.