use super::simulation_player::assembly_signature;
use super::EngineState;
use brep_instructions::edit;
use brep_simulation::model::{Simulation, BLOCK_KEY};
use serde_json::{json, Value};
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PublishedMotion {
pub study: String,
pub name: String,
pub frames: usize,
pub duration: f64,
pub fps: u32,
pub current: bool,
}
impl EngineState {
fn instructions_target_document(&self) -> Result<(Value, String), String> {
let (_, part) = self.instructions_wrapper()?;
let document = self.history.parts_library()[part.as_str()]["document"].clone();
if document.is_null() {
return Err(format!("'{part}' has no embedded document"));
}
let signature = assembly_signature(&document);
Ok((document, signature))
}
pub fn instructions_published_motions(&self) -> Result<Vec<PublishedMotion>, String> {
let (document, signature) = self.instructions_target_document()?;
let block = match document.get(BLOCK_KEY) {
None | Some(Value::Null) => return Ok(Vec::new()),
Some(block) => Simulation::from_value(block)?,
};
Ok(block
.studies
.iter()
.filter_map(|study| {
let motion = study.motion.as_ref()?;
Some(PublishedMotion {
study: study.id.clone(),
name: study.name.clone(),
frames: motion.get("frames").and_then(Value::as_array).map_or(0, Vec::len),
duration: motion.get("duration").and_then(Value::as_f64).unwrap_or(study.duration),
fps: motion.get("fps").and_then(Value::as_u64).map_or(study.fps, |f| f as u32),
current: motion.get("assemblySignature").and_then(Value::as_str) == Some(signature.as_str()),
})
})
.collect())
}
pub fn instructions_attach_motion(&mut self, step: &str, study: Option<&str>) -> Result<String, String> {
let (document, signature) = self.instructions_target_document()?;
let block = match document.get(BLOCK_KEY) {
None | Some(Value::Null) => return Err("the assembly has no motion study: publish one in the Simulation workbench".into()),
Some(block) => Simulation::from_value(block)?,
};
let published: Vec<_> = block.studies.iter().filter(|s| s.motion.is_some()).collect();
let study = match study {
Some(id) => block.study(id).ok_or_else(|| format!("the assembly has no study '{id}'"))?,
None => match published.as_slice() {
[one] => one,
[] => return Err("the assembly has no published motion: publish a study in the Simulation workbench".into()),
_ => return Err(format!("the assembly has {} published motions: name the study", published.len())),
},
};
let motion = study.motion.as_ref().ok_or_else(|| format!("study '{}' is not published: publish it in the Simulation workbench", study.name))?;
if motion.get("assemblySignature").and_then(Value::as_str) != Some(signature.as_str()) {
return Err(format!("study '{}' was published before the assembly changed: publish it again there", study.name));
}
if motion.get("frames").and_then(Value::as_array).is_none_or(Vec::is_empty) {
return Err(format!("study '{}' published no frames", study.name));
}
let id = study.id.clone();
let patch = json!({ "motion": motion });
self.instructions_edit(None, |block| edit::update_step(block, step, &patch))?;
Ok(id)
}
pub fn instructions_detach_motion(&mut self, step: &str) -> Result<(), String> {
self.instructions_edit(None, |block| edit::update_step(block, step, &json!({ "motion": null })))
}
}