* WIP deep rename (not for push) * WIP2 * WIP3 * WIP4 wxs fix * rustfmt * Rename the remaining identifiers in merged crash-report code Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
81 lines
3.6 KiB
Rust
81 lines
3.6 KiB
Rust
//! Recorded ownership changes are evaluated at their timeline position.
|
|
use crate::{DocError, Document, Mat, ModelBody, ModelState, Result};
|
|
use solidcraft_geom::Vec3;
|
|
use std::collections::BTreeSet;
|
|
|
|
pub fn valid_transfer_matrix(m: &Mat) -> bool {
|
|
let axis = |i: usize| Vec3::new(m[i][0], m[i][1], m[i][2]);
|
|
let (x, y, z) = (axis(0), axis(1), axis(2));
|
|
m.iter().flatten().all(|v| v.is_finite())
|
|
&& m[3][..3].iter().all(|v| v.abs() <= 1e9)
|
|
&& [x, y, z].iter().all(|v| (v.dot(*v) - 1.0).abs() < 1e-6)
|
|
&& x.dot(y).abs() < 1e-6
|
|
&& x.dot(z).abs() < 1e-6
|
|
&& y.dot(z).abs() < 1e-6
|
|
&& x.cross(y).dot(z) > 0.0
|
|
&& [m[0][3], m[1][3], m[2][3]].iter().all(|v| v.abs() < 1e-9)
|
|
&& (m[3][3] - 1.0).abs() < 1e-9
|
|
}
|
|
|
|
/// Validate the stored boundary before allocation, loading, or evaluation.
|
|
pub fn validate_body_transfer(doc: &Document, bodies: &[String], from: u64, to: u64, transform: &Mat) -> Result<()> {
|
|
if bodies.is_empty()
|
|
|| bodies.len() > 1000
|
|
|| bodies.iter().any(|name| name.trim().is_empty() || name.len() > 512 || name.chars().any(char::is_control))
|
|
|| bodies.iter().collect::<BTreeSet<_>>().len() != bodies.len()
|
|
|| !valid_transfer_matrix(transform)
|
|
|| [from, to].iter().any(|id| *id != 0 && !doc.components.iter().any(|c| c.id == *id))
|
|
{
|
|
return Err(DocError::Invalid("invalid body transfer boundary".into()));
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn evaluate(doc: &Document, st: &mut ModelState, bodies: &[String], from: u64, to: u64, transform: Mat) -> Result<()> {
|
|
validate_body_transfer(doc, bodies, from, to, &transform)?;
|
|
let mut next = st.clone();
|
|
for name in bodies {
|
|
let old = st.body(name).ok_or_else(|| DocError::Unknown(format!("transferred body `{name}`")))?;
|
|
if doc.model_body_component(old) != from {
|
|
return Err(DocError::Invalid(format!("body `{name}` no longer belongs to transfer source component {from}")));
|
|
}
|
|
let shape = if crate::is_identity(&transform) { old.body.clone() } else { solidcraft_kernel::transform_matrix(&old.body, transform)? };
|
|
let mut body = ModelBody::new(old.name.clone(), shape, old.feature);
|
|
body.component = to;
|
|
body.transferred = true;
|
|
body.source_name = old.source_name.clone();
|
|
body.source_transform = crate::mat_mul(&transform, &old.source_transform);
|
|
body.imported = old.imported.clone();
|
|
body.names = crate::naming::NameCell::same(old);
|
|
let slot = next.bodies.iter_mut().find(|b| b.name == *name).ok_or_else(|| DocError::Unknown(format!("transferred body `{name}`")))?;
|
|
*slot = body;
|
|
}
|
|
crate::body_transfer_sheet::remap_sheet_metadata(&mut next, bodies, &transform)?;
|
|
*st = next;
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn invalid_transfer_constructor_preserves_document_and_allocator() {
|
|
let mut doc = Document::default();
|
|
let before = doc.to_json();
|
|
let mut scaled = crate::IDENTITY;
|
|
scaled[0][0] = 2.0;
|
|
for (bodies, from, transform) in [
|
|
(vec!["Block".into()], 42, crate::IDENTITY),
|
|
(vec!["Block".into(), "Block".into()], 0, crate::IDENTITY),
|
|
(vec![String::new()], 0, crate::IDENTITY),
|
|
(vec![" ".into()], 0, crate::IDENTITY),
|
|
(vec!["bad\nname".into()], 0, crate::IDENTITY),
|
|
(vec!["Block".into()], 0, scaled),
|
|
] {
|
|
assert!(doc.add_feature(crate::FeatureKind::BodyTransfer { bodies, from, to: 0, transform }, None).is_err());
|
|
assert_eq!(doc.to_json(), before);
|
|
}
|
|
}
|
|
}
|