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

12 KiB

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 ShellActions + 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 TextInputs 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 TextInputs in the properties panel; full drag+fine-control (anchor/step/ctrl) is a UX polish, not an inspection capability. (cross-ref Phase H.2.)
  • 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.

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.