Skip to main content

brep_render/
engine_state.rs

1//! [`EngineState`] — the windowing-agnostic viewer state machine the host UI
2//! programs against (R3): scene + camera + controls + settings + emphasis, plus
3//! the whole event/command/query surface (run-history feed, pointer/wheel
4//! ingestion, camera commands, picking, world→screen, visibility). No GPU, no
5//! canvas — the wasm `Engine` and the winit desktop shell both wrap this; it is
6//! fully unit-testable on native.
7//!
8//! Everything crosses the R3 boundary as plain JSON/scalars: the host never holds a
9//! renderer object, only names, ids, and JSON.
10
11use crate::controls::ArcballControls;
12use crate::history::History;
13use crate::pick::{self, PickOptions};
14use crate::scene::RenderScene;
15use crate::style::{Emphasis, RenderSettings};
16use crate::view::ViewCamera;
17use crate::widgets::{gizmo_camera, WidgetOverlay, WidgetRegistry};
18use brep_kernel::HistoryRequest;
19use std::collections::HashMap;
20
21pub struct EngineState {
22    pub scene: RenderScene,
23    pub camera: ViewCamera,
24    pub controls: ArcballControls,
25    pub settings: RenderSettings,
26    pub emphasis: Emphasis,
27    /// In-scene overlay widgets: datums/dimensions/curves, transform
28    /// gizmo, ViewCube — fed as JSON, drawn by the render core's overlay pass.
29    pub widgets: WidgetRegistry,
30    /// The engine-owned editable model recipe (ordered features + rollback
31    /// index) — the SINGLE source of truth for the model. The UI never keeps its
32    /// own copy; it mutates/reads this through the `history_*` / feature methods.
33    pub history: History,
34    /// The build report (`{featureErrors, unresolved, displayErrors}`) of the
35    /// last history run, so the UI can show it without re-running.
36    history_report: String,
37    /// The Properties-panel metadata store: user attributes keyed by OBJECT NAME
38    /// (solid / face / edge kernel name), NOT feature id — so a record survives
39    /// feature edits as long as the object's name persists. Persisted with the
40    /// model (a top-level `metadata` field in the history document); see the
41    /// [`crate::metadata`] module for the store + the object-info/measurement API.
42    pub metadata: crate::metadata::MetadataStore,
43    /// Bumped whenever settings change so the renderer re-derives per-solid
44    /// base styles (a cheap key, not a per-frame diff).
45    pub settings_generation: u64,
46    /// The engine sets this whenever the camera/scene/emphasis changed; the
47    /// presentation shell renders only when it is set (R22 on-demand render —
48    /// the OrthoCameraIdle matrix-compare analogue, made explicit).
49    pub dirty: bool,
50    /// The modal reference-selection state (the ref-select widget). `Some` while
51    /// the user is picking references for a feature-dialog field; `None`
52    /// otherwise. The picked-name list here is the SINGLE source of truth while
53    /// active (the UI reads it back; the viewport appends to it on a pick).
54    pub ref_select: Option<RefSelectState>,
55    /// Which entity KINDS a plain viewport click may select (the selection
56    /// filter, mirroring the earlier `SelectionFilter.allowedSelectionTypes`). `select_top_at`
57    /// consults it via `pick_filtered`; see the appended `SelectionFilter` impl
58    /// block near the end of this file for the state + honoring logic.
59    pub selection_filter: SelectionFilter,
60    /// The transform-controls gizmo controller: which feature (if any) has the
61    /// move/rotate gizmo armed (via the in-viewport center-sphere toggle), plus the
62    /// in-flight handle drag. All the arm/drag/apply logic lives in the appended
63    /// transform-gizmo impl block near the end of this file.
64    pub transform_gizmo: TransformArm,
65    /// The active engine-native sketch edit (`Some` while in sketch mode). Holds
66    /// the live [`crate::sketch::SketchSession`] plus the pre-entry camera + roll
67    /// to restore on exit. All the enter/exit/new logic lives in the appended
68    /// sketch-mode impl block at the END of this file.
69    sketch_edit: Option<SketchEdit>,
70    /// Sketch-mode camera lock. When true (the default on every sketch entry), the
71    /// camera is held flat-on to the sketch plane and an empty drag only PANS —
72    /// no orbit. Toggling it back on re-faces the camera to the plane. Meaningful
73    /// only while `sketch_edit` is `Some`. See the sketch-mode impl block.
74    sketch_camera_locked: bool,
75    /// Set for ONE frame when the sketch entity-LIST panel hovers a row (it calls
76    /// [`sketch_hover_entity`](Self::sketch_hover_entity)). The viewport, which
77    /// draws AFTER the panel and would otherwise `sketch_clear_hover` because the
78    /// pointer is off the viewport, consumes this flag via
79    /// [`take_sketch_list_hover`](Self::take_sketch_list_hover) and keeps the
80    /// panel-set hover so list→canvas highlight survives the frame.
81    sketch_list_hover_active: bool,
82    /// Transient user-facing notices (e.g. a sketch solve that failed after an
83    /// edit). The shell drains them each frame via [`take_notices`](Self::take_notices)
84    /// into a toast overlay; the engine only queues. Replaces the swallowed
85    /// `eprintln!` on the interactive re-solve paths.
86    notices: Vec<String>,
87    /// Committed-sketch visibility: the feature ids whose persistent committed-sketch
88    /// overlay is HIDDEN (its Scene-tree checkbox off). Absent = visible. See the
89    /// committed-sketch impl block appended at the END of this file.
90    hidden_sketches: std::collections::HashSet<String>,
91    /// The committed-sketch feature ids whose overlay groups were fed on the LAST
92    /// [`refresh_committed_sketches`](Self::refresh_committed_sketches), so an id that
93    /// is no longer shown (rolled back, deleted, hidden, or became the active edit)
94    /// can have its now-stale groups cleared.
95    shown_sketch_ids: Vec<String>,
96    /// The named plane FRAMES the last history run resolved `(frame name, frame)`.
97    /// Stored so [`refresh_construction_datums`](Self::refresh_construction_datums)
98    /// (and datum selection) can display/pick the construction datum/plane frames
99    /// without re-running. Filled from the run's
100    /// [`crate::pipeline::SceneBuildReport::frames`]; the D/P filter is applied at
101    /// display time (a frame name maps to its producing feature TYPE via the
102    /// history). See the appended construction-datum impl block at the END.
103    construction_frames: Vec<(String, brep_kernel::Frame)>,
104    /// The solved sketch PROFILES the last history run produced `(sketch id,
105    /// profile)`. Stored so [`refresh_committed_sketches`](Self::refresh_committed_sketches)
106    /// can synthesize each committed sketch's SHEET SOLID (planar face + named
107    /// boundary edges + corner vertices) without re-running. Filled from the run's
108    /// [`crate::pipeline::SceneBuildReport::profiles`]; rollback / active-edit /
109    /// visibility gating is applied at display time.
110    sketch_profiles: Vec<(String, brep_kernel::SketchProfile)>,
111    /// The named axis LINES the last history run produced `(axis name, line)`.
112    /// Stored so the feature-dimension angle gizmo can resolve a revolve `axis`
113    /// reference to a world line without re-running. Filled from the run's
114    /// [`crate::pipeline::SceneBuildReport::axes`].
115    sketch_axes: Vec<(String, brep_kernel::Axis)>,
116    /// Construction datum/plane visibility: the frame NAMES whose datum plane is
117    /// HIDDEN (its Scene-tree checkbox off). Absent = visible. Mirrors
118    /// [`hidden_sketches`].
119    hidden_datums: std::collections::HashSet<String>,
120    /// The datum frame NAMES fed to the widget on the LAST
121    /// [`refresh_construction_datums`](Self::refresh_construction_datums). The datum
122    /// feed REPLACES its set wholesale each call, so this is a bookkeeping mirror of
123    /// what is currently shown (parallels [`shown_sketch_ids`]).
124    shown_datum_names: Vec<String>,
125    /// The history-run seam (M2a of the off-thread runner). Owns the scene-free
126    /// runner + its delta baseline (`name → last-emitted handle`) ACROSS reruns:
127    /// [`rerun_history`](Self::rerun_history) SUBMITS a run tagged with
128    /// [`run_generation`](Self::run_generation), and [`pump`](Self::pump) drains
129    /// the completed reply and [`apply_run_output`](Self::apply_run_output)s its
130    /// delta to [`Self::scene`]. The [`InlineRunner`](crate::runner::InlineRunner)
131    /// default runs synchronously (submit → immediate poll, byte-identical to the
132    /// old in-place reconcile); a background thread/worker impl slots in behind the
133    /// same trait in M2b/M3. Reset on a document switch
134    /// ([`set_history_json`](Self::set_history_json)) so a new model rebuilds fully.
135    pub(crate) runner: Box<dyn crate::runner::HistoryRunner>,
136    /// Monotonic run counter: bumped each time [`rerun_history`](Self::rerun_history)
137    /// SUBMITS a run, and stamped onto the reply so a stale reply (a newer run that
138    /// finished first) can be dropped. `run_generation != applied_generation` means
139    /// a run is in flight (always equal for the synchronous Inline runner).
140    run_generation: u64,
141    /// The generation of the last reply [`pump`](Self::pump) APPLIED — the high-water
142    /// mark that gates stale replies.
143    applied_generation: u64,
144    /// EAGER provenance `name → creating-feature id` for the current resident
145    /// solids, shipped with each run ([`crate::pipeline::RunOutput::provenance`]) and
146    /// replaced wholesale in [`apply_run_output`](Self::apply_run_output). Answers
147    /// `creating_feature` + the Info tab's `creatingFeature` WITHOUT a cold
148    /// `execute_history` — the freeze side-door once the run lives off-thread.
149    pub(crate) provenance: std::collections::HashMap<String, String>,
150    /// Object-info MEASUREMENT cache `name → merged object-info JSON`, filled by
151    /// [`pump_queries`](crate::metadata) from the runner's replies and served every
152    /// frame a selection persists (so `object_info_json` fires ONE query per
153    /// selection, not one per frame). Invalidated on any geometry change
154    /// (`apply_run_output`) or metadata edit (`set_metadata_attribute`).
155    pub(crate) info_cache: std::collections::HashMap<String, String>,
156    /// In-flight measurement queries `id → (object name, owning solid name)` — the
157    /// data needed to MERGE a reply back into the info cache (inject `name` +
158    /// `creatingFeature`). Cleared alongside `info_cache` on a geometry change so a
159    /// stale reply is dropped rather than caching a superseded measurement.
160    pub(crate) pending_query: std::collections::HashMap<u64, (String, String)>,
161    /// Monotonic measurement-query id (pairs a [`crate::runner::MeasureReply`] back
162    /// with its `pending_query` entry).
163    pub(crate) next_query_id: u64,
164}
165
166/// The modal state of the reference-selection widget while it is ACTIVE: which
167/// feature-dialog field is being filled, what it accepts, and the running list
168/// of picked kernel names (the source of truth — added by picking in the view,
169/// removed via the per-line X). See the ref-select methods below.
170#[derive(Debug, Clone, Default)]
171pub struct RefSelectState {
172    /// The id of the feature whose param is being edited.
173    pub feature_id: String,
174    /// The JSON path into that feature's `inputParams` the names write to
175    /// (`["targetSolid"]`, `["boolean","targets"]`, `["faceRef"]`, …).
176    pub path: Vec<String>,
177    /// A human label for the modal heading (the field's label).
178    pub label: String,
179    /// The allowed pick kinds (`["SOLID"]`, `["FACE"]`, …) — the type constraint.
180    pub filter: Vec<String>,
181    /// Whether the field takes a LIST of references (else a single one).
182    pub multiple: bool,
183    /// The picked kernel names — the running selection (source of truth).
184    pub names: Vec<String>,
185    /// The rollback step to restore on Finish/Cancel (the edited feature's own
186    /// step): entering the mode rolls to the pre-feature "before" state, so we
187    /// remember where to return.
188    pub restore_index: usize,
189}
190
191impl Default for EngineState {
192    fn default() -> Self {
193        Self {
194            scene: RenderScene::new(),
195            camera: ViewCamera::default(),
196            controls: ArcballControls::new(),
197            settings: RenderSettings::default(),
198            emphasis: Emphasis::default(),
199            widgets: WidgetRegistry::new(),
200            history: History::default(),
201            history_report: String::new(),
202            metadata: crate::metadata::MetadataStore::new(),
203            settings_generation: 1,
204            dirty: true,
205            ref_select: None,
206            selection_filter: SelectionFilter::default(),
207            transform_gizmo: TransformArm::default(),
208            sketch_edit: None,
209            sketch_camera_locked: true,
210            sketch_list_hover_active: false,
211            notices: Vec::new(),
212            hidden_sketches: std::collections::HashSet::new(),
213            shown_sketch_ids: Vec::new(),
214            construction_frames: Vec::new(),
215            sketch_profiles: Vec::new(),
216            sketch_axes: Vec::new(),
217            hidden_datums: std::collections::HashSet::new(),
218            shown_datum_names: Vec::new(),
219            runner: Box::new(crate::runner::InlineRunner::new()),
220            run_generation: 0,
221            applied_generation: 0,
222            provenance: std::collections::HashMap::new(),
223            info_cache: std::collections::HashMap::new(),
224            pending_query: std::collections::HashMap::new(),
225            next_query_id: 0,
226        }
227    }
228}
229
230impl EngineState {
231    pub fn new() -> Self {
232        Self::default()
233    }
234
235    fn pick_options(&self) -> PickOptions {
236        PickOptions {
237            double_sided: self.settings.pick_double_sided,
238            ..PickOptions::default()
239        }
240    }
241}
242
243// ============================================================================
244// MODULE MAP — engine_state is split into topic children. THIS file is the
245// module root: it keeps the EngineState/RefSelectState structs, Default, the
246// constructor + shared pick_options, the child `mod` declarations, and the
247// re-exports that preserve the original `engine_state::*` public paths.
248// Append new work to the matching child (or add a new child + re-export here).
249// ============================================================================
250
251/// Camera & view commands (zoom-to-fit, resize, pointer/wheel ingestion,
252/// projection, standard views, camera state/matrices, world→screen) plus the
253/// widget-overlay feeds (datums/overlay/dimensions/transform JSON), the
254/// ViewCube (incl. `apply_look_direction`), `datum_pick`, the widget
255/// transform-handle hover/pick/drag path, and `dimension_anchors_json`.
256mod camera_widgets;
257/// Committed-sketch persistent overlays: sheet-solid synthesis from stored
258/// profiles (`refresh_committed_sketches`), per-sketch visibility, the
259/// Scene-tree rows + `sketch_entities_json`, and the committed-sketch tests.
260mod committed_sketches;
261/// Construction datum/plane display: frame→feature mapping,
262/// `refresh_construction_datums`, datum visibility + selection + entity rows,
263/// and the construction-datum tests.
264mod construction_datums;
265/// Expressions & configurator surface (`expressions_json`, `set_expressions`,
266/// `configurator_json`, `expression_variables_json`) + parsing helpers + tests.
267mod expressions;
268/// The feature-dimension gizmo (dimension arrows / angle arc / center-sphere
269/// toggle): arm state, `__brep`-style annotation JSON, overlay publishing,
270/// drag + set-value writeback, `FEATURE_DIM_OVERLAY`, fd_* math helpers, tests.
271mod feature_dims;
272/// History runs & the feature CRUD surface: `run_history_json`, the
273/// runner/pump/apply seam, history JSON accessors, roll/update/add/delete/
274/// reorder, engine undo/redo, `load_model_and_fit`, `parse_color_overrides`,
275/// and the history-cache rollback tests.
276mod history_ops;
277/// Model I/O: `import_step_feature`, `export_step_text`, `export_stl_text`,
278/// `triangle_normal`, and the io tests.
279mod model_io;
280/// Scene & object queries: `pick_json`/`hover_json`, settings + emphasis +
281/// visibility + color overrides, `scene_listing_json`, `depth_range_bbox`,
282/// `scene_entities_json`/select-by-name, mass properties + object info, tests.
283mod scene_query;
284/// The selection-filter state + kind gating (`SelectionFilter`,
285/// `select_filtered_at`, hide-selected) and its tests.
286mod selection_filter;
287/// Selection & hover UX: clear/select-top/selection JSON, the modal
288/// ref-select widget (incl. `set_json_at`), hover/candidate cycling, and the
289/// live sketch overlay feed (`set_sketch_overlay`/`clear_sketch_overlay`) +
290/// selection UX tests.
291mod selection_ux;
292/// Sketch editing ops appended after dimensions: dimension labels/value/drag,
293/// sketch undo/redo + diagnostics dump, trim, external edge refs
294/// (pick/link/reproject), and hand-draw strokes.
295mod sketch_edit_ops;
296/// Sketch-mode viewport input: overlay refresh, uv picking, hover/click/drag,
297/// the draw tools + pending-geometry helpers, and delete-selection.
298mod sketch_input;
299/// Sketch mode session: `SketchDrag`/`SketchEdit` state, plane-frame helpers,
300/// enter/exit/new sketch, and the camera lock.
301mod sketch_mode;
302/// Sketch entity-list panel rows + notices + solver settings
303/// (`SketchEntityRow`), and the constraint palette/actions
304/// (`SketchConstraintAction`, add-constraint builders, ground/construction
305/// toggles, cleanup).
306mod sketch_panel;
307/// The transform-controls gizmo: `GizmoMode`/`TransformArm`, arm/drag/apply,
308/// pose ⇄ params JSON, quaternion helpers (`rotate_euler_xyz_f64`), tests.
309mod transform_gizmo;
310
311// Re-exports preserving the original `engine_state::*` public surface.
312pub use selection_filter::SelectionFilter;
313pub use sketch_mode::{SketchDrag, SketchEdit};
314pub use sketch_panel::{SketchConstraintAction, SketchEntityRow};
315pub use transform_gizmo::{GizmoMode, TransformArm};
316pub(crate) use transform_gizmo::rotate_euler_xyz_f64;
317
318#[cfg(test)]
319mod tests;
320
321#[cfg(test)]
322mod sketch_mode_tests;