use crate::scene::{solid_display_from_payload, RenderScene, SolidDisplay};
use brep_kernel::{display_payload_handle_native, execute_history, HistoryRequest};
use std::collections::HashMap;
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
pub struct SceneBuildReport {
#[serde(default)]
pub plugin_persistent_data: Vec<(String, serde_json::Value)>,
pub feature_errors: Vec<String>,
pub unresolved: Vec<String>,
#[serde(default)]
pub feature_notes: Vec<String>,
#[serde(default)]
pub feature_fulfilment: Vec<(String, brep_kernel::Fulfilment)>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub feature_refusals: Vec<(String, brep_kernel::KernelRefusal, Option<String>)>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub feature_approximations: Vec<(String, Vec<brep_kernel::Approximation>)>,
pub display_errors: Vec<String>,
pub feature_timings: Vec<(String, f64)>,
pub feature_outputs: Vec<(String, Vec<String>)>,
pub frames: Vec<(String, brep_kernel::Frame)>,
pub profiles: Vec<(String, brep_kernel::SketchProfile)>,
#[serde(default)]
pub axes: Vec<(String, brep_kernel::Axis)>,
#[serde(default)]
pub paths: Vec<(String, Vec<brep_kernel::NurbsCurve>)>,
#[serde(default)]
pub points: Vec<(String, brep_kernel::ScenePoint)>,
#[serde(default)]
pub wire_harness: Option<brep_kernel::WireHarnessReport>,
#[serde(default)]
pub pmi: Option<brep_kernel::PmiReport>,
#[serde(default)]
pub ports: Option<brep_kernel::PortsReport>,
}
pub fn scene_from_history_json(
request_json: &str,
) -> Result<(RenderScene, SceneBuildReport), String> {
let request: HistoryRequest = serde_json::from_str(request_json)
.map_err(|error| format!("history request parse: {error}"))?;
scene_from_history(&request)
}
pub fn scene_from_history(
request: &HistoryRequest,
) -> Result<(RenderScene, SceneBuildReport), String> {
let mut scene = RenderScene::new();
let report = update_scene_from_history(&mut scene, request)?;
Ok((scene, report))
}
pub fn scene_from_history_with_plugins(
request: &HistoryRequest,
runtime: &brep_plugins::Runtime,
) -> Result<(RenderScene, SceneBuildReport), String> {
runtime.with_provider(|| scene_from_history(request))
}
struct SceneLayout {
order: Vec<String>,
handles: std::collections::HashMap<String, u32>,
reused: std::collections::HashSet<String>,
creators: std::collections::HashMap<String, String>,
entity_origin: std::collections::HashMap<String, String>,
}
fn request_has_assembly(request: &HistoryRequest) -> bool {
request.assembly.is_some()
|| request.features.iter().any(|feature| {
feature.feature_type.eq_ignore_ascii_case("ACOMP")
|| feature.feature_type.eq_ignore_ascii_case("ASSEMBLY COMPONENT")
})
}
fn fold_history(result: &brep_kernel::HistoryResult, report: &mut SceneBuildReport) -> SceneLayout {
let mut order: Vec<String> = Vec::new();
let mut handles: std::collections::HashMap<String, u32> = std::collections::HashMap::new();
let mut reused: std::collections::HashSet<String> = std::collections::HashSet::new();
let mut creators: std::collections::HashMap<String, String> = std::collections::HashMap::new();
let mut entity_origin: std::collections::HashMap<String, String> =
std::collections::HashMap::new();
for (index, feature) in result.results.iter().enumerate() {
if let Some(data) = &feature.persistent_data {
if feature.error.is_none() { report.plugin_persistent_data.push((feature.id.clone(), data.clone())); }
}
let label = if feature.id.is_empty() { format!("feature[{index}]") } else { feature.id.clone() };
for removed in &feature.removed {
if handles.remove(removed).is_some() {
order.retain(|name| name != removed);
}
reused.remove(removed);
creators.remove(removed);
}
for added in &feature.added {
if handles.insert(added.name.clone(), added.handle).is_none() {
order.push(added.name.clone());
}
creators.insert(added.name.clone(), feature.id.clone());
for (_, name) in &added.face_names {
entity_origin
.entry(name.clone())
.or_insert_with(|| feature.id.clone());
}
for (_, name) in &added.edge_names {
entity_origin
.entry(name.clone())
.or_insert_with(|| feature.id.clone());
}
if feature.reused {
reused.insert(added.name.clone());
} else {
reused.remove(&added.name);
}
}
if let Some(error) = &feature.error {
report.feature_errors.push(format!("{label}: {error}"));
}
for name in &feature.unresolved {
report.unresolved.push(format!("{label}: {name}"));
}
for note in &feature.notes {
report.feature_notes.push(format!("{label}: {note}"));
}
if let Some(fulfilment) = &feature.fulfilment {
report
.feature_fulfilment
.push((feature.id.clone(), fulfilment.clone()));
}
if let Some(refusal) = &feature.refusal {
report
.feature_refusals
.push((feature.id.clone(), refusal.clone(), feature.refused_step.clone()));
}
if !feature.approximations.is_empty() {
report
.feature_approximations
.push((feature.id.clone(), feature.approximations.clone()));
}
report.feature_outputs.push((
feature.id.clone(),
feature.added.iter().map(|a| a.name.clone()).collect(),
));
for (name, frame) in &feature.frames {
report.frames.push((name.clone(), *frame));
}
for (name, profile) in &feature.profiles {
report.profiles.push((name.clone(), profile.clone()));
}
for (name, axis) in &feature.axes {
report.axes.push((name.clone(), *axis));
}
for (name, curves) in &feature.paths {
report.paths.push((name.clone(), curves.clone()));
}
for (name, point) in &feature.points {
report.points.push((name.clone(), *point));
}
}
report.feature_timings = result.timings.clone();
report.wire_harness = result.wire_harness.clone();
report.pmi = result.pmi.clone();
report.ports = result.ports.clone();
SceneLayout { order, handles, reused, creators, entity_origin }
}
pub struct SceneRunner {
pub(crate) plugin_revision: Option<u64>,
plugins: Option<brep_plugins::Runtime>,
plugin_error: Option<String>,
last_sent: HashMap<String, u32>,
last_lod: f64,
pub(crate) parts_library_revision: Option<u64>,
pub(crate) parts_library_names: std::collections::BTreeSet<String>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ComponentSnapshot {
pub id: String,
pub part_name: String,
pub transform: [f64; 16],
#[serde(default)]
pub requested_transform: Option<[f64; 16]>,
#[serde(default)]
pub authored_transform: serde_json::Value,
pub fixed: bool,
pub source: serde_json::Value,
pub solids: Vec<String>,
pub ports: Vec<String>,
}
impl ComponentSnapshot {
pub fn affine(&self) -> brep_kernel::AffineTransform {
brep_kernel::AffineTransform { elements: self.transform }
}
}
fn pose_params_to_transform(pose: &serde_json::Value) -> [f64; 16] {
let vec3 = |key: &str| -> [f64; 3] {
let value = &pose[key];
std::array::from_fn(|axis| value[axis].as_f64().unwrap_or(0.0))
};
let translate = vec3("translate");
let [rx, ry, rz] = vec3("rotateEulerDeg").map(f64::to_radians);
let (sx, cx) = rx.sin_cos();
let (sy, cy) = ry.sin_cos();
let (sz, cz) = rz.sin_cos();
let x = [[1.0, 0.0, 0.0], [0.0, cx, -sx], [0.0, sx, cx]];
let y = [[cy, 0.0, sy], [0.0, 1.0, 0.0], [-sy, 0.0, cy]];
let z = [[cz, -sz, 0.0], [sz, cz, 0.0], [0.0, 0.0, 1.0]];
let mul = |a: [[f64; 3]; 3], b: [[f64; 3]; 3]| -> [[f64; 3]; 3] {
std::array::from_fn(|i| {
std::array::from_fn(|j| (0..3).map(|k| a[i][k] * b[k][j]).sum())
})
};
let r = mul(x, mul(y, z));
[
r[0][0], r[0][1], r[0][2], translate[0],
r[1][0], r[1][1], r[1][2], translate[1],
r[2][0], r[2][1], r[2][2], translate[2],
0.0, 0.0, 0.0, 1.0,
]
}
impl From<&brep_kernel::ComponentRecord> for ComponentSnapshot {
fn from(record: &brep_kernel::ComponentRecord) -> Self {
Self {
id: record.id.clone(),
part_name: record.part_name.clone(),
transform: record.transform.elements,
requested_transform: Some(record.transform.elements),
authored_transform: serde_json::Value::Null,
fixed: record.fixed,
source: record.source.clone(),
solids: record.solids.clone(),
ports: record.ports.clone(),
}
}
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct AssemblySync {
pub components: Vec<ComponentSnapshot>,
pub state: serde_json::Value,
pub statuses: serde_json::Value,
pub dof: serde_json::Value,
pub overlay: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub parts_library: Option<brep_kernel::PartsLibraryMap>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct RunOutput {
pub snapshot: Vec<(String /* name */, u32 /* handle */, Option<SolidDisplay>)>,
pub report: SceneBuildReport,
pub provenance: Vec<(String, String)>,
pub entity_origin: Vec<(String, String)>,
#[serde(default)]
pub assembly_poses: Vec<(String, serde_json::Value)>,
#[serde(default)]
pub assembly_fixed: Vec<(String, bool)>,
#[serde(default)]
pub moved_solids: Vec<String>,
#[serde(default)]
pub imported_colors: Vec<(String, String)>,
#[serde(default)]
pub consumed: Vec<String>,
#[serde(default)]
pub assembly: Option<AssemblySync>,
}
impl SceneRunner {
pub fn set_plugins(&mut self, revision: u64, packages: serde_json::Value) {
let runtime = serde_json::from_value(packages)
.map_err(|e| format!("plugin packages: {e}"))
.and_then(brep_plugins::Runtime::new);
match runtime {
Ok(runtime) => { self.plugins = Some(runtime); self.plugin_error = None; }
Err(error) => { self.plugins = None; self.plugin_error = Some(error); }
}
self.plugin_revision = Some(revision);
self.last_sent.clear();
}
pub(crate) fn plugin_action(&self, request: crate::runner::PluginActionRequest) -> crate::runner::PluginActionReply {
let result = if let Some(source) = &request.script {
brep_plugins::execute_script(source, &request.input)
} else { match &self.plugins {
Some(runtime) => runtime.execute_action(&request.pins, &request.action, &request.input),
None => Err(self.plugin_error.clone().unwrap_or_else(|| "no plugin packages installed".into())),
}};
crate::runner::PluginActionReply { id: request.id, result }
}
pub fn new() -> Self {
Self {
plugin_revision: None,
plugins: None,
plugin_error: None,
last_sent: HashMap::new(),
last_lod: 1.0,
parts_library_revision: None,
parts_library_names: std::collections::BTreeSet::new(),
}
}
pub fn reset(&mut self) {
self.last_sent.clear();
self.parts_library_revision = None;
self.parts_library_names.clear();
}
pub fn run(&mut self, request: &HistoryRequest) -> RunOutput {
self.run_observed(request, &mut |_| true)
}
pub fn run_observed(
&mut self,
request: &HistoryRequest,
observe: &mut dyn FnMut(brep_kernel::HistoryProgress<'_>) -> bool,
) -> RunOutput {
let library_revision_before = brep_kernel::parts_library_revision();
let result = {
let mut trace = crate::run_trace::span("runner");
let result = match &self.plugins {
Some(runtime) => runtime.with_provider(|| brep_kernel::execute_history_observed(request, observe)),
None => brep_kernel::execute_history_observed(request, observe),
};
if let Some(trace) = trace.as_mut() {
trace.result(&result);
}
result
};
let mut report = SceneBuildReport::default();
let layout = fold_history(&result, &mut report);
let pose_updates: serde_json::Value =
serde_json::from_str(&brep_kernel::assembly_pose_updates_json())
.unwrap_or(serde_json::Value::Null);
let assembly_poses: Vec<(String, serde_json::Value)> = pose_updates["poses"]
.as_object()
.map(|map| map.iter().map(|(id, pose)| (id.clone(), pose.clone())).collect())
.unwrap_or_default();
let assembly_fixed: Vec<(String, bool)> = pose_updates["isFixed"]
.as_object()
.map(|map| {
map.iter()
.filter_map(|(id, flag)| flag.as_bool().map(|b| (id.clone(), b)))
.collect()
})
.unwrap_or_default();
let dof: serde_json::Value = serde_json::from_str(&brep_kernel::assembly_dof_json())
.unwrap_or(serde_json::Value::Null);
let moved_solids: Vec<String> = dof["movedSolids"]
.as_array()
.map(|items| {
items
.iter()
.filter_map(|item| item["name"].as_str().map(String::from))
.collect()
})
.unwrap_or_default();
for name in &moved_solids {
self.last_sent.remove(name);
}
let assembly = request_has_assembly(request).then(|| {
let authored: HashMap<&str, &serde_json::Value> = request.features.iter()
.filter_map(|f| f.input_params["id"].as_str().map(|id| (id, &f.input_params["transform"])))
.collect();
let mut components: std::collections::BTreeMap<String, ComponentSnapshot> =
std::collections::BTreeMap::new();
for feature in &result.results {
for removed in &feature.removed {
components.remove(removed);
}
for record in &feature.components {
let mut snapshot = ComponentSnapshot::from(record);
snapshot.authored_transform = authored.get(record.id.as_str())
.map(|value| (*value).clone()).unwrap_or_default();
components.insert(record.id.clone(), snapshot);
}
}
for (id, pose) in &assembly_poses {
if let Some(component) = components.get_mut(id) {
component.transform = pose_params_to_transform(pose);
}
}
let parse = |json: String| {
serde_json::from_str::<serde_json::Value>(&json).unwrap_or(serde_json::Value::Null)
};
AssemblySync {
components: components.into_values().collect(),
state: parse(brep_kernel::assembly_state_json()),
statuses: parse(brep_kernel::assembly_statuses_json()),
dof: dof.clone(),
overlay: parse(brep_kernel::assembly_overlay_json()),
parts_library: (brep_kernel::parts_library_revision() != library_revision_before)
.then(brep_kernel::parts_library_map),
}
});
let consumed: Vec<String> = result
.results
.iter()
.flat_map(|feature| feature.removed.iter().cloned())
.collect::<std::collections::BTreeSet<String>>()
.into_iter()
.collect();
let lod = if request.display_lod.is_finite() && request.display_lod > 0.0 {
request.display_lod
} else {
1.0
};
if lod != self.last_lod {
self.last_sent.clear();
self.last_lod = lod;
}
let mut snapshot: Vec<(String, u32, Option<SolidDisplay>)> =
Vec::with_capacity(layout.order.len());
let mut next_sent: HashMap<String, u32> = HashMap::with_capacity(layout.order.len());
for name in &layout.order {
let handle = layout.handles[name];
if self.last_sent.get(name) == Some(&handle) {
debug_assert!(
layout.reused.contains(name),
"handle unchanged must imply reused (monotonic handles): {name}"
);
snapshot.push((name.clone(), handle, None));
next_sent.insert(name.clone(), handle);
continue;
}
match display_payload_handle_native(handle, lod) {
Ok(payload) => {
let mut solid = solid_display_from_payload(name, payload);
solid.source_handle = handle;
solid.is_sheet_metal = brep_kernel::is_sheet_metal_handle(handle);
snapshot.push((name.clone(), handle, Some(solid)));
next_sent.insert(name.clone(), handle);
}
Err(error) => report.display_errors.push(format!("{name}: {error}")),
}
}
self.last_sent = next_sent;
let provenance: Vec<(String, String)> = layout
.creators
.iter()
.map(|(name, id)| (name.clone(), id.clone()))
.collect();
let entity_origin: Vec<(String, String)> = layout
.entity_origin
.iter()
.map(|(name, id)| (name.clone(), id.clone()))
.collect();
let imported_colors: Vec<(String, String)> = serde_json::from_str::<serde_json::Value>(
&brep_kernel::scene_metadata_colors_json(),
)
.ok()
.and_then(|value| value.as_object().cloned())
.map(|map| {
map.into_iter()
.filter(|(name, _)| {
layout.handles.contains_key(name) || layout.entity_origin.contains_key(name)
})
.filter_map(|(name, hex)| hex.as_str().map(|hex| (name, hex.to_string())))
.collect()
})
.unwrap_or_default();
RunOutput {
snapshot,
report,
provenance,
entity_origin,
assembly_poses,
assembly_fixed,
moved_solids,
imported_colors,
consumed,
assembly,
}
}
pub fn handle_of(&self, name: &str) -> Option<u32> {
self.last_sent.get(name).copied()
}
}
impl Default for SceneRunner {
fn default() -> Self {
Self::new()
}
}
pub fn update_scene_from_history(
scene: &mut RenderScene,
request: &HistoryRequest,
) -> Result<SceneBuildReport, String> {
let mut runner = SceneRunner::new();
runner.last_sent = scene
.solids()
.iter()
.map(|solid| (solid.name.clone(), solid.source_handle))
.collect();
let output = runner.run(request);
let mut kept: HashMap<String, SolidDisplay> = scene
.drain()
.into_iter()
.map(|solid| (solid.name.clone(), solid))
.collect();
for (name, _handle, maybe) in output.snapshot {
match maybe {
Some(display) => scene.insert_solid(display),
None => scene.insert_solid(
kept.remove(&name).expect("keep target present"),
),
}
}
Ok(output.report)
}
pub fn resident_solid_handles(request: &HistoryRequest) -> Vec<(String, u32)> {
let _trace = crate::run_trace::span("export_resident_handles");
let result = execute_history(request);
let mut report = SceneBuildReport::default();
let layout = fold_history(&result, &mut report);
layout
.order
.iter()
.map(|name| (name.clone(), layout.handles[name]))
.collect()
}