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
21/// One DIALOG row's hover state — which panel lit it, the row's own TEXT, and
22/// what that text resolved to in the scene.
23///
24/// The row text is the memo key rather than the candidate's name because the two
25/// differ whenever the derived-face fallback fires (a fillet's `Edges` row reads
26/// `A|B[0]` while the face it lights is `F1:BLEND:A|B[0]` — see
27/// [`EngineState::hover_entity_by_name`]). An unresolvable row memoizes as
28/// `candidate: None`, so a held hover over a name the scene does not carry scans
29/// the scene ONCE, not once per frame.
30#[derive(Clone)]
31pub(crate) struct DialogHover {
32    /// Which panel set it (`"history"`, `"constraints"`, `"pmi"`, `"refsel"`) —
33    /// [`EngineState::dialog_hover_end`] acts only for the owner that set it, so a
34    /// pane that never hovered a row cannot end another pane's hover.
35    pub(crate) owner: &'static str,
36    /// The row's TEXT, exactly as the dialog listed it.
37    pub(crate) row: String,
38    /// What it resolved to, or `None` for a row the scene does not carry.
39    pub(crate) candidate: Option<pick::PickCandidate>,
40}
41
42pub struct EngineState {
43    pub scene: RenderScene,
44    pub camera: ViewCamera,
45    pub controls: ArcballControls,
46    pub settings: RenderSettings,
47    pub emphasis: Emphasis,
48    /// In-scene overlay widgets: datums/dimensions/curves, transform
49    /// gizmo, ViewCube — fed as JSON, drawn by the render core's overlay pass.
50    pub widgets: WidgetRegistry,
51    /// The engine-owned editable model recipe (ordered features + rollback
52    /// index) — the SINGLE source of truth for the model. The UI never keeps its
53    /// own copy; it mutates/reads this through the `history_*` / feature methods.
54    pub history: History,
55    /// The build report (`{featureErrors, unresolved, displayErrors}`) of the
56    /// last history run, so the UI can show it without re-running.
57    history_report: String,
58    /// The Properties-panel metadata store: user attributes keyed by OBJECT NAME
59    /// (solid / face / edge kernel name), NOT feature id — so a record survives
60    /// feature edits as long as the object's name persists. Persisted with the
61    /// model (a top-level `metadata` field in the history document); see the
62    /// [`crate::metadata`] module for the store + the object-info/measurement API.
63    pub metadata: crate::metadata::MetadataStore,
64    /// Bumped whenever settings change so the renderer re-derives per-solid
65    /// base styles (a cheap key, not a per-frame diff).
66    pub settings_generation: u64,
67    /// The engine sets this whenever the camera/scene/emphasis changed; the
68    /// presentation shell renders only when it is set (R22 on-demand render —
69    /// the OrthoCameraIdle matrix-compare analogue, made explicit).
70    pub dirty: bool,
71    /// The modal reference-selection state (the ref-select widget). `Some` while
72    /// the user is picking references for a feature-dialog field; `None`
73    /// otherwise. The picked-name list here is the SINGLE source of truth while
74    /// active (the UI reads it back; the viewport appends to it on a pick).
75    pub ref_select: Option<RefSelectState>,
76    /// Which entity KINDS a plain viewport click may select (the selection
77    /// filter, mirroring the earlier `SelectionFilter.allowedSelectionTypes`). `select_top_at`
78    /// consults it via `pick_filtered`; see the appended `SelectionFilter` impl
79    /// block near the end of this file for the state + honoring logic.
80    pub selection_filter: SelectionFilter,
81    /// The transform-controls gizmo controller: which feature (if any) has the
82    /// move/rotate gizmo armed (via the in-viewport center-sphere toggle), plus the
83    /// in-flight handle drag. All the arm/drag/apply logic lives in the appended
84    /// transform-gizmo impl block near the end of this file.
85    pub transform_gizmo: TransformArm,
86    /// The COMPONENT Move gizmo controller (assemblies §8.5): which ACOMP
87    /// instance has the bbox-center move/rotate gizmo armed, the translate/
88    /// rotate cycle mode, and the commit-on-release drag. Exclusive with
89    /// `transform_gizmo` (shared widget slot). See `component_move.rs`.
90    pub component_move: ComponentMoveArm,
91    /// The active engine-native sketch edit (`Some` while in sketch mode). Holds
92    /// the live [`crate::sketch::SketchSession`] plus the pre-entry camera + roll
93    /// to restore on exit. All the enter/exit/new logic lives in the appended
94    /// sketch-mode impl block at the END of this file.
95    sketch_edit: Option<SketchEdit>,
96    /// Sketch-mode camera lock. When true (the default on every sketch entry), the
97    /// camera is held flat-on to the sketch plane and an empty drag only PANS —
98    /// no orbit. Toggling it back on re-faces the camera to the plane. Meaningful
99    /// only while `sketch_edit` is `Some`. See the sketch-mode impl block.
100    sketch_camera_locked: bool,
101    /// Set for ONE frame when the sketch entity-LIST panel hovers a row (it calls
102    /// [`sketch_hover_entity`](Self::sketch_hover_entity)). The viewport, which
103    /// draws AFTER the panel and would otherwise `sketch_clear_hover` because the
104    /// pointer is off the viewport, consumes this flag via
105    /// [`take_sketch_list_hover`](Self::take_sketch_list_hover) and keeps the
106    /// panel-set hover so list→canvas highlight survives the frame.
107    sketch_list_hover_active: bool,
108    /// Transient user-facing notices (e.g. a sketch solve that failed after an
109    /// edit). The shell drains them each frame via [`take_notices`](Self::take_notices)
110    /// into a toast overlay; the engine only queues. Replaces the swallowed
111    /// `eprintln!` on the interactive re-solve paths.
112    notices: Vec<String>,
113    /// Committed-sketch visibility: the feature ids whose persistent committed-sketch
114    /// overlay is HIDDEN (its Scene-tree checkbox off). Absent = visible. See the
115    /// committed-sketch impl block appended at the END of this file.
116    hidden_sketches: std::collections::HashSet<String>,
117    /// The committed-sketch feature ids whose overlay groups were fed on the LAST
118    /// [`refresh_committed_sketches`](Self::refresh_committed_sketches), so an id that
119    /// is no longer shown (rolled back, deleted, hidden, or became the active edit)
120    /// can have its now-stale groups cleared.
121    shown_sketch_ids: Vec<String>,
122    /// The named plane FRAMES the last history run resolved `(frame name, frame)`.
123    /// Stored so [`refresh_construction_datums`](Self::refresh_construction_datums)
124    /// (and datum selection) can display/pick the construction datum/plane frames
125    /// without re-running. Filled from the run's
126    /// [`crate::pipeline::SceneBuildReport::frames`]; the D/P filter is applied at
127    /// display time (a frame name maps to its producing feature TYPE via the
128    /// history). See the appended construction-datum impl block at the END.
129    construction_frames: Vec<(String, brep_kernel::Frame)>,
130    /// The solved sketch PROFILES the last history run produced `(sketch id,
131    /// profile)`. Stored so [`refresh_committed_sketches`](Self::refresh_committed_sketches)
132    /// can synthesize each committed sketch's SHEET SOLID (planar face + named
133    /// boundary edges + corner vertices) without re-running. Filled from the run's
134    /// [`crate::pipeline::SceneBuildReport::profiles`]; rollback / active-edit /
135    /// visibility gating is applied at display time.
136    sketch_profiles: Vec<(String, brep_kernel::SketchProfile)>,
137    /// The named PATH chains the last history run produced `(path name, curves)`.
138    /// Stored alongside [`sketch_profiles`](Self::sketch_profiles) so
139    /// [`refresh_committed_sketches`](Self::refresh_committed_sketches) can draw the
140    /// sketch geometry no closed profile covers: a sketch's OPEN chain publishes no
141    /// profile, so the sheet builder had nothing to draw it from and an open sketch
142    /// was invisible in 3D. Filled from the run's
143    /// [`crate::pipeline::SceneBuildReport::paths`]; the per-segment `{id}:G{gid}`
144    /// entries are the display input (the whole-chain `{id}` entry duplicates them
145    /// and `{id}:REF:{source}` is projected reference geometry).
146    sketch_paths: Vec<(String, Vec<brep_kernel::NurbsCurve>)>,
147    /// The named world POINTS the last history run produced `(point name, point)`.
148    /// Stored alongside [`sketch_paths`](Self::sketch_paths) so
149    /// [`refresh_committed_sketches`](Self::refresh_committed_sketches) can draw the
150    /// sketch points no segment covers: a points-only sketch (a hole-placement
151    /// sketch) publishes no profile and no path, so it had nothing to draw from
152    /// and was invisible in 3D. Filled from the run's
153    /// [`crate::pipeline::SceneBuildReport::points`]; the per-point `{id}:P{pid}`
154    /// entries are the display input (construction points are skipped at draw
155    /// time, like construction geometry).
156    sketch_points: Vec<(String, brep_kernel::ScenePoint)>,
157    /// The named axis LINES the last history run produced `(axis name, line)`.
158    /// Stored so the feature-dimension angle gizmo can resolve a revolve `axis`
159    /// reference to a world line without re-running. Filled from the run's
160    /// [`crate::pipeline::SceneBuildReport::axes`].
161    sketch_axes: Vec<(String, brep_kernel::Axis)>,
162    /// The wire-harness routing report of the last APPLIED run (`None` before
163    /// the first run). Filled from [`crate::pipeline::SceneBuildReport::wire_harness`];
164    /// the harness panel reads it, the committed-curve display reads the port
165    /// kinds off it. See the appended wire-harness impl block.
166    wire_harness_report: Option<brep_kernel::WireHarnessReport>,
167    /// The PMI tail's report of the last APPLIED run (`pmi_ops.rs`).
168    pub(crate) pmi_report: Option<brep_kernel::PmiReport>,
169    /// The ACTIVE PMI view (engine memory, never persisted): its camera,
170    /// display state and explode poses are applied; its annotations drawn.
171    pub(crate) pmi_active_view: Option<String>,
172    /// The annotation whose form the PMI panel shows (engine memory).
173    pub(crate) pmi_open_annotation: Option<String>,
174    /// The modeling camera / visibility / wireframe remembered on entering
175    /// the PMI workbench, restored when a view deactivates or the workbench
176    /// is left.
177    pub(crate) pmi_modeling: Option<pmi_ops::PmiModelingSnapshot>,
178    /// The un-exploded displays of the solids the active view's explode
179    /// annotations posed, for an exact restore.
180    pub(crate) pmi_explode_originals: std::collections::HashMap<String, crate::scene::SolidDisplay>,
181    /// `(world_per_pixel, view direction)` the PMI overlay was last baked at.
182    pub(crate) pmi_overlay_key: Option<(f64, [f64; 3])>,
183    pub(crate) pmi_hovered: Option<String>,
184    pub(crate) pmi_label_hover_active: bool,
185    /// What the spline-anchor cage overlay was last fed for — `(spline id,
186    /// selected anchor, applied run generation)` — so the per-frame refresh
187    /// the history panel makes while an SP form is open is a no-op until one
188    /// of those moves. See `spline_edit.rs`.
189    spline_overlay_key: Option<(String, Option<usize>, u64)>,
190    /// Whether the ports placed components carry (`ACOMP1:PORT1`) draw their
191    /// sheets. The app sets it from the active workbench — one that shows the
192    /// Wire Harness panel; a document's own PORT features always draw.
193    component_ports_visible: bool,
194    /// The spline whose anchor editor is open (fed by the history panel with
195    /// the cage overlay), so a viewport click on one of its anchor dots selects
196    /// that anchor; the index of the last such pick, until the panel takes it.
197    spline_edit_feature: Option<String>,
198    spline_anchor_picked: Option<usize>,
199    /// Construction datum/plane visibility: the frame NAMES whose datum plane is
200    /// HIDDEN (its Scene-tree checkbox off). Absent = visible. Mirrors
201    /// [`hidden_sketches`].
202    hidden_datums: std::collections::HashSet<String>,
203    /// The datum frame NAMES fed to the widget on the LAST
204    /// [`refresh_construction_datums`](Self::refresh_construction_datums). The datum
205    /// feed REPLACES its set wholesale each call, so this is a bookkeeping mirror of
206    /// what is currently shown (parallels [`shown_sketch_ids`]).
207    shown_datum_names: Vec<String>,
208    /// The history-run seam (M2a of the off-thread runner). Owns the scene-free
209    /// runner + its delta baseline (`name → last-emitted handle`) ACROSS reruns:
210    /// [`rerun_history`](Self::rerun_history) SUBMITS a run tagged with
211    /// [`run_generation`](Self::run_generation), and [`pump`](Self::pump) drains
212    /// the completed reply and [`apply_run_output`](Self::apply_run_output)s its
213    /// delta to [`Self::scene`]. The [`InlineRunner`](crate::runner::InlineRunner)
214    /// default runs synchronously (submit → immediate poll, byte-identical to the
215    /// old in-place reconcile); a background thread/worker impl slots in behind the
216    /// same trait in M2b/M3. Reset on a document switch
217    /// ([`set_history_json`](Self::set_history_json)) so a new model rebuilds fully.
218    pub(crate) runner: Box<dyn crate::runner::HistoryRunner>,
219    /// Monotonic run counter: bumped each time [`rerun_history`](Self::rerun_history)
220    /// SUBMITS a run, and stamped onto the reply so a stale reply (a newer run that
221    /// finished first) can be dropped. `run_generation != applied_generation` means
222    /// a run is in flight (always equal for the synchronous Inline runner).
223    run_generation: u64,
224    /// The generation of the last reply [`pump`](Self::pump) APPLIED — the high-water
225    /// mark that gates stale replies.
226    applied_generation: u64,
227    /// The latest [`crate::runner::RunProgress`] of the run in flight (a
228    /// background runner posts one before each feature it executes), or
229    /// `None` when nothing is running. Names what the spinner is waiting on.
230    run_progress: Option<crate::runner::RunProgress>,
231    /// The feature the last CANCELLED run was executing (`Some("")` when the
232    /// run was cancelled before any progress arrived), cleared by the next
233    /// submit. The history header shows it until the model is rebuilt.
234    cancelled_run: Option<String>,
235    /// A pending one-shot "frame the scene once the in-flight run lands" request.
236    /// Import / Open SUBMIT an async run (native [`ThreadRunner`], wasm worker) and
237    /// want to `zoom_to_fit` the RESULT — but the scene is still empty when they
238    /// return, so an immediate fit frames nothing (bbox empty → no-op). Instead they
239    /// set this flag and [`pump`](Self::pump) performs the fit on the first apply that
240    /// leaves the run no longer pending. Under the synchronous Inline runner the run
241    /// applies inside the submitting call's own `pump`, so the fit is still immediate.
242    pending_fit: bool,
243    /// EAGER provenance `name → creating-feature id` for the current resident
244    /// solids, shipped with each run ([`crate::pipeline::RunOutput::provenance`]) and
245    /// replaced wholesale in [`apply_run_output`](Self::apply_run_output). Answers
246    /// `creating_feature` + the Info tab's `creatingFeature` WITHOUT a cold
247    /// `execute_history` — the freeze side-door once the run lives off-thread.
248    pub(crate) provenance: std::collections::HashMap<String, String>,
249    /// EAGER ENTITY ORIGIN `face/edge NAME → ORIGINATING feature id` (FIRST writer in
250    /// timeline order), shipped with each run
251    /// ([`crate::pipeline::RunOutput::entity_origin`]) and replaced wholesale in
252    /// [`apply_run_output`](Self::apply_run_output). Unlike `provenance` (the SOLID's
253    /// LAST writer) this is the feature that gave the face/edge its NAME — the answer
254    /// `creating_feature` returns for a face/edge (the "Edit owning feature" action +
255    /// the Info tab's `creatingFeature`), so it rolls to the entity's origin, not the
256    /// owning solid's last producer.
257    pub(crate) entity_origin: std::collections::HashMap<String, String>,
258    /// Object-info MEASUREMENT cache `name → merged object-info JSON`, filled by
259    /// [`pump_queries`](crate::metadata) from the runner's replies and served every
260    /// frame a selection persists (so `object_info_json` fires ONE query per
261    /// selection, not one per frame). Invalidated on any geometry change
262    /// (`apply_run_output`) or metadata edit (`set_metadata_attribute`).
263    pub(crate) info_cache: std::collections::HashMap<String, String>,
264    /// In-flight measurement queries `id → object NAME` — the key needed to MERGE a
265    /// reply back into the info cache (inject `name` + `creatingFeature`, the latter
266    /// now resolved by the entity name itself). Cleared alongside `info_cache` on a
267    /// geometry change so a stale reply is dropped rather than caching a superseded
268    /// measurement.
269    pub(crate) pending_query: std::collections::HashMap<u64, String>,
270    /// Monotonic measurement-query id (pairs a [`crate::runner::MeasureReply`] back
271    /// with its `pending_query` entry).
272    pub(crate) next_query_id: u64,
273    /// Off-thread mesh reconstructions awaiting a runner reply.
274    pub(crate) pending_mesh_imports: std::collections::HashMap<u64, MeshImportDestination>,
275    pub(crate) mesh_preview_results: std::collections::VecDeque<crate::runner::MeshImportReply>,
276    /// Monotonic id for pairing mesh import replies with submissions.
277    pub(crate) next_mesh_import_id: u64,
278    /// The scene's assembly COMPONENT records (deterministic id order), a
279    /// PROJECTION captured by the main-side assembly sync (`assembly_ops`) —
280    /// the Assembly Structure tree's source. Empty for componentless documents.
281    pub(crate) assembly_components: Vec<brep_kernel::ComponentRecord>,
282    /// The [`Self::applied_generation`] the last assembly sync ran against
283    /// (`None` = never synced). `ensure_assembly_synced` re-syncs when a newer
284    /// display run has been applied. See `assembly_ops`.
285    pub(crate) assembly_synced_generation: Option<u64>,
286    /// Re-entrancy guard for the parts-library resync in [`Self::pump`] (a
287    /// refused run re-runs, and `rerun_history` pumps).
288    pub(crate) library_resync: bool,
289    /// The kernel parts-library revision the document's `partsLibrary` block
290    /// was last refreshed from (`sync_assembly`). Serializing the store is
291    /// proportional to the embedded part payload, so it is re-read only when
292    /// the library actually changed, not once per edit.
293    pub(crate) parts_library_block_revision: Option<u64>,
294    /// The assembly-constraint viewport overlays (build-spec §8.4) — the cached
295    /// [`crate::constraint_overlays::ConstraintOverlay`] records the engine last
296    /// built from the kernel session (`assembly_overlay_json` + state), the source
297    /// of the drawn leader/arrow group, the label feed, and the grabbable-handle
298    /// hit regions. Refreshed after every history apply and every constraint
299    /// mutation; see the appended assembly-overlay impl block.
300    pub(crate) constraint_overlays: Vec<crate::constraint_overlays::ConstraintOverlay>,
301    /// A live GRAB on a constraint's distance-arrow / angle-arc handle (`Some`
302    /// between `constraint_drag_begin` and `constraint_drag_release`): the drag
303    /// previews the value locally and COMMITS on release via
304    /// `assembly_update_constraint_json` (which auto-solves).
305    pub(crate) constraint_drag: Option<ConstraintDrag>,
306    /// The `world_per_pixel` the constraint overlay buffers were last baked at
307    /// (screen-constant arc/rod sizing): `ensure_constraint_overlay_current`
308    /// re-bakes when the camera zoom moves it materially. `0.0` = never baked.
309    constraint_overlay_wpp: f64,
310    /// The `world_per_pixel` the FEATURE-DIMENSION gizmo group
311    /// (`feature-dim-leaders`) was last baked at. The leaders' rod/cone/origin
312    /// sphere — and the angular arc's whole world RADIUS — are
313    /// `pixels × world_per_pixel`, so a zoom makes the drawn gizmo stale;
314    /// `ensure_feature_dimension_overlay_current` re-bakes on a material move.
315    /// `0.0` = nothing baked (gizmo disarmed / not in dimension mode).
316    /// See `feature_dims`.
317    pub(crate) feature_dim_overlay_wpp: f64,
318    /// The `world_per_pixel` the live SKETCH overlay groups (geometry, points,
319    /// preview, `sketch-dim-leaders`, `sketch-constraint-glyphs`) were last baked
320    /// at — construction dashes, dimension arrowheads and constraint glyphs are
321    /// all screen-constant. `ensure_sketch_overlay_current` re-bakes on a material
322    /// zoom. `0.0` = nothing baked (not in sketch mode). See `sketch_input`.
323    pub(crate) sketch_overlay_wpp: f64,
324    /// The constraint id whose referenced ELEMENTS the panel/label hover is
325    /// currently highlighting (dedupe key so a held hover doesn't re-bump the
326    /// emphasis generation every frame).
327    constraint_hovered: Option<String>,
328    /// Set for ONE frame when a constraint LABEL hover applied the element
329    /// highlight; the viewport's modeling hover branch consumes it (mirrors
330    /// [`Self::take_sketch_list_hover`]) so the scene hover doesn't clobber it.
331    constraint_label_hover_active: bool,
332    /// The SCENE-TREE row the pointer is over, as the pick candidate its hover
333    /// lit (the row→viewport highlight). Kept so
334    /// [`scene_tree_hover_end`](Self::scene_tree_hover_end) clears ONLY a hover
335    /// the tree itself set — never one the viewport lit in the meantime — and as
336    /// the dedupe key that keeps a held row hover from re-bumping the emphasis
337    /// generation every frame. See `scene_query.rs`.
338    scene_tree_hovered: Option<pick::PickCandidate>,
339    /// Set for ONE frame while a Scene-tree row is hovered; the viewport's
340    /// modeling hover branch consumes it via
341    /// [`take_scene_tree_hover`](Self::take_scene_tree_hover) and skips the
342    /// pointer-off-viewport `clear_hover` (mirrors
343    /// [`Self::take_sketch_list_hover`]) so the row-set highlight survives.
344    scene_tree_hover_active: bool,
345    /// The DIALOG row the pointer is over — a feature form's reference line, its
346    /// read-only `Outputs` line, or the reference picker card's picked-name line
347    /// (the row→viewport highlight, the twin of the Scene tree's above). Its OWN
348    /// slot, not the tree's: History, Constraints, PMI and the Scene tree can all
349    /// be on screen at once in a split dock, and a shared slot would let the pane
350    /// that draws second end the hover the pane that drew first had just set. See
351    /// [`DialogHover`] for what it memoizes and `scene_query.rs` for the API.
352    dialog_hovered: Option<DialogHover>,
353    /// Set for ONE frame while a dialog row is hovered; the viewport's modeling
354    /// hover branch consumes it via [`take_dialog_hover`](Self::take_dialog_hover)
355    /// and skips the pointer-off-viewport `clear_hover` (mirrors
356    /// [`Self::take_scene_tree_hover`]) so the row-set highlight survives.
357    dialog_hover_active: bool,
358    /// The constraint SELECTED via its viewport label chip (`Some` = selected):
359    /// drives the chip's selected accent, the context bar's Delete-constraint
360    /// action, and clears with the selection (Esc / Clear / delete). Read
361    /// through [`Self::selected_constraint`], which prunes a stale id.
362    selected_constraint: Option<String>,
363    /// The STEP product structure a [`Self::probe_step_assembly`] parsed, held
364    /// until the import dialog's button decides its fate: consumed by
365    /// [`Self::import_probed_step_assembly`], dropped by
366    /// [`Self::discard_probed_step_assembly`] (Cancel), by the next probe, or by
367    /// a document switch. The ONE parse of a structured STEP import lives here —
368    /// the dialog needs the counts BEFORE the user chooses, and re-parsing
369    /// multi-MB Part-21 text on the way back would double the most expensive step
370    /// of the import. See `model_io`'s import block.
371    pub(crate) pending_step_assembly: Option<brep_kernel::StepAssembly>,
372    /// STEP probes submitted to the runner and not yet answered (their ids).
373    pub(crate) pending_step_probes: std::collections::HashSet<u64>,
374    /// Answered probes awaiting [`Self::take_step_probe`], oldest first.
375    pub(crate) step_probe_results: std::collections::VecDeque<(u64, StepProbeOutcome)>,
376    /// Monotonic id pairing a probe reply with its submission.
377    pub(crate) next_step_probe_id: u64,
378}
379
380/// What a STEP probe found, as the file panel consumes it (see
381/// [`EngineState::submit_step_probe`]).
382#[derive(Debug, Clone, PartialEq)]
383pub enum StepProbeOutcome {
384    /// Product structure: the parsed assembly is stashed for
385    /// [`EngineState::import_probed_step_assembly`]; these are its counts.
386    Structure(model_io::StepAssemblyProbe),
387    /// No structure worth keeping — take the flat lane.
388    Flat,
389    /// The text did not parse as Part 21; the flat lane refuses it with its
390    /// own wording.
391    Failed(String),
392}
393
394/// A live constraint-handle drag: which constraint + which `inputParams` field
395/// (`distance` / `angle`), the params snapshot the commit mutates, and the live
396/// preview value the overlay/label show while dragging.
397#[derive(Debug, Clone)]
398pub struct ConstraintDrag {
399    pub id: String,
400    pub field: &'static str,
401    pub params: serde_json::Value,
402    pub preview: f64,
403}
404
405/// WHO a finished reference-selection commits to: a history FEATURE's params
406/// (the original widget) or an ASSEMBLY CONSTRAINT's `inputParams` (same modal,
407/// different commit lane — see `assembly_ops::begin_ref_select_for_constraint`).
408#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
409pub enum RefSelectTarget {
410    #[default]
411    Feature,
412    AssemblyConstraint,
413    /// ONE SPLINE ANCHOR's port attachment: `path` is unused, the picked name
414    /// must be a PORT feature id (a click on a port's drawn sheet), and Finish
415    /// writes `persistentData.spline.points[index].attachment` (see
416    /// `spline_edit.rs`).
417    SplineAnchor { index: usize },
418    /// A PMI annotation's reference field: `feature_id` is the annotation id;
419    /// Finish writes the picked names into its params through the PMI block
420    /// (`pmi_ops.rs`). Vertex picks are `{solid}@x,y,z` in WORLD coordinates.
421    Pmi,
422}
423
424/// The modal state of the reference-selection widget while it is ACTIVE: which
425/// feature-dialog field is being filled, what it accepts, and the running list
426/// of picked kernel names (the source of truth — added by picking in the view,
427/// removed via the per-line X). See the ref-select methods below.
428#[derive(Debug, Clone, Default)]
429pub struct RefSelectState {
430    /// The id of the feature whose param is being edited.
431    pub feature_id: String,
432    /// The JSON path into that feature's `inputParams` the names write to
433    /// (`["targetSolid"]`, `["boolean","targets"]`, `["faceRef"]`, …).
434    pub path: Vec<String>,
435    /// A human label for the modal heading (the field's label).
436    pub label: String,
437    /// The allowed pick kinds (`["SOLID"]`, `["FACE"]`, …) — the type constraint.
438    pub filter: Vec<String>,
439    /// Whether the field takes a LIST of references (else a single one).
440    pub multiple: bool,
441    /// The picked kernel names — the running selection (source of truth).
442    pub names: Vec<String>,
443    /// The rollback step to restore on Finish/Cancel (the edited feature's own
444    /// step): entering the mode rolls to the pre-feature "before" state, so we
445    /// remember where to return.
446    pub restore_index: usize,
447    /// Which surface Finish commits the names to (feature params vs an
448    /// assembly constraint's `inputParams`). Defaults to [`RefSelectTarget::Feature`].
449    pub target: RefSelectTarget,
450}
451
452impl Default for EngineState {
453    fn default() -> Self {
454        Self {
455            scene: RenderScene::new(),
456            camera: ViewCamera::default(),
457            controls: ArcballControls::new(),
458            settings: RenderSettings::default(),
459            emphasis: Emphasis::default(),
460            widgets: WidgetRegistry::new(),
461            history: History::default(),
462            history_report: String::new(),
463            metadata: crate::metadata::MetadataStore::new(),
464            settings_generation: 1,
465            dirty: true,
466            ref_select: None,
467            selection_filter: SelectionFilter::default(),
468            transform_gizmo: TransformArm::default(),
469            component_move: ComponentMoveArm::default(),
470            sketch_edit: None,
471            sketch_camera_locked: true,
472            sketch_list_hover_active: false,
473            notices: Vec::new(),
474            hidden_sketches: std::collections::HashSet::new(),
475            shown_sketch_ids: Vec::new(),
476            construction_frames: Vec::new(),
477            sketch_profiles: Vec::new(),
478            sketch_paths: Vec::new(),
479            sketch_points: Vec::new(),
480            sketch_axes: Vec::new(),
481            wire_harness_report: None,
482            pmi_report: None,
483            pmi_active_view: None,
484            pmi_open_annotation: None,
485            pmi_modeling: None,
486            pmi_explode_originals: std::collections::HashMap::new(),
487            pmi_overlay_key: None,
488            pmi_hovered: None,
489            pmi_label_hover_active: false,
490            spline_overlay_key: None,
491            component_ports_visible: false,
492            spline_edit_feature: None,
493            spline_anchor_picked: None,
494            hidden_datums: std::collections::HashSet::new(),
495            shown_datum_names: Vec::new(),
496            runner: Box::new(crate::runner::InlineRunner::new()),
497            run_generation: 0,
498            applied_generation: 0,
499            run_progress: None,
500            cancelled_run: None,
501            pending_fit: false,
502            provenance: std::collections::HashMap::new(),
503            entity_origin: std::collections::HashMap::new(),
504            info_cache: std::collections::HashMap::new(),
505            pending_query: std::collections::HashMap::new(),
506            next_query_id: 0,
507            pending_mesh_imports: std::collections::HashMap::new(),
508            mesh_preview_results: std::collections::VecDeque::new(),
509            next_mesh_import_id: 0,
510            assembly_components: Vec::new(),
511            assembly_synced_generation: None,
512            library_resync: false,
513            parts_library_block_revision: None,
514            constraint_overlays: Vec::new(),
515            constraint_drag: None,
516            constraint_overlay_wpp: 0.0,
517            feature_dim_overlay_wpp: 0.0,
518            sketch_overlay_wpp: 0.0,
519            constraint_hovered: None,
520            constraint_label_hover_active: false,
521            scene_tree_hovered: None,
522            scene_tree_hover_active: false,
523            dialog_hovered: None,
524            dialog_hover_active: false,
525            selected_constraint: None,
526            pending_step_assembly: None,
527            pending_step_probes: std::collections::HashSet::new(),
528            step_probe_results: std::collections::VecDeque::new(),
529            next_step_probe_id: 1,
530        }
531    }
532}
533
534impl EngineState {
535    pub fn new() -> Self {
536        Self::default()
537    }
538
539    fn pick_options(&self) -> PickOptions {
540        PickOptions {
541            double_sided: self.settings.pick_double_sided,
542            ..PickOptions::default()
543        }
544    }
545}
546
547/// Has the camera's `world_per_pixel` moved MATERIALLY (>0.5%) away from the
548/// value an overlay group's screen-constant sizing was baked at?
549///
550/// The ONE judgement every camera-keyed overlay re-bake shares
551/// (`ensure_overlays_current` and the per-group ensures it drives), so
552/// "what counts as a zoom" cannot drift between them. `baked == 0.0` means
553/// "never baked" — a state each caller handles itself, so it reads as NOT
554/// stale here. The band is what keeps a quiet frame quiet: float jitter in the
555/// camera never trips it, so there is no per-frame re-bake loop.
556pub(crate) fn overlay_wpp_stale(baked: f64, now: f64) -> bool {
557    baked > 0.0 && (now - baked).abs() > baked * 0.005
558}
559
560// ============================================================================
561// MODULE MAP — engine_state is split into topic children. THIS file is the
562// module root: it keeps the EngineState/RefSelectState structs, Default, the
563// constructor + shared pick_options, the child `mod` declarations, and the
564// re-exports that preserve the original `engine_state::*` public paths.
565// Append new work to the matching child (or add a new child + re-export here).
566// ============================================================================
567
568/// The assembly surface (Wave-3): main-side session sync + component
569/// projection, constraint CRUD with the document fold (pose-authority
570/// contract), the insert-component flow, component fix/select actions, the
571/// constraint flavor of the reference picker, and `document_signature` — the
572/// ONE parts-library `sourceSignature` hash every writer shares.
573mod assembly_ops;
574/// Assembly-constraint viewport overlays (build-spec §8.4): overlay refresh from
575/// the kernel session, the grabbable distance/angle handle pick + drag preview +
576/// `assembly_update_constraint_json` commit, movedSolids re-tessellation, the
577/// drag-path document fold, label feed + element hover, and tests.
578mod assembly_overlay;
579/// BOM export (assemblies build-spec §9): the `{partName, sourceKey,
580/// quantity}` parts list off the main-side parts library + live component
581/// projection, serialized as CSV / JSON for the file dialog's Export modal.
582mod bom;
583/// Camera & view commands (zoom-to-fit, resize, pointer/wheel ingestion,
584/// projection, standard views, camera state/matrices, world→screen) plus the
585/// widget-overlay feeds (datums/overlay/dimensions/transform JSON), the
586/// ViewCube (incl. `apply_look_direction`), `datum_pick`, the widget
587/// transform-handle hover/pick/drag path, and `dimension_anchors_json`.
588mod camera_widgets;
589/// Committed-sketch persistent overlays: sheet-solid synthesis from stored
590/// profiles (`refresh_committed_sketches`), per-sketch visibility, the
591/// Scene-tree rows + `sketch_entities_json`, and the committed-sketch tests.
592mod committed_sketches;
593/// The COMPONENT Move gizmo (assemblies §8.5): translate→rotate→off cycle at
594/// the member-bbox center, fixed-refusal, free-move drag with the pose commit
595/// (and re-solve) on release. See `ComponentMoveArm`.
596mod component_move;
597/// Assembly COMPONENT read surface: the app-side component projection derived
598/// from the history's ACOMP features + the scene's namespaced solid names
599/// (`component_of_solid` / `component_info` / `component_bbox_center`), and its
600/// tests + shared assembly fixtures.
601mod components;
602/// Construction datum/plane display: frame→feature mapping,
603/// `refresh_construction_datums`, datum visibility + selection + entity rows,
604/// and the construction-datum tests.
605mod construction_datums;
606/// Expressions & configurator surface (`expressions_json`, `set_expressions`,
607/// `configurator_json`, `expression_variables_json`) + parsing helpers + tests.
608mod expressions;
609/// The feature-dimension gizmo (dimension arrows / angle arc / center-sphere
610/// toggle): arm state, `__brep`-style annotation JSON, overlay publishing,
611/// drag + set-value writeback, `FEATURE_DIM_OVERLAY`, fd_* math helpers, tests.
612mod feature_dims;
613/// The assembly interference check (build-spec §9): the bbox-prefiltered
614/// pairwise non-destructive INTERSECT sweep over component instances
615/// (`interference_check`), its report types, and the pure pair planner.
616mod interference;
617/// History runs & the feature CRUD surface: `run_history_json`, the
618/// runner/pump/apply seam, history JSON accessors, roll/update/add/delete/
619/// reorder, engine undo/redo, `load_model_and_fit`,
620/// and the history-cache rollback tests.
621mod history_ops;
622/// Model I/O: `import_step_feature`, `export_step_text`, `export_stl_text`,
623/// `triangle_normal`, and the io tests.
624mod model_io;
625/// Construction PLANES as ordinary pick candidates: the combined
626/// scene+plane-card candidate list (`pick_candidates_at`), the planes-aware
627/// single-hit pick (`pick_top_at`), the shared candidate ordering, and the
628/// plane-pick tests. Read its header for how a plane competes for a pick.
629mod plane_pick;
630/// Scene & object queries: `pick_json`/`hover_json`, settings + emphasis +
631/// visibility + color overrides, `scene_listing_json`, `depth_range_bbox`,
632/// `scene_entities_json`/select-by-name, mass properties + object info, tests.
633mod scene_query;
634/// The selection-filter state + kind gating (`SelectionFilter`,
635/// `select_filtered_at`, hide-selected) and its tests.
636mod selection_filter;
637/// Selection & hover UX: clear/select-top/selection JSON, the modal
638/// ref-select widget (incl. `set_json_at`), hover/candidate cycling, and the
639/// live sketch overlay feed (`set_sketch_overlay`/`clear_sketch_overlay`) +
640/// selection UX tests.
641mod selection_ux;
642/// Sketch editing ops appended after dimensions: dimension labels/value/drag,
643/// sketch undo/redo + diagnostics dump, trim, external edge refs
644/// (pick/link/reproject), and hand-draw strokes.
645mod sketch_edit_ops;
646/// Sketch-mode viewport input: overlay refresh, uv picking, hover/click/drag,
647/// the draw tools + pending-geometry helpers, and delete-selection.
648mod sketch_input;
649/// Sketch mode session: `SketchDrag`/`SketchEdit` state, plane-frame helpers,
650/// enter/exit/new sketch, and the camera lock.
651mod sketch_mode;
652/// Sketch entity-list panel rows + notices + solver settings
653/// (`SketchEntityRow`), and the constraint palette/actions
654/// (`SketchConstraintAction`, add-constraint builders, ground/construction
655/// toggles, cleanup).
656mod sketch_panel;
657/// The transform-controls gizmo: `GizmoMode`/`TransformArm`, arm/drag/apply,
658/// pose ⇄ params JSON, quaternion helpers (`rotate_euler_xyz_f64`), tests.
659mod transform_gizmo;
660/// The wire-harness surface: the document's `wireHarness` block (connection
661/// add / edit / remove / bundles toggle — checkpointed document edits that
662/// re-run the history, whose tail routes them), the applied run's routing
663/// report, the endpoint list, and the panel's hover highlight.
664mod wire_harness_ops;
665mod pmi_ops;
666pub use pmi_ops::{world_vertex_ref, PmiModelingSnapshot, PmiViewPatch};
667mod pmi_overlay;
668/// The 3D spline-anchor editor: the resolved anchor list, the value edits
669/// (position / distances / flip / side / add / remove / reorder / detach),
670/// the anchor flavour of the transform gizmo, the port-attach flavour of the
671/// reference picker, and the direction-cage overlay.
672mod spline_edit;
673
674// Re-exports preserving the original `engine_state::*` public surface.
675pub use assembly_ops::{document_signature, ComponentInsert};
676pub use bom::PART_ATTRIBUTES;
677pub use component_move::ComponentMoveArm;
678pub use components::ComponentInfo;
679pub use interference::{InterferencePair, InterferenceReport};
680pub use model_io::{
681    EmbeddedOnly, PartSink, StepAssemblyImport, StepAssemblyProbe, StepAssemblyReport,
682};
683// BREP private tests: 46ca25670c1b5601
684pub use selection_filter::SelectionFilter;
685pub use sketch_mode::{SketchDrag, SketchEdit};
686pub use sketch_panel::{SketchConstraintAction, SketchEntityRow};
687pub use wire_harness_ops::ConnectionPatch;
688pub use spline_edit::SplineAnchorRow;
689pub use transform_gizmo::{GizmoMode, TransformArm};
690pub(crate) use transform_gizmo::rotate_euler_xyz_f64;
691
692// BREP private tests: 49a4d8d471e9cf5a
693
694// BREP private tests: 34eb9d031fb01de9
695
696/// Reconstruction results either append to a document or remain uncommitted.
697#[derive(Clone, Copy, PartialEq, Eq)]
698pub(crate) enum MeshImportDestination {
699    Document,
700    Preview,
701}