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    /// Display-only assembly surroundings in the active work part frame. Never picked or saved.
45    pub context_scene: RenderScene,
46    /// The Work part CONTEXT face clicked / hovered ([`Self::context_hit_at`]):
47    /// kept apart from [`Self::emphasis`], so no selection consumer (feature
48    /// prefill, references, plugins, metadata) ever sees a name the part's own
49    /// kernel cannot resolve.
50    pub(crate) context_selected: Option<crate::pick::PickCandidate>,
51    pub(crate) context_hovered: Option<crate::pick::PickCandidate>,
52    /// Ghosts (context solids) lit WHOLE by a running instructions occurrence
53    /// pick whose names they are (`sync_ref_select_emphasis`): every face of
54    /// each goes into the context highlight. Cleared when the pick ends.
55    pub(crate) context_pick_lit: Vec<String>,
56    /// How the context is shown: kept across context rebuilds and Work parts.
57    pub(crate) context_display: crate::engine_state::context_pick::ContextDisplay,
58    pub camera: ViewCamera,
59    pub controls: ArcballControls,
60    pub settings: RenderSettings,
61    pub emphasis: Emphasis,
62    /// In-scene overlay widgets: datums/dimensions/curves, transform
63    /// gizmo, ViewCube — fed as JSON, drawn by the render core's overlay pass.
64    pub widgets: WidgetRegistry,
65    /// The engine-owned editable model recipe (ordered features + rollback
66    /// index) — the SINGLE source of truth for the model. The UI never keeps its
67    /// own copy; it mutates/reads this through the `history_*` / feature methods.
68    pub history: History,
69    interference_sessions: std::collections::BTreeMap<String, interference::InterferenceCursor>,
70    interference_sequence: u64,
71    illustration_snapshot: Option<illustration::IllustrationSnapshot>,
72    geometry_diagnostics: std::collections::BTreeMap<String, serde_json::Value>,
73    /// A single feature staged by [`EngineState::stage_feature_begin`] whose
74    /// run has not landed yet (an agent's write; `batch.rs`).
75    staged_feature: Option<batch::StagedFeature>,
76    /// `History::busy_refused` already reported to the user.
77    busy_refused_seen: u64,
78    /// The build report (`{featureErrors, unresolved, displayErrors}`) of the
79    /// last history run, so the UI can show it without re-running.
80    history_report: String,
81    /// The Properties-panel metadata store: user attributes keyed by OBJECT NAME
82    /// (solid / face / edge kernel name), NOT feature id — so a record survives
83    /// feature edits as long as the object's name persists. Persisted with the
84    /// model (a top-level `metadata` field in the history document); see the
85    /// [`crate::metadata`] module for the store + the object-info/measurement API.
86    pub metadata: crate::metadata::MetadataStore,
87    /// Derived preview data stays outside the recipe and undo history: the
88    /// document's own picture at a history edit serial (opened with it, or the
89    /// last one a save rendered), and the last picture a save produced by key.
90    document_thumbnail: std::cell::RefCell<Option<(u64, serde_json::Value)>>,
91    thumbnail_cache: std::cell::RefCell<Option<(Vec<u64>, serde_json::Value)>>,
92    /// Bumped whenever settings change so the renderer re-derives per-solid
93    /// base styles (a cheap key, not a per-frame diff).
94    pub settings_generation: u64,
95    /// The engine sets this whenever the camera/scene/emphasis changed; the
96    /// presentation shell renders only when it is set (R22 on-demand render —
97    /// the OrthoCameraIdle matrix-compare analogue, made explicit).
98    pub dirty: bool,
99    /// The modal reference-selection state (the ref-select widget). `Some` while
100    /// the user is picking references for a feature-dialog field; `None`
101    /// otherwise. The picked-name list here is the SINGLE source of truth while
102    /// active (the UI reads it back; the viewport appends to it on a pick).
103    pub ref_select: Option<RefSelectState>,
104    /// A finished REPLACE REPAIR pick `(mate, old ref, picked name)` waiting
105    /// for the replace dialog, which owns the remap (nested components
106    /// included) — see [`RefSelectTarget::ReplaceRepair`].
107    pub(crate) replace_repair_pick: Option<(String, String, String)>,
108    /// Which entity KINDS a plain viewport click may select (the selection
109    /// filter, mirroring the earlier `SelectionFilter.allowedSelectionTypes`). `select_top_at`
110    /// consults it via `pick_filtered`; see the appended `SelectionFilter` impl
111    /// block near the end of this file for the state + honoring logic.
112    pub selection_filter: SelectionFilter,
113    /// The transform-controls gizmo controller: which feature (if any) has the
114    /// move/rotate gizmo armed (via the in-viewport center-sphere toggle), plus the
115    /// in-flight handle drag. All the arm/drag/apply logic lives in the appended
116    /// transform-gizmo impl block near the end of this file.
117    pub transform_gizmo: TransformArm,
118    /// The COMPONENT Move gizmo controller (assemblies §8.5): which ACOMP
119    /// instance has the bbox-center move/rotate gizmo armed, the translate/
120    /// rotate cycle mode, and the commit-on-release drag. Exclusive with
121    /// `transform_gizmo` (shared widget slot). See `component_move.rs`.
122    pub component_move: ComponentMoveArm,
123    /// The active engine-native sketch edit (`Some` while in sketch mode). Holds
124    /// the live [`crate::sketch::SketchSession`] plus the pre-entry camera + roll
125    /// to restore on exit. All the enter/exit/new logic lives in the appended
126    /// sketch-mode impl block at the END of this file.
127    sketch_edit: Option<SketchEdit>,
128    /// The active engine-native 3D SKETCH edit (`Some` while in 3D sketch
129    /// mode): the live [`crate::sketch3d::Sketch3dSession`] plus the pre-entry
130    /// camera + roll. See `sketch3d_mode.rs`.
131    sketch3d_edit: Option<Sketch3dEdit>,
132    /// The `world_per_pixel` the 3D sketch overlay was last baked at (its
133    /// working-plane and reference-plane squares are pixel-sized); `0.0` =
134    /// not in 3D sketch mode.
135    pub(crate) sketch3d_overlay_wpp: f64,
136    /// Sketch-mode camera lock. When true (the default on every sketch entry), the
137    /// camera is held flat-on to the sketch plane and an empty drag only PANS —
138    /// no orbit. Toggling it back on re-faces the camera to the plane. Meaningful
139    /// only while `sketch_edit` is `Some`. See the sketch-mode impl block.
140    sketch_camera_locked: bool,
141    /// Set for ONE frame when the sketch entity-LIST panel hovers a row (it calls
142    /// [`sketch_hover_entity`](Self::sketch_hover_entity)). The viewport, which
143    /// draws AFTER the panel and would otherwise `sketch_clear_hover` because the
144    /// pointer is off the viewport, consumes this flag via
145    /// [`take_sketch_list_hover`](Self::take_sketch_list_hover) and keeps the
146    /// panel-set hover so list→canvas highlight survives the frame.
147    sketch_list_hover_active: bool,
148    /// Transient user-facing notices (e.g. a sketch solve that failed after an
149    /// edit). The shell drains them each frame via [`take_notices`](Self::take_notices)
150    /// into a toast overlay; the engine only queues. Replaces the swallowed
151    /// `eprintln!` on the interactive re-solve paths.
152    notices: Vec<String>,
153    /// Notices that are NOT errors — a warning, or a success such as "Updated 2
154    /// part(s)" — with their severity, queued by
155    /// [`push_notice_as`](Self::push_notice_as). Beside [`Self::notices`] rather
156    /// than in it, so every existing `push_notice` stays an error unchanged.
157    graded_notices: Vec<(NoticeSeverity, String)>,
158    /// Committed-sketch visibility: the feature ids whose persistent committed-sketch
159    /// overlay is HIDDEN (its Scene-tree checkbox off). Absent = visible. See the
160    /// committed-sketch impl block appended at the END of this file.
161    hidden_sketches: std::collections::HashSet<String>,
162    /// The committed-sketch feature ids whose overlay groups were fed on the LAST
163    /// [`refresh_committed_sketches`](Self::refresh_committed_sketches), so an id that
164    /// is no longer shown (rolled back, deleted, hidden, or became the active edit)
165    /// can have its now-stale groups cleared.
166    shown_sketch_ids: Vec<String>,
167    /// The board bodies fed on the LAST
168    /// [`refresh_board_geometry`](Self::refresh_board_geometry), in stack order,
169    /// so a body this build did not produce can be swept out of the scene.
170    /// Parallels [`shown_sketch_ids`].
171    shown_board_ids: Vec<String>,
172    /// The eCAD board those bodies were built from — the rebuild test. A
173    /// SCHEMATIC edit rewrites the whole `pcb` block with the same board inside
174    /// it, and this is what makes that edit free.
175    board_built_from: Option<brep_ecad_core::board::Board>,
176    /// The pin-to-net map those bodies' NET COLOURS were built from. The board
177    /// alone does not decide them — a copper island carries the net of the pads
178    /// it touches, and which net a pad carries is the SCHEMATIC's answer — so
179    /// this is the second half of the rebuild test. Empty for a board with no
180    /// placements, which can carry no net at all.
181    board_pin_nets: std::collections::BTreeMap<(brep_ecad_core::Uuid, String), String>,
182    /// What that build cost, for the Scene tree and the tests.
183    board_build: board_geometry::BoardBuild,
184    /// The built bodies themselves, KEPT across runs. The scene reconcile drops
185    /// every derived body, so without this a dense board would be re-meshed on
186    /// every history run; a clone of these is what goes back in instead.
187    board_displays: Vec<crate::scene::SolidDisplay>,
188    /// The named plane FRAMES the last history run resolved `(frame name, frame)`.
189    /// Stored so [`refresh_construction_datums`](Self::refresh_construction_datums)
190    /// (and datum selection) can display/pick the construction datum/plane frames
191    /// without re-running. Filled from the run's
192    /// [`crate::pipeline::SceneBuildReport::frames`]; the D/P filter is applied at
193    /// display time (a frame name maps to its producing feature TYPE via the
194    /// history). See the appended construction-datum impl block at the END.
195    construction_frames: Vec<(String, brep_kernel::Frame)>,
196    /// The solved sketch PROFILES the last history run produced `(sketch id,
197    /// profile)`. Stored so [`refresh_committed_sketches`](Self::refresh_committed_sketches)
198    /// can synthesize each committed sketch's SHEET SOLID (planar face + named
199    /// boundary edges + corner vertices) without re-running. Filled from the run's
200    /// [`crate::pipeline::SceneBuildReport::profiles`]; rollback / active-edit /
201    /// visibility gating is applied at display time.
202    sketch_profiles: Vec<(String, brep_kernel::SketchProfile)>,
203    /// The named PATH chains the last history run produced `(path name, curves)`.
204    /// Stored alongside [`sketch_profiles`](Self::sketch_profiles) so
205    /// [`refresh_committed_sketches`](Self::refresh_committed_sketches) can draw the
206    /// sketch geometry no closed profile covers: a sketch's OPEN chain publishes no
207    /// profile, so the sheet builder had nothing to draw it from and an open sketch
208    /// was invisible in 3D. Filled from the run's
209    /// [`crate::pipeline::SceneBuildReport::paths`]; the per-segment `{id}:G{gid}`
210    /// entries are the display input (the whole-chain `{id}` entry duplicates them
211    /// and `{id}:REF:{source}` is projected reference geometry).
212    sketch_paths: Vec<(String, Vec<brep_kernel::NurbsCurve>)>,
213    /// The named world POINTS the last history run produced `(point name, point)`.
214    /// Stored alongside [`sketch_paths`](Self::sketch_paths) so
215    /// [`refresh_committed_sketches`](Self::refresh_committed_sketches) can draw the
216    /// sketch points no segment covers: a points-only sketch (a hole-placement
217    /// sketch) publishes no profile and no path, so it had nothing to draw from
218    /// and was invisible in 3D. Filled from the run's
219    /// [`crate::pipeline::SceneBuildReport::points`]; the per-point `{id}:P{pid}`
220    /// entries are the display input (construction points are skipped at draw
221    /// time, like construction geometry).
222    sketch_points: Vec<(String, brep_kernel::ScenePoint)>,
223    /// The named axis LINES the last history run produced `(axis name, line)`.
224    /// Stored so the feature-dimension angle gizmo can resolve a revolve `axis`
225    /// reference to a world line without re-running. Filled from the run's
226    /// [`crate::pipeline::SceneBuildReport::axes`].
227    sketch_axes: Vec<(String, brep_kernel::Axis)>,
228    /// The wire-harness routing report of the last APPLIED run (`None` before
229    /// the first run). Filled from [`crate::pipeline::SceneBuildReport::wire_harness`];
230    /// the harness panel reads it, the committed-curve display reads the port
231    /// kinds off it. See the appended wire-harness impl block.
232    wire_harness_report: Option<brep_kernel::WireHarnessReport>,
233    /// The PORTS tail's report of the last APPLIED run (`None` before the first
234    /// run, and for a document that declares no `ports` block). Filled from
235    /// [`crate::pipeline::SceneBuildReport::ports`]; the Qualify panel reads it,
236    /// and it is the only place the tail's refused names and unresolved
237    /// references surface, the tail not being a feature.
238    ports_report: Option<brep_kernel::PortsReport>,
239    /// The PMI tail's report of the last APPLIED run (`pmi_ops.rs`).
240    pub(crate) pmi_report: Option<brep_kernel::PmiReport>,
241    /// The ACTIVE PMI view (engine memory, never persisted): its camera,
242    /// display state and explode poses are applied; its annotations drawn.
243    pub(crate) pmi_active_view: Option<String>,
244    /// The annotation whose form the PMI panel shows (engine memory). Never
245    /// assign `Some` here: go through [`EngineState::open_pmi_annotation`], the
246    /// one place that counts the open.
247    pub(crate) pmi_open_annotation: Option<String>,
248    /// The VIEW whose form the PMI panel shows (engine memory) — exclusive
249    /// with [`Self::pmi_open_annotation`], since the pane shows one dialog.
250    /// Never assign `Some` here: go through [`EngineState::open_pmi_view`].
251    pub(crate) pmi_open_view: Option<String>,
252    /// The annotation SELECTED in the PMI tree or by its viewport label: the
253    /// tree row's highlight and the label's accent. Cleared with the selection
254    /// (Esc / Clear / a click on empty space); read through
255    /// [`EngineState::pmi_selected_annotation`], which prunes a stale id.
256    pub(crate) pmi_selected_annotation: Option<String>,
257    /// The VIEW selected in the PMI tree — its row's highlight. Selecting a
258    /// view never ACTIVATES it (a double click does); exclusive with
259    /// [`Self::pmi_selected_annotation`], since the tree selects one row.
260    /// Read through [`EngineState::pmi_selected_view`], which prunes a stale id.
261    pub(crate) pmi_selected_view: Option<String>,
262    /// How many times a PMI dialog — an annotation's or a view's — has been
263    /// OPENED: monotonic, never reset, closes do not count. The app's dialog
264    /// door keys on it beside the subject so a re-open of the dialog that is
265    /// ALREADY open is still one open event (a viewport label double click on the
266    /// annotation whose form is up, which a user makes precisely because they
267    /// are on another tab).
268    pub(crate) pmi_dialog_opens: u64,
269    /// The modeling camera / visibility / wireframe remembered on entering
270    /// the PMI workbench, restored when a view deactivates or the workbench
271    /// is left.
272    pub(crate) pmi_modeling: Option<pmi_ops::PmiModelingSnapshot>,
273    /// The un-exploded displays of the solids the active view's explode
274    /// annotations posed, for an exact restore.
275    pub(crate) pmi_explode_originals: std::collections::HashMap<String, crate::scene::SolidDisplay>,
276    /// How far the active view's explode steps are shown, and any Explode /
277    /// Collapse animation (`exploded_ops`). Display state, never saved.
278    pub(crate) explode_playback: exploded_ops::ExplodePlayback,
279    /// The motion study being played or scrubbed and its bake
280    /// (`simulation_play`). Display state, never saved.
281    pub(crate) simulation_play: Option<simulation_play::SimulationPlayback>,
282    /// Solids the active view's section removes whole: hidden (no copy, no
283    /// re-upload) rather than clipped, and shown again on restore.
284    pub(crate) pmi_section_hidden: Vec<String>,
285    /// Bumped when an un-posed original's `visible` changes (a user or view
286    /// hide while the active view poses or sections that solid): sheets
287    /// draw from the originals, so their cache key carries it.
288    pub(crate) pmi_unposed_visibility: u64,
289    /// The `pmi` block parsed, keyed by the history revision it was parsed
290    /// at ([`Self::pmi_state_shared`]): every write to the saved document,
291    /// undo and redo included, moves that revision.
292    pub(crate) pmi_state_cache: std::cell::RefCell<Option<(u64, std::rc::Rc<brep_kernel::PmiState>)>>,
293    /// A balloon whose dragged bubble could not be re-projected yet because
294    /// the scene did not hold its occurrence's exact solids: re-derived when
295    /// the topology reply lands (`pmi_refresh_stale_balloon`).
296    pub(crate) pmi_balloon_stale: Option<String>,
297    /// `(world_per_pixel, view direction)` the PMI overlay was last baked at.
298    pub(crate) pmi_overlay_key: Option<(f64, [f64; 3])>,
299    /// The center marks the last PMI overlay bake drew, for automation
300    /// ([`Self::pmi_center_marks_json`]): `(annotation id, per mark its two
301    /// cross arms as unit world directions)`, read in f64 before the
302    /// overlay's f32 buffers, with the bake's view direction.
303    pub(crate) pmi_center_marks_drawn: (Vec<(String, Vec<[[f64; 3]; 2]>)>, [f64; 3]),
304    pub(crate) pmi_hovered: Option<String>,
305    pub(crate) pmi_label_hover_active: bool,
306    /// The sheet the sheet viewport shows (engine memory, never persisted).
307    /// `None` = the 3D view (`sheet_ops.rs`).
308    pub(crate) sheet_open: Option<String>,
309    /// The sheet or placed view whose form the Sheets pane shows. Never assign
310    /// `Some` here: go through [`EngineState::open_sheet_object`], the one
311    /// place that counts the open.
312    pub(crate) sheet_open_object: Option<String>,
313    /// How many times a sheet object's form has been OPENED — the sheet half
314    /// of the same counter as [`Self::pmi_dialog_opens`], read by the
315    /// app's dialog door so a re-open of the object already open still counts.
316    pub(crate) sheet_object_opens: u64,
317    /// The placed view, sheet dimension or ordinate set SELECTED in the Sheets
318    /// tree or on the paper: the tree row's highlight and the object's accent
319    /// on the sheet. Cleared with the selection; read through
320    /// [`EngineState::sheet_selected_object`], which prunes a stale id.
321    pub(crate) sheet_selected_object: Option<String>,
322    /// The last projected sheet, kept until the model or the block moves: a
323    /// sheet is a hidden-line pass over the whole model and must never run
324    /// per frame.
325    pub(crate) sheet_cache: Option<sheet_ops::SheetCache>,
326    /// The sheet's exact passes on the runner and waiting for it.
327    pub(crate) sheet_lines: sheet_ops::SheetLinesWork,
328    /// The open document's name, as the shell means it (the tab, the file, the
329    /// root PRODUCT of a STEP export). Engine memory, never persisted: the
330    /// document does not carry its own name, the SHELL owns it, and it is here
331    /// because a drawing sheet's TITLE BLOCK prints it. Set every frame by the
332    /// app from the active tab (a no-op when unchanged).
333    pub(crate) document_name: String,
334    /// What the spline-anchor cage overlay was last fed for — `(spline id,
335    /// selected anchor, applied run generation)` — so the per-frame refresh
336    /// the history panel makes while an SP form is open is a no-op until one
337    /// of those moves. See `spline_edit.rs`.
338    spline_overlay_key: Option<(String, Option<usize>, u64)>,
339    /// The CAM workbench's generation queue and results (`cam_ops.rs`).
340    pub cam: cam_ops::CamGeneration,
341    /// Whether the ports placed components carry (`ACOMP1:PORT1`) draw their
342    /// sheets. The app sets it from the active workbench — one that shows the
343    /// Wire Harness panel; a document's own PORT features always draw.
344    component_ports_visible: bool,
345    /// The spline whose anchor editor is open (fed by the history panel with
346    /// the cage overlay), so a viewport click on one of its anchor dots selects
347    /// that anchor; the index of the last such pick, until the panel takes it.
348    spline_edit_feature: Option<String>,
349    spline_anchor_picked: Option<usize>,
350    /// The NURBS solid whose control-cage editor is open (fed by the history
351    /// panel with the cage overlay) and what that overlay was last fed for —
352    /// `(feature id, selected points, applied run generation)`. The selection
353    /// itself is the transform gizmo's (`TransformArm::cage`). See
354    /// `cage_edit.rs`.
355    cage_edit_feature: Option<String>,
356    cage_overlay_key: Option<(String, Vec<usize>, u64)>,
357    /// Construction datum/plane visibility: the frame NAMES whose datum plane is
358    /// HIDDEN (its Scene-tree checkbox off). Absent = visible. Mirrors
359    /// [`hidden_sketches`].
360    hidden_datums: std::collections::HashSet<String>,
361    /// The datum frame NAMES fed to the widget on the LAST
362    /// [`refresh_construction_datums`](Self::refresh_construction_datums). The datum
363    /// feed REPLACES its set wholesale each call, so this is a bookkeeping mirror of
364    /// what is currently shown (parallels [`shown_sketch_ids`]).
365    shown_datum_names: Vec<String>,
366    /// The history-run seam (M2a of the off-thread runner). Owns the scene-free
367    /// runner + its delta baseline (`name → last-emitted handle`) ACROSS reruns:
368    /// [`rerun_history`](Self::rerun_history) SUBMITS a run tagged with
369    /// [`run_generation`](Self::run_generation), and [`pump`](Self::pump) drains
370    /// the completed reply and [`apply_run_output`](Self::apply_run_output)s its
371    /// delta to [`Self::scene`]. The [`InlineRunner`](crate::runner::InlineRunner)
372    /// default runs synchronously (submit → immediate poll, byte-identical to the
373    /// old in-place reconcile); a background thread/worker impl slots in behind the
374    /// same trait in M2b/M3. Reset on a document switch
375    /// ([`set_history_json`](Self::set_history_json)) so a new model rebuilds fully.
376    pub(crate) runner: Box<dyn crate::runner::HistoryRunner>,
377    plugins: plugins::Plugins,
378    /// Monotonic run counter: bumped each time [`rerun_history`](Self::rerun_history)
379    /// SUBMITS a run, and stamped onto the reply so a stale reply (a newer run that
380    /// finished first) can be dropped. `run_generation != applied_generation` means
381    /// a run is in flight (always equal for the synchronous Inline runner).
382    run_generation: u64,
383    /// The generation of the last reply [`pump`](Self::pump) APPLIED — the high-water
384    /// mark that gates stale replies.
385    applied_generation: u64,
386    /// The latest [`crate::runner::RunProgress`] of the run in flight (a
387    /// background runner posts one before each feature it executes), or
388    /// `None` when nothing is running. Names what the spinner is waiting on.
389    run_progress: Option<crate::runner::RunProgress>,
390    /// The feature the last CANCELLED run was executing (`Some("")` when the
391    /// run was cancelled before any progress arrived), cleared by the next
392    /// submit. The history header shows it until the model is rebuilt.
393    cancelled_run: Option<String>,
394    /// A TRANSIENT expressions source the runs evaluate instead of the
395    /// document's own (the family table's row preview). Never written into
396    /// the history: not saved, not undone, not dirty. See
397    /// [`Self::set_expression_preview`].
398    expression_preview: Option<ExpressionPreview>,
399    /// How many run replies came back from the runner (applied or dropped as
400    /// stale). A run the runner coalesced away never replies, so this counts
401    /// the runs actually EXECUTED.
402    runs_replied: u64,
403    /// Displays shipped by replies [`pump`](Self::pump) DROPPED as superseded,
404    /// by solid name, latest wins. A background runner executes every run it
405    /// is handed and its delta baseline (`name → last-emitted handle`) advances
406    /// on each, so a later reply can say "unchanged, keep yours" about a
407    /// display that only ever travelled in a dropped reply (the reporter's
408    /// arrow-head drag: the frame that built the new radius was superseded by
409    /// the still-pointer frames that replayed its handle). The keep is served
410    /// from here when the scene's display is not the handle it names. Cleared
411    /// with the runner's baseline (document switch, cancel) and whenever a
412    /// fresh display for the name is applied.
413    superseded_displays: HashMap<String, crate::scene::SolidDisplay>,
414    /// A pending one-shot "frame the scene once the in-flight run lands" request.
415    /// Import / Open SUBMIT an async run (native [`ThreadRunner`], wasm worker) and
416    /// want to `zoom_to_fit` the RESULT — but the scene is still empty when they
417    /// return, so an immediate fit frames nothing (bbox empty → no-op). Instead they
418    /// set this flag and [`pump`](Self::pump) performs the fit on the first apply that
419    /// leaves the run no longer pending. Under the synchronous Inline runner the run
420    /// applies inside the submitting call's own `pump`, so the fit is still immediate.
421    pending_fit: bool,
422    /// The instructions wizard waits for the run that places the wrapper:
423    /// its step views read the solids that run makes. [`pump`](Self::pump)
424    /// runs it once no run is pending.
425    instructions_wizard_pending: bool,
426    /// A re-target folded new steps in: their views wait for the run that
427    /// builds the new revision's solids, like the wizard's.
428    instructions_views_pending: bool,
429    /// The in-app player's insertion motion: the shown step's arrowed parts
430    /// moving home ([`instructions_ops::Insertion`]).
431    pub(crate) instructions_insertion: Option<instructions_ops::Insertion>,
432    /// EAGER provenance `name → creating-feature id` for the current resident
433    /// solids, shipped with each run ([`crate::pipeline::RunOutput::provenance`]) and
434    /// replaced wholesale in [`apply_run_output`](Self::apply_run_output). Answers
435    /// `creating_feature` + the Info tab's `creatingFeature` WITHOUT a cold
436    /// `execute_history` — the freeze side-door once the run lives off-thread.
437    pub(crate) provenance: std::collections::HashMap<String, String>,
438    /// EAGER ENTITY ORIGIN `face/edge NAME → ORIGINATING feature id` (FIRST writer in
439    /// timeline order), shipped with each run
440    /// ([`crate::pipeline::RunOutput::entity_origin`]) and replaced wholesale in
441    /// [`apply_run_output`](Self::apply_run_output). Unlike `provenance` (the SOLID's
442    /// LAST writer) this is the feature that gave the face/edge its NAME — the answer
443    /// `creating_feature` returns for a face/edge (the "Edit owning feature" action +
444    /// the Info tab's `creatingFeature`), so it rolls to the entity's origin, not the
445    /// owning solid's last producer.
446    pub(crate) entity_origin: std::collections::HashMap<String, String>,
447    /// Object-info MEASUREMENT cache `name → merged object-info JSON`, filled by
448    /// [`pump_queries`](crate::metadata) from the runner's replies and served every
449    /// frame a selection persists (so `object_info_json` fires ONE query per
450    /// selection, not one per frame). Invalidated on any geometry change
451    /// (`apply_run_output`) or metadata edit (`set_metadata_attribute`).
452    pub(crate) info_cache: std::collections::HashMap<String, String>,
453    /// In-flight measurement queries `id → object NAME` — the key needed to MERGE a
454    /// reply back into the info cache (inject `name` + `creatingFeature`, the latter
455    /// now resolved by the entity name itself). Cleared alongside `info_cache` on a
456    /// geometry change so a stale reply is dropped rather than caching a superseded
457    /// measurement.
458    pub(crate) pending_query: std::collections::HashMap<u64, String>,
459    /// Monotonic measurement-query id (pairs a [`crate::runner::MeasureReply`] back
460    /// with its `pending_query` entry).
461    pub(crate) next_query_id: u64,
462    /// Off-thread mesh reconstructions awaiting a runner reply.
463    pub(crate) pending_mesh_imports: std::collections::HashMap<u64, MeshImportDestination>,
464    pub(crate) mesh_preview_results: std::collections::VecDeque<crate::runner::MeshImportReply>,
465    /// Phases of preview imports (interactive import), oldest first.
466    pub(crate) mesh_preview_phases: std::collections::VecDeque<crate::runner::MeshImportPhaseReply>,
467    /// Completed region refits, oldest first.
468    pub(crate) region_fit_results: std::collections::VecDeque<crate::runner::RegionFitReply>,
469    pub(crate) apply_choices_results: std::collections::VecDeque<crate::runner::ApplyRegionChoicesReply>,
470    /// Monotonic id for pairing mesh import replies with submissions.
471    pub(crate) next_mesh_import_id: u64,
472    /// Topology requests in flight (see [`crate::runner::TopologyRequest`]).
473    pub(crate) pending_topology: std::collections::HashSet<u64>,
474    /// Every resident handle asked for since the last applied run, answered or
475    /// not: a handle the runner could not clone is not asked for again until a
476    /// run brings new handles.
477    pub(crate) topology_asked: std::collections::HashSet<u32>,
478    /// Monotonic id for pairing topology replies with requests.
479    pub(crate) next_topology_id: u64,
480    /// The run's ASSEMBLY tail as the runner read it
481    /// ([`crate::pipeline::AssemblySync`]) — the constraint state, statuses, DOF
482    /// and overlay rows every assembly panel reads, adopted from the reply of the
483    /// last applied run. `None` for a componentless document. This is what
484    /// replaced the second, main-side `execute_history`: see `assembly_ops`.
485    pub(crate) assembly_sync: Option<crate::pipeline::AssemblySync>,
486    /// The scene's assembly COMPONENT records (deterministic id order), adopted
487    /// from [`Self::assembly_sync`] — the Assembly Structure tree's source.
488    /// Empty for componentless documents.
489    pub(crate) assembly_components: Vec<crate::pipeline::ComponentSnapshot>,
490    /// The [`Self::applied_generation`] the main-side kernel assembly SESSION was
491    /// last installed for (`None` = never). Only a constraint MUTATION needs one
492    /// — its solve runs against the resident geometry of the thread that asks —
493    /// so only `ensure_assembly_session` sets this. See `assembly_ops`.
494    pub(crate) assembly_session_generation: Option<u64>,
495    /// The kernel's session serial when this document's session was put in
496    /// place: the kernel session is one per thread, shared by every open
497    /// document, so a moved serial means another one replaced it.
498    pub(crate) assembly_session_serial: u64,
499    /// Take the runner's session copy instead of replaying (tests switch it
500    /// off to compare the two lanes), and how often each lane served.
501    pub(crate) session_transfer: bool,
502    pub(crate) session_imports: u64,
503    pub(crate) session_replays: u64,
504    /// A component replace whose run has not landed yet (see
505    /// `replace_component`), and the last finished replace's report.
506    pub(crate) replace_pending: Option<replace_component::PendingReplace>,
507    pub(crate) last_replace_report: Option<serde_json::Value>,
508    /// Every output name the last applied run CONSUMED
509    /// ([`crate::pipeline::RunOutput::consumed`]) — what the committed-sketch
510    /// refresh hides a consumed sketch by, and what it used to re-execute the
511    /// whole history on the UI thread to learn.
512    pub(crate) consumed_names: std::collections::HashSet<String>,
513    /// Re-entrancy guard for the parts-library resync in [`Self::pump`] (a
514    /// refused run re-runs, and `rerun_history` pumps).
515    pub(crate) library_resync: bool,
516    /// A run just RELEASED a sketch's edge links whose edges it could not
517    /// resolve (see [`Self::release_unresolved_sketch_links`]): the history
518    /// changed under the applied report, so [`Self::pump`] re-runs it once and
519    /// the report stops naming edges the sketch no longer lists.
520    pub(crate) sketch_links_released: bool,
521    /// The assembly-constraint viewport overlays — the cached
522    /// [`crate::constraint_overlays::ConstraintOverlay`] records the engine last
523    /// built from the kernel session (`assembly_overlay_json` + state), the source
524    /// of the drawn leader/arrow group, the label feed, and the grabbable-handle
525    /// hit regions. Refreshed after every history apply and every constraint
526    /// mutation; see the appended assembly-overlay impl block.
527    pub(crate) constraint_overlays: Vec<crate::constraint_overlays::ConstraintOverlay>,
528    /// A live GRAB on a constraint's distance-arrow / angle-arc handle (`Some`
529    /// between `constraint_drag_begin` and `constraint_drag_release`): the drag
530    /// previews the value locally and COMMITS on release via
531    /// `assembly_update_constraint_json` (which auto-solves).
532    pub(crate) constraint_drag: Option<ConstraintDrag>,
533    /// The `world_per_pixel` the constraint overlay buffers were last baked at
534    /// (screen-constant arc/rod sizing): `ensure_constraint_overlay_current`
535    /// re-bakes when the camera zoom moves it materially. `0.0` = never baked.
536    constraint_overlay_wpp: f64,
537    /// What the free-motion glyph group was last baked from (component,
538    /// its freedom row, centre), and at which `world_per_pixel`.
539    freedom_glyph_key: Option<String>,
540    freedom_glyph_wpp: f64,
541    /// The `world_per_pixel` the FEATURE-DIMENSION gizmo group
542    /// (`feature-dim-leaders`) was last baked at. The leaders' rod/cone/origin
543    /// sphere — and the angular arc's whole world RADIUS — are
544    /// `pixels × world_per_pixel`, so a zoom makes the drawn gizmo stale;
545    /// `ensure_feature_dimension_overlay_current` re-bakes on a material move.
546    /// `0.0` = nothing baked (gizmo disarmed / not in dimension mode).
547    /// See `feature_dims`.
548    pub(crate) feature_dim_overlay_wpp: f64,
549    /// The `world_per_pixel` the live SKETCH overlay groups (geometry, points,
550    /// preview, `sketch-dim-leaders`, `sketch-constraint-glyphs`) were last baked
551    /// at — construction dashes, dimension arrowheads and constraint glyphs are
552    /// all screen-constant. `ensure_sketch_overlay_current` re-bakes on a material
553    /// zoom. `0.0` = nothing baked (not in sketch mode). See `sketch_input`.
554    pub(crate) sketch_overlay_wpp: f64,
555    /// The constraint id whose referenced ELEMENTS the panel/label hover is
556    /// currently highlighting (dedupe key so a held hover doesn't re-bump the
557    /// emphasis generation every frame).
558    constraint_hovered: Option<String>,
559    /// Set for ONE frame when a constraint LABEL hover applied the element
560    /// highlight; the viewport's modeling hover branch consumes it (mirrors
561    /// [`Self::take_sketch_list_hover`]) so the scene hover doesn't clobber it.
562    constraint_label_hover_active: bool,
563    /// The SCENE-TREE row the pointer is over, as the pick candidate its hover
564    /// lit (the row→viewport highlight). Kept so
565    /// [`scene_tree_hover_end`](Self::scene_tree_hover_end) clears ONLY a hover
566    /// the tree itself set — never one the viewport lit in the meantime — and as
567    /// the dedupe key that keeps a held row hover from re-bumping the emphasis
568    /// generation every frame. See `scene_query.rs`.
569    scene_tree_hovered: Option<pick::PickCandidate>,
570    /// Set for ONE frame while a Scene-tree row is hovered; the viewport's
571    /// modeling hover branch consumes it via
572    /// [`take_scene_tree_hover`](Self::take_scene_tree_hover) and skips the
573    /// pointer-off-viewport `clear_hover` (mirrors
574    /// [`Self::take_sketch_list_hover`]) so the row-set highlight survives.
575    scene_tree_hover_active: bool,
576    /// The DIALOG row the pointer is over — a feature form's reference line, its
577    /// read-only `Outputs` line, or the reference picker card's picked-name line
578    /// (the row→viewport highlight, the twin of the Scene tree's above). Its OWN
579    /// slot, not the tree's: History, Constraints, PMI and the Scene tree can all
580    /// be on screen at once in a split dock, and a shared slot would let the pane
581    /// that draws second end the hover the pane that drew first had just set. See
582    /// [`DialogHover`] for what it memoizes and `scene_query.rs` for the API.
583    dialog_hovered: Option<DialogHover>,
584    /// Set for ONE frame while a dialog row is hovered; the viewport's modeling
585    /// hover branch consumes it via [`take_dialog_hover`](Self::take_dialog_hover)
586    /// and skips the pointer-off-viewport `clear_hover` (mirrors
587    /// [`Self::take_scene_tree_hover`]) so the row-set highlight survives.
588    dialog_hover_active: bool,
589    /// The constraint SELECTED via its viewport label chip (`Some` = selected):
590    /// drives the chip's selected accent, the context bar's Delete-constraint
591    /// action, and clears with the selection (Esc / Clear / delete). Read
592    /// through [`Self::selected_constraint`], which prunes a stale id.
593    selected_constraint: Option<String>,
594    /// The feature SELECTED in the history tree, beside the geometry selection
595    /// that selecting it made. It stays selected only while that geometry
596    /// selection is unchanged — a viewport pick, Esc or Clear replaces the
597    /// geometry and so deselects the feature. Read through
598    /// [`Self::selected_feature`].
599    selected_feature: Option<(String, selection_ux::FeatureSelection)>,
600    /// How many times a constraint's dialog has been OPENED through
601    /// [`Self::assembly_set_constraint_open`] — the constraint half of the same
602    /// counter as [`Self::pmi_dialog_opens`], read by the app's dialog door
603    /// so a re-open of the constraint already open still counts. The flag
604    /// itself lives in the kernel (it is part of the saved document), so this
605    /// counts the OPENS this engine performed, not the flag's history.
606    pub(crate) constraint_opens: u64,
607    /// The STEP product structure a [`Self::probe_step_assembly`] parsed, held
608    /// until the import dialog's button decides its fate: consumed by
609    /// [`Self::import_probed_step_assembly`], dropped by
610    /// [`Self::discard_probed_step_assembly`] (Cancel), by the next probe, or by
611    /// a document switch. The ONE parse of a structured STEP import lives here —
612    /// the dialog needs the counts BEFORE the user chooses, and re-parsing
613    /// multi-MB Part-21 text on the way back would double the most expensive step
614    /// of the import. See `model_io`'s import block.
615    pub(crate) pending_step_assembly: Option<brep_kernel::StepAssembly>,
616    /// STEP probes submitted to the runner and not yet answered (their ids).
617    pub(crate) pending_step_probes: std::collections::HashSet<u64>,
618    /// Answered probes awaiting [`Self::take_step_probe`], oldest first.
619    pub(crate) step_probe_results: std::collections::VecDeque<(u64, StepProbeOutcome)>,
620    /// Monotonic id pairing a probe reply with its submission.
621    pub(crate) next_step_probe_id: u64,
622}
623
624/// What a STEP probe found, as the file panel consumes it (see
625/// [`EngineState::submit_step_probe`]).
626#[derive(Debug, Clone, PartialEq)]
627pub enum StepProbeOutcome {
628    /// Product structure: the parsed assembly is stashed for
629    /// [`EngineState::import_probed_step_assembly`]; these are its counts.
630    Structure(model_io::StepAssemblyProbe),
631    /// No structure worth keeping — take the flat lane.
632    Flat,
633    /// The text did not parse as Part 21; the flat lane refuses it with its
634    /// own wording.
635    Failed(String),
636}
637
638/// A live constraint-handle drag: which constraint + which `inputParams` field
639/// (`distance` / `angle`), the params snapshot the commit mutates, and the live
640/// preview value the overlay/label show while dragging.
641#[derive(Debug, Clone)]
642pub struct ConstraintDrag {
643    pub id: String,
644    pub field: &'static str,
645    pub params: serde_json::Value,
646    pub preview: f64,
647}
648
649/// WHO a finished reference-selection commits to: a history FEATURE's params
650/// (the original widget) or an ASSEMBLY CONSTRAINT's `inputParams` (same modal,
651/// different commit lane — see `assembly_ops::begin_ref_select_for_constraint`).
652#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
653pub enum RefSelectTarget {
654    #[default]
655    Feature,
656    AssemblyConstraint,
657    /// ONE SPLINE ANCHOR's port attachment: `path` is unused, the picked name
658    /// must be a PORT feature id (a click on a port's drawn sheet), and Finish
659    /// writes `persistentData.spline.points[index].attachment` (see
660    /// `spline_edit.rs`).
661    SplineAnchor { index: usize },
662    /// A PMI annotation's reference field: `feature_id` is the annotation id;
663    /// Finish writes the picked names into its params through the PMI block
664    /// (`pmi_ops.rs`). Vertex picks are `{solid}@x,y,z` in WORLD coordinates.
665    Pmi,
666    /// ONE CONNECTION POINT's reference field: `feature_id` is the point's
667    /// part-local ADDRESS (`J1.VCC`) — a connection point is data, not a
668    /// feature, so there is no id to carry — and `path` is the block key the
669    /// pick writes (`pointRef` / `directionRef`). Finish writes it through
670    /// `ports_ops::ports_commit_ref`, and NOTHING ROLLS: a point resolves at
671    /// the tail of the run, so the geometry it may name is what is on screen.
672    PortPoint { field: ports_ops::PortRefField },
673    /// A drawing-sheet object's reference field — a dimension's anchors, an
674    /// ordinate set's datum or members, a section's cutting line, a detail's
675    /// centre or rim: `feature_id` is the object id, the picks are ANCHORS
676    /// clicked on the open sheet's paper (`sheet_ops.rs`), and nothing rolls.
677    Sheet,
678    /// A CAM record's reference field (a setup's orientation picks and
679    /// bodies, an operation's targets): `feature_id` is the record id and
680    /// `path` addresses the field from the `cam` block's ROOT, a `#<id>`
681    /// segment selecting the array element with that id
682    /// (`["setups", "#SETUP1", "targets"]`). Finish writes it through
683    /// `cam_ops::cam_commit_refs`, and nothing rolls: CAM references name the
684    /// finished model, which is what is on screen.
685    Cam,
686    /// A manufacturing-instructions step's OCCURRENCE field — an insertion
687    /// arrow's part or a callout's: `feature_id` is the step id and `field`
688    /// says which row. The pick is a SOLID of the placed assembly; Finish
689    /// derives the occurrence (the solid's name without its member) and writes
690    /// it through `instructions_ops::instructions_commit_ref`, one undo step,
691    /// and nothing rolls.
692    Instructions { field: InstructionsRefField },
693    /// A motion-study CLIP's parts field: `feature_id` is the clip id and
694    /// `path` its list field (`["occurrences"]`). Finish writes the picked
695    /// names, as the components that hold them, through
696    /// `simulation_refs::simulation_commit_refs`; nothing rolls.
697    Simulation,
698    /// A REPLACE COMPONENT repair: a held mate's broken reference, re-aimed
699    /// at ONE face or edge picked in the view. `feature_id` is the mate id
700    /// and `path` is `[old reference]`. Finish commits nothing itself: it
701    /// hands `(mate, old ref, pick)` to the replace dialog
702    /// ([`EngineState::take_replace_repair_pick`]), whose repair also
703    /// reaches mates inside nested assemblies; nothing rolls.
704    ReplaceRepair,
705}
706
707/// Which occurrence field of a step a pick fills ([`RefSelectTarget::Instructions`]).
708#[derive(Debug, Clone, Copy, PartialEq, Eq)]
709pub enum InstructionsRefField {
710    /// The part insertion arrow `index` animates (one of the step's own adds).
711    Arrow(usize),
712    /// The part callout `index` points at (any part of the assembly).
713    Callout(usize),
714}
715
716/// The modal state of the reference-selection widget while it is ACTIVE: which
717/// feature-dialog field is being filled, what it accepts, and the running list
718/// of picked kernel names (the source of truth — added by picking in the view,
719/// removed via the per-line X). See the ref-select methods below.
720#[derive(Debug, Clone, Default)]
721pub struct RefSelectState {
722    /// The id of the feature whose param is being edited.
723    pub feature_id: String,
724    /// The JSON path into that feature's `inputParams` the names write to
725    /// (`["targetSolid"]`, `["boolean","targets"]`, `["faceRef"]`, …).
726    pub path: Vec<String>,
727    /// A human label for the modal heading (the field's label).
728    pub label: String,
729    /// The allowed pick kinds (`["SOLID"]`, `["FACE"]`, …) — the type constraint.
730    pub filter: Vec<String>,
731    /// Whether the field takes a LIST of references (else a single one).
732    pub multiple: bool,
733    /// The picked kernel names — the running selection (source of truth).
734    pub names: Vec<String>,
735    /// The rollback step to restore on Finish/Cancel (the edited feature's own
736    /// step): entering the mode rolls to the pre-feature "before" state, so we
737    /// remember where to return.
738    pub restore_index: usize,
739    /// Which surface Finish commits the names to (feature params vs an
740    /// assembly constraint's `inputParams`). Defaults to [`RefSelectTarget::Feature`].
741    pub target: RefSelectTarget,
742    /// How many picks landed since the field was activated (a viewport click,
743    /// a fed name, a sheet anchor). A SINGLE-valued field drawn IN ITS FORM
744    /// finishes on its first pick — the form's consumer has nothing to wait
745    /// for once the one reference is in — and this is what says one landed,
746    /// as opposed to the seed the field opened with.
747    pub picks: usize,
748    /// Whether the field's own form drew the pick this frame. An in-form pick
749    /// has no card of its own, so a form that stops being drawn (its pane
750    /// tabbed away or closed, its subject gone) would strand the pick with
751    /// nothing on screen to end it; the shell finishes a pick nobody claimed.
752    pub claimed: bool,
753    /// The form asked for the pick to finish at the END of this frame rather
754    /// than now: an edit elsewhere on a feature's form while its pick runs.
755    /// Deferred because the viewport pane may draw AFTER the form in the same
756    /// frame and the click that caused the edit (a text field losing focus)
757    /// may itself be the pick — finishing at once would hand that click to
758    /// the ordinary selection instead.
759    pub finish_requested: bool,
760}
761
762impl Default for EngineState {
763    fn default() -> Self {
764        Self {
765            scene: RenderScene::new(),
766            context_scene: RenderScene::new(),
767            context_selected: None,
768            context_hovered: None,
769            context_pick_lit: Vec::new(),
770            context_display: Default::default(),
771            camera: ViewCamera::default(),
772            controls: ArcballControls::new(),
773            settings: RenderSettings::default(),
774            emphasis: Emphasis::default(),
775            widgets: WidgetRegistry::new(),
776            history: History::default(),
777            history_report: String::new(),
778            metadata: crate::metadata::MetadataStore::new(),
779            document_thumbnail: std::cell::RefCell::new(None),
780            thumbnail_cache: std::cell::RefCell::new(None),
781            settings_generation: 1,
782            dirty: true,
783            ref_select: None,
784            replace_repair_pick: None,
785            selection_filter: SelectionFilter::default(),
786            transform_gizmo: TransformArm::default(),
787            component_move: ComponentMoveArm::default(),
788            sketch_edit: None,
789            sketch3d_edit: None,
790            sketch3d_overlay_wpp: 0.0,
791            sketch_camera_locked: true,
792            sketch_list_hover_active: false,
793            notices: Vec::new(),
794            graded_notices: Vec::new(),
795            hidden_sketches: std::collections::HashSet::new(),
796            shown_sketch_ids: Vec::new(),
797            shown_board_ids: Vec::new(),
798            board_built_from: None,
799            board_pin_nets: Default::default(),
800            board_build: board_geometry::BoardBuild::default(),
801            board_displays: Vec::new(),
802            construction_frames: Vec::new(),
803            sketch_profiles: Vec::new(),
804            sketch_paths: Vec::new(),
805            sketch_points: Vec::new(),
806            sketch_axes: Vec::new(),
807            wire_harness_report: None,
808            ports_report: None,
809            sheet_open: None,
810            sheet_open_object: None,
811            sheet_object_opens: 0,
812            sheet_selected_object: None,
813            sheet_cache: None,
814            sheet_lines: Default::default(),
815            document_name: String::new(),
816            pmi_report: None,
817            pmi_active_view: None,
818            pmi_open_annotation: None,
819            pmi_open_view: None,
820            pmi_selected_annotation: None,
821            pmi_selected_view: None,
822            pmi_dialog_opens: 0,
823            pmi_modeling: None,
824            pmi_explode_originals: std::collections::HashMap::new(),
825            explode_playback: Default::default(),
826            simulation_play: None,
827            pmi_section_hidden: Vec::new(),
828            pmi_unposed_visibility: 0,
829            pmi_state_cache: Default::default(),
830            pmi_balloon_stale: None,
831            pmi_overlay_key: None,
832            pmi_center_marks_drawn: Default::default(),
833            pmi_hovered: None,
834            pmi_label_hover_active: false,
835            spline_overlay_key: None,
836            cam: cam_ops::CamGeneration::default(),
837            component_ports_visible: false,
838            spline_edit_feature: None,
839            spline_anchor_picked: None,
840            cage_edit_feature: None,
841            cage_overlay_key: None,
842            hidden_datums: std::collections::HashSet::new(),
843            shown_datum_names: Vec::new(),
844            runner: Box::new(crate::runner::InlineRunner::new()),
845            plugins: plugins::Plugins::default(),
846            interference_sessions: Default::default(),
847            interference_sequence: 0,
848            illustration_snapshot: None,
849            geometry_diagnostics: Default::default(),
850            staged_feature: None,
851            busy_refused_seen: 0,
852            run_generation: 0,
853            applied_generation: 0,
854            run_progress: None,
855            cancelled_run: None,
856            expression_preview: None,
857            runs_replied: 0,
858            superseded_displays: HashMap::new(),
859            pending_fit: false,
860            instructions_wizard_pending: false,
861            instructions_views_pending: false,
862            instructions_insertion: None,
863            provenance: std::collections::HashMap::new(),
864            entity_origin: std::collections::HashMap::new(),
865            info_cache: std::collections::HashMap::new(),
866            pending_query: std::collections::HashMap::new(),
867            next_query_id: 0,
868            pending_mesh_imports: std::collections::HashMap::new(),
869            mesh_preview_results: std::collections::VecDeque::new(),
870            mesh_preview_phases: std::collections::VecDeque::new(),
871            region_fit_results: std::collections::VecDeque::new(),
872            apply_choices_results: std::collections::VecDeque::new(),
873            next_mesh_import_id: 0,
874            pending_topology: std::collections::HashSet::new(),
875            topology_asked: std::collections::HashSet::new(),
876            next_topology_id: 0,
877            assembly_components: Vec::new(),
878            assembly_sync: None,
879            assembly_session_generation: None,
880            assembly_session_serial: 0,
881            session_transfer: true,
882            session_imports: 0,
883            session_replays: 0,
884            replace_pending: None,
885            last_replace_report: None,
886            consumed_names: std::collections::HashSet::new(),
887            library_resync: false,
888            sketch_links_released: false,
889            constraint_overlays: Vec::new(),
890            constraint_drag: None,
891            constraint_overlay_wpp: 0.0,
892            freedom_glyph_key: None,
893            freedom_glyph_wpp: 0.0,
894            feature_dim_overlay_wpp: 0.0,
895            sketch_overlay_wpp: 0.0,
896            constraint_hovered: None,
897            constraint_label_hover_active: false,
898            scene_tree_hovered: None,
899            scene_tree_hover_active: false,
900            dialog_hovered: None,
901            dialog_hover_active: false,
902            selected_constraint: None,
903            selected_feature: None,
904            constraint_opens: 0,
905            pending_step_assembly: None,
906            pending_step_probes: std::collections::HashSet::new(),
907            step_probe_results: std::collections::VecDeque::new(),
908            next_step_probe_id: 1,
909        }
910    }
911}
912
913impl EngineState {
914    pub fn new() -> Self {
915        Self::default()
916    }
917
918    fn pick_options(&self) -> PickOptions {
919        PickOptions {
920            double_sided: self.settings.pick_double_sided,
921            ..PickOptions::default()
922        }
923    }
924}
925
926/// Has the camera's `world_per_pixel` moved MATERIALLY (>0.5%) away from the
927/// value an overlay group's screen-constant sizing was baked at?
928///
929/// The ONE judgement every camera-keyed overlay re-bake shares
930/// (`ensure_overlays_current` and the per-group ensures it drives), so
931/// "what counts as a zoom" cannot drift between them. `baked == 0.0` means
932/// "never baked" — a state each caller handles itself, so it reads as NOT
933/// stale here. The band is what keeps a quiet frame quiet: float jitter in the
934/// camera never trips it, so there is no per-frame re-bake loop.
935pub(crate) fn overlay_wpp_stale(baked: f64, now: f64) -> bool {
936    baked > 0.0 && (now - baked).abs() > baked * 0.005
937}
938
939// ============================================================================
940// MODULE MAP — engine_state is split into topic children. THIS file is the
941// module root: it keeps the EngineState/RefSelectState structs, Default, the
942// constructor + shared pick_options, the child `mod` declarations, and the
943// re-exports that preserve the original `engine_state::*` public paths.
944// Append new work to the matching child (or add a new child + re-export here).
945// ============================================================================
946
947/// The assembly surface (Wave-3): main-side session sync + component
948/// projection, constraint CRUD with the document fold (pose-authority
949/// contract), the insert-component flow, component fix/select actions, the
950/// constraint flavor of the reference picker, and `document_signature` — the
951/// ONE parts-library `sourceSignature` hash every writer shares.
952mod assembly_ops;
953mod context_pick;
954/// Assembly-constraint viewport overlays: overlay refresh from the kernel
955/// session, the grabbable distance/angle handle pick + drag preview +
956/// `assembly_update_constraint_json` commit, movedSolids re-tessellation, the
957/// drag-path document fold, label feed + element hover, and tests.
958mod assembly_overlay;
959/// Replace component (swap an occurrence's part, keep its mates by name when
960/// the parts share lineage; mark the rest unresolved) — `replace_components`,
961/// the report, repair, and the Save As lineage stamp.
962pub mod replace_component;
963/// BOM export: the `{partName, sourceKey, quantity}` parts list off the
964/// main-side parts library + live component projection, serialized as CSV /
965/// JSON for the file dialog's Export modal.
966mod bom;
967/// Mass properties of a part: computed from its geometry and the density the
968/// PART carries (never per occurrence), and the load-time migration of old
969/// occurrence-stored Mass/Material onto the part.
970pub mod part_mass;
971/// Camera & view commands (zoom-to-fit, resize, pointer/wheel ingestion,
972/// projection, standard views, camera state/matrices, world→screen) plus the
973/// widget-overlay feeds (datums/overlay/dimensions/transform JSON), the
974/// ViewCube (incl. `apply_look_direction`), `datum_pick`, the widget
975/// transform-handle hover/pick/drag path, and `dimension_anchors_json`.
976mod camera_widgets;
977/// Committed-sketch persistent overlays: sheet-solid synthesis from stored
978/// profiles (`refresh_committed_sketches`), per-sketch visibility, the
979/// Scene-tree rows + `sketch_entities_json`, and the committed-sketch tests.
980/// The board's 3D bodies (substrate + copper + via barrels), derived from the
981/// document's `pcb` block after every run — see the module notes.
982mod board_geometry;
983mod committed_sketches;
984/// The COMPONENT Move gizmo (assemblies §8.5): translate→rotate→off cycle at
985/// the member-bbox center, fixed-refusal, free-move drag with the pose commit
986/// (and re-solve) on release. See `ComponentMoveArm`.
987mod component_move;
988/// Flip a constraint to its other solution (one undo step, a seeded pose).
989mod assembly_flip;
990/// Assembly COMPONENT read surface: the app-side component projection derived
991/// from the history's ACOMP features + the scene's namespaced solid names
992/// (`component_of_solid` / `component_info` / `component_bbox_center`), and its
993/// tests + shared assembly fixtures.
994mod components;
995/// Construction datum/plane display: frame→feature mapping,
996/// `refresh_construction_datums`, datum visibility + selection + entity rows,
997/// and the construction-datum tests.
998mod construction_datums;
999/// Expressions & configurator surface (`expressions_json`, `set_expressions`,
1000/// `configurator_json`, `expression_variables_json`) + parsing helpers + tests.
1001mod expressions;
1002pub use expressions::ExpressionPreview;
1003/// The feature-dimension gizmo (dimension arrows / angle arc / center-sphere
1004/// toggle): arm state, `__brep`-style annotation JSON, overlay publishing,
1005/// drag + set-value writeback, `FEATURE_DIM_OVERLAY`, fd_* math helpers, tests.
1006mod feature_dims;
1007/// The assembly interference check: the bbox-prefiltered pairwise
1008/// non-destructive INTERSECT sweep over component instances
1009/// (`interference_check`), its report types, and the pure pair planner.
1010mod interference;
1011mod batch;
1012mod plugins;
1013mod illustration;
1014/// History runs & the feature CRUD surface: `run_history_json`, the
1015/// runner/pump/apply seam, history JSON accessors, roll/update/add/delete/
1016/// reorder, engine undo/redo, `load_model_and_fit`,
1017/// and the history-cache rollback tests.
1018mod history_ops;
1019/// Model I/O: `import_step_feature`, `export_step_text`, `export_stl_text`,
1020/// `triangle_normal`, and the io tests.
1021mod model_io;
1022/// Construction PLANES as ordinary pick candidates: the combined
1023/// scene+plane-card candidate list (`pick_candidates_at`), the planes-aware
1024/// single-hit pick (`pick_top_at`), the shared candidate ordering, and the
1025/// plane-pick tests. Read its header for how a plane competes for a pick.
1026mod plane_pick;
1027/// Scene & object queries: `pick_json`/`hover_json`, settings + emphasis +
1028/// visibility + color overrides, `scene_listing_json`, `depth_range_bbox`,
1029/// `scene_entities_json`/select-by-name, mass properties + object info, tests.
1030mod scene_query;
1031/// The selection-filter state + kind gating (`SelectionFilter`,
1032/// `select_filtered_at`, hide-selected) and its tests.
1033mod selection_filter;
1034/// Selection & hover UX: clear/select-top/selection JSON, the modal
1035/// ref-select widget (incl. `set_json_at`), hover/candidate cycling, and the
1036/// live sketch overlay feed (`set_sketch_overlay`/`clear_sketch_overlay`) +
1037/// selection UX tests.
1038mod selection_ux;
1039/// Sketch editing ops appended after dimensions: dimension labels/value/drag,
1040/// sketch undo/redo + diagnostics dump, trim, external edge refs
1041/// (pick/link/reproject), and hand-draw strokes.
1042mod sketch_edit_ops;
1043/// Sketch-mode viewport input: overlay refresh, uv picking, hover/click/drag,
1044/// the draw tools + pending-geometry helpers, and delete-selection.
1045mod sketch_input;
1046/// Sketch mode session: `SketchDrag`/`SketchEdit` state, plane-frame helpers,
1047/// enter/exit/new sketch, and the camera lock.
1048mod sketch_mode;
1049/// 3D sketch mode: `Sketch3dEdit`, enter/exit/new, the cursor-to-3D mapping,
1050/// the draw tools, reference linking, constraint edits and undo.
1051mod sketch3d_mode;
1052pub use sketch3d_mode::{Sketch3dCursorHit, Sketch3dEdit};
1053/// Sketch entity-list panel rows + notices + solver settings
1054/// (`SketchEntityRow`), and the constraint palette/actions
1055/// (`SketchConstraintAction`, add-constraint builders, ground/construction
1056/// toggles, cleanup).
1057mod sketch_panel;
1058/// The transform-controls gizmo: `GizmoMode`/`TransformArm`, arm/drag/apply,
1059/// pose ⇄ params JSON, quaternion helpers (`rotate_euler_xyz_f64`), tests.
1060mod transform_gizmo;
1061/// The wire-harness surface: the document's `wireHarness` block (connection
1062/// add / edit / remove / bundles toggle — checkpointed document edits that
1063/// re-run the history, whose tail routes them), the applied run's routing
1064/// report, the endpoint list, and the panel's hover highlight.
1065mod wire_harness_ops;
1066/// The PORTS surface: the ports tail's report of the last applied run — the
1067/// one place the tail's resolved points, refused names and unresolved
1068/// references reach a caller, the tail not being a feature.
1069mod ports_ops;
1070pub use ports_ops::PortRefField;
1071pub use selection_ux::RefSelectField;
1072// Every harness wire as a BOM line with its cut length (MF QTY), or the reason
1073// the last applied run cannot give one.
1074mod wire_bom;
1075mod pmi_ops;
1076mod pmi_section;
1077// Assembly exploded views: PMI views whose explode rows are ordered steps.
1078mod exploded_ops;
1079pub use exploded_ops::{ExplodePlayback, ExplodeStep, ExplodedView};
1080// Manufacturing instructions: the wrapper assembly, the wizard, step views and the player.
1081mod instructions_ops;
1082pub use instructions_ops::{Insertion, PlayTo};
1083// Motion studies: the simulation block, motors and clips.
1084mod simulation_ops;
1085pub use simulation_ops::MotorMate;
1086// Motion-study playback: bake the frames through the kernel, pose the displays.
1087mod simulation_play;
1088pub use simulation_play::{BakedFrame, SimulationPlayback};
1089// Motion-study player data: the study's frames for the instructions player
1090// template (the offline .html, and what the PLM serves).
1091mod simulation_player;
1092pub use simulation_player::assembly_signature;
1093// A clip's parts picked in the viewport: the reference-selection lane of
1094// the Simulation pane's forms.
1095mod simulation_refs;
1096// A study's published motion on a manufacturing-instructions step.
1097mod instructions_motion;
1098pub use instructions_motion::PublishedMotion;
1099// Their printed PDF, per-step PNG and SVG, and the print settings.
1100mod instructions_export;
1101pub use instructions_export::IMAGE_PX as INSTRUCTIONS_IMAGE_PX;
1102pub use instructions_export::{TOPOLOGY_WAIT, InstructionsPdfExport};
1103pub mod cam_ops;
1104pub use pmi_ops::{pmi_view_params, pmi_view_schema, world_vertex_ref, PmiModelingSnapshot, PmiViewPatch};
1105// Drawing sheets: the `sheets` block, the open sheet and the projection cache.
1106mod sheet_ops;
1107pub(crate) mod pmi_overlay;
1108pub use pmi_overlay::PmiLabel;
1109/// The 3D spline-anchor editor: the resolved anchor list, the value edits
1110/// (position / distances / flip / side / add / remove / reorder / detach),
1111/// the anchor flavour of the transform gizmo, the port-attach flavour of the
1112/// reference picker, and the direction-cage overlay.
1113mod spline_edit;
1114/// The NURBS Solid CONTROL-CAGE editor: the cage's points through the
1115/// kernel's resolver, the multi-point selection that arms the transform
1116/// gizmo at its centroid, the drag write-back, loop insertion, reset and the
1117/// cage overlay.
1118mod cage_edit;
1119
1120// Re-exports preserving the original `engine_state::*` public surface.
1121pub use assembly_ops::{document_signature, nested_fixed_state, ComponentInsert};
1122pub use board_geometry::{
1123    default_color as board_default_color, BoardBuild, BOARD_SOLID_PREFIX, SUBSTRATE_SOLID,
1124    VIAS_SOLID,
1125};
1126pub use bom::{OCCURRENCE_ATTRIBUTES, PART_ATTRIBUTES};
1127pub use component_move::{ComponentMoveArm, TypedMove};
1128pub use components::ComponentInfo;
1129pub use interference::{ClearancePair, InterferencePair, InterferenceReport, INTERFERENCE_OVERLAY_GROUP};
1130pub use model_io::{
1131    EmbeddedOnly, PartSink, StepAssemblyImport, StepAssemblyProbe, StepAssemblyReport,
1132};
1133pub(crate) use model_io::is_iges_text;
1134pub use selection_filter::SelectionFilter;
1135pub use sketch_mode::{SketchDrag, SketchEdit};
1136pub use sketch_panel::{NoticeSeverity, SketchConstraintAction, SketchEntityRow};
1137pub use wire_harness_ops::ConnectionPatch;
1138pub use wire_bom::{WireBomLine, WireLengthState};
1139pub use spline_edit::SplineAnchorRow;
1140pub use cage_edit::{CageArm, CageSelect, CageSummary, CAGE_PICK_RADIUS_PX};
1141pub use transform_gizmo::{GizmoMode, TransformArm};
1142pub(crate) use transform_gizmo::rotate_euler_xyz_f64;
1143
1144
1145
1146
1147
1148
1149/// Reconstruction results either append to a document or remain uncommitted.
1150#[derive(Clone, Copy, PartialEq, Eq)]
1151pub(crate) enum MeshImportDestination {
1152    Document,
1153    Preview,
1154}