brep_render/engine_state/simulation_refs.rs
1//! A clip's parts picked in the viewport: the Simulation pane's forms are the
2//! app's schema-driven dialogs, and a visibility or transparency clip's parts
3//! are a `reference_selection` field — the same pick every feature dialog
4//! runs ([`EngineState::begin_ref_select`] and kin). This is its lane
5//! ([`RefSelectTarget::Simulation`]): begin on a clip's field, and on Finish
6//! write the picked names into the clip through `simulation_patch` as ONE
7//! undo step. Nothing rolls (a clip names the finished assembly, which is
8//! what is on screen) and nothing re-runs.
9
10use super::{EngineState, RefSelectState, RefSelectTarget};
11use crate::engine_state::selection_filter::SelectionFilter;
12use serde_json::json;
13
14impl EngineState {
15 /// Enter reference selection for clip `clip`'s list field at `path`
16 /// (`["occurrences"]`). The filter is the schema's (`SOLID`, `COMPONENT`:
17 /// a click on component geometry answers as its component).
18 pub fn begin_ref_select_for_simulation(
19 &mut self,
20 clip: &str,
21 path: Vec<String>,
22 label: String,
23 filter: Vec<String>,
24 multiple: bool,
25 seed_names: Vec<String>,
26 ) {
27 let restore_index = self.history.rollback();
28 self.selection_filter = SelectionFilter::from_ref_filter(&filter);
29 self.ref_select = Some(RefSelectState {
30 feature_id: clip.to_string(),
31 path,
32 label,
33 filter,
34 multiple,
35 names: seed_names,
36 restore_index,
37 target: RefSelectTarget::Simulation,
38 picks: 0,
39 claimed: false,
40 finish_requested: false,
41 });
42 self.sync_ref_select_emphasis();
43 }
44
45 /// Commit a finished clip pick: every name as the component that holds it
46 /// (a solid `ACOMP2:Part` stands for `ACOMP2`; a component pick is one
47 /// already), without repeats, in pick order, written at `path`'s field
48 /// of the clip as one undo step. The clip's study is found by the clip's
49 /// id (ids are unique across the block).
50 pub(crate) fn simulation_commit_refs(&mut self, clip: &str, path: &[String], names: &[String]) -> Result<(), String> {
51 let block = self.simulation()?.ok_or("the document has no motion study")?;
52 let study = block
53 .studies
54 .iter()
55 .find(|study| study.clips.iter().any(|c| c.id == clip))
56 .map(|study| study.id.clone())
57 .ok_or_else(|| format!("no clip '{clip}'"))?;
58 let mut occurrences: Vec<String> = Vec::new();
59 for name in names {
60 let occurrence = self.component_of_solid(name).unwrap_or_else(|| name.clone());
61 if !occurrence.is_empty() && !occurrences.contains(&occurrence) {
62 occurrences.push(occurrence);
63 }
64 }
65 let field = path.last().map(String::as_str).unwrap_or("occurrences");
66 self.simulation_patch(&study, Some(clip), &json!({ field: occurrences }), None)?;
67 // A pick is a discrete change (it ends with Finish): the next edit of
68 // the same clip's field is its own undo step.
69 self.history.break_coalescing();
70 Ok(())
71 }
72}
73