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