brep_render/engine_state/
instructions_motion.rs1use super::simulation_player::assembly_signature;
14use super::EngineState;
15use brep_instructions::edit;
16use brep_simulation::model::{Simulation, BLOCK_KEY};
17use serde_json::{json, Value};
18
19#[derive(Debug, Clone, PartialEq, serde::Serialize)]
21#[serde(rename_all = "camelCase")]
22pub struct PublishedMotion {
23 pub study: String,
24 pub name: String,
25 pub frames: usize,
26 pub duration: f64,
27 pub fps: u32,
28 pub current: bool,
31}
32
33impl EngineState {
34 fn instructions_target_document(&self) -> Result<(Value, String), String> {
36 let (_, part) = self.instructions_wrapper()?;
37 let document = self.history.parts_library()[part.as_str()]["document"].clone();
38 if document.is_null() {
39 return Err(format!("'{part}' has no embedded document"));
40 }
41 let signature = assembly_signature(&document);
42 Ok((document, signature))
43 }
44
45 pub fn instructions_published_motions(&self) -> Result<Vec<PublishedMotion>, String> {
47 let (document, signature) = self.instructions_target_document()?;
48 let block = match document.get(BLOCK_KEY) {
49 None | Some(Value::Null) => return Ok(Vec::new()),
50 Some(block) => Simulation::from_value(block)?,
51 };
52 Ok(block
53 .studies
54 .iter()
55 .filter_map(|study| {
56 let motion = study.motion.as_ref()?;
57 Some(PublishedMotion {
58 study: study.id.clone(),
59 name: study.name.clone(),
60 frames: motion.get("frames").and_then(Value::as_array).map_or(0, Vec::len),
61 duration: motion.get("duration").and_then(Value::as_f64).unwrap_or(study.duration),
62 fps: motion.get("fps").and_then(Value::as_u64).map_or(study.fps, |f| f as u32),
63 current: motion.get("assemblySignature").and_then(Value::as_str) == Some(signature.as_str()),
64 })
65 })
66 .collect())
67 }
68
69 pub fn instructions_attach_motion(&mut self, step: &str, study: Option<&str>) -> Result<String, String> {
75 let (document, signature) = self.instructions_target_document()?;
76 let block = match document.get(BLOCK_KEY) {
77 None | Some(Value::Null) => return Err("the assembly has no motion study: publish one in the Simulation workbench".into()),
78 Some(block) => Simulation::from_value(block)?,
79 };
80 let published: Vec<_> = block.studies.iter().filter(|s| s.motion.is_some()).collect();
81 let study = match study {
82 Some(id) => block.study(id).ok_or_else(|| format!("the assembly has no study '{id}'"))?,
83 None => match published.as_slice() {
84 [one] => one,
85 [] => return Err("the assembly has no published motion: publish a study in the Simulation workbench".into()),
86 _ => return Err(format!("the assembly has {} published motions: name the study", published.len())),
87 },
88 };
89 let motion = study.motion.as_ref().ok_or_else(|| format!("study '{}' is not published: publish it in the Simulation workbench", study.name))?;
90 if motion.get("assemblySignature").and_then(Value::as_str) != Some(signature.as_str()) {
91 return Err(format!("study '{}' was published before the assembly changed: publish it again there", study.name));
92 }
93 if motion.get("frames").and_then(Value::as_array).is_none_or(Vec::is_empty) {
94 return Err(format!("study '{}' published no frames", study.name));
95 }
96 let id = study.id.clone();
97 let patch = json!({ "motion": motion });
98 self.instructions_edit(None, |block| edit::update_step(block, step, &patch))?;
99 Ok(id)
100 }
101
102 pub fn instructions_detach_motion(&mut self, step: &str) -> Result<(), String> {
104 self.instructions_edit(None, |block| edit::update_step(block, step, &json!({ "motion": null })))
105 }
106}