Motion analysis: plots of a study's joint values and a point's path, and the parts that collide along it
- motion.plot: joint values and a point's path sampled as the study plays (closed loops following), with velocities and accelerations by finite differences, per step or per second with a duration. - motion.interference: the pairs of parts that pass through each other along a study, with the first step, the step ranges, the depth and the shared volume; occurrences, against or contact sets choose the pairs; bounded samples and work. - Motion Study dialog: a Study box opens a saved study; a chart of a joint value or a picked point with a cursor at the step shown (click to move there); Check collisions with the steps shaded on the chart. ui.start motion.study takes study.
This commit is contained in:
1 parent
8a055afdd6
commit
8fdf6a61a2
23 files changed
+1398
-40
No files matched your search
@@ -27,9 +27,13 @@ struct CachedBody {
|
||||
size: f64,
|
||||
}
|
||||
|
||||
/// A member of a contact set: (occurrence id, name).
|
||||
pub(super) type Member = (u64, String);
|
||||
|
||||
pub(super) struct ContactGeometry {
|
||||
bodies: Vec<CachedBody>,
|
||||
pairs: Vec<(usize, usize, String, String)>,
|
||||
/// Body indices, and the set members (occurrence id, name) they are under.
|
||||
pairs: Vec<(usize, usize, Member, Member)>,
|
||||
step: f64,
|
||||
}
|
||||
|
||||
@@ -41,6 +45,12 @@ impl ContactGeometry {
|
||||
} else {
|
||||
assembly.contact_sets.iter().filter(|set| !set.suppressed).map(|set| set.occurrences.clone()).collect()
|
||||
};
|
||||
Self::of_sets(s, &sets)
|
||||
}
|
||||
|
||||
/// The geometry of the bodies under `sets` (occurrence ids), paired as contact sets pair
|
||||
/// them: bodies under different members of one set.
|
||||
pub(super) fn of_sets(s: &Session, sets: &[Vec<u64>]) -> Result<Self> {
|
||||
let mut out = Self { bodies: Vec::new(), pairs: Vec::new(), step: f64::INFINITY };
|
||||
if sets.is_empty() {
|
||||
return Ok(out);
|
||||
@@ -120,7 +130,7 @@ impl ContactGeometry {
|
||||
}
|
||||
}
|
||||
let mut pairs = BTreeSet::new();
|
||||
for set in &sets {
|
||||
for set in sets {
|
||||
let tagged: Vec<(usize, u64)> =
|
||||
out.bodies.iter().enumerate().filter_map(|(i, b)| b.path.iter().rev().find(|id| set.contains(id)).map(|id| (i, *id))).collect();
|
||||
for (i, (a, oa)) in tagged.iter().enumerate() {
|
||||
@@ -132,7 +142,7 @@ impl ContactGeometry {
|
||||
}
|
||||
}
|
||||
let name = |id| s.doc.occurrences.iter().find(|o| o.id == id).map(|o| o.name.clone()).unwrap_or_default();
|
||||
out.pairs = pairs.into_iter().map(|(a, b, oa, ob)| (a, b, name(oa), name(ob))).collect();
|
||||
out.pairs = pairs.into_iter().map(|(a, b, oa, ob)| (a, b, (oa, name(oa)), (ob, name(ob)))).collect();
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
@@ -140,6 +150,16 @@ impl ContactGeometry {
|
||||
!self.pairs.is_empty()
|
||||
}
|
||||
|
||||
/// Keeps only the pairs of set members (occurrence ids) `keep` takes.
|
||||
pub(super) fn keep_pairs(&mut self, keep: impl Fn(u64, u64) -> bool) {
|
||||
self.pairs.retain(|(_, _, a, b)| keep(a.0, b.0));
|
||||
}
|
||||
|
||||
/// How many pairs of set members are checked against each other.
|
||||
pub(super) fn pair_count(&self) -> usize {
|
||||
self.pairs.iter().map(|(_, _, a, b)| (a.0, b.0)).collect::<BTreeSet<_>>().len()
|
||||
}
|
||||
|
||||
fn frame(body: &CachedBody, s: &Session) -> Result<Mat> {
|
||||
let mut frame = IDENTITY;
|
||||
for id in &body.path {
|
||||
@@ -205,6 +225,13 @@ impl ContactGeometry {
|
||||
}
|
||||
|
||||
pub(super) fn first_hit(&self, s: &Session, work: &mut usize) -> Result<Option<(String, String, f64)>> {
|
||||
Ok(self.hits(s, work, MAX_WORK, false)?.into_iter().next().map(|(a, b, depth)| (a.1, b.1, depth)))
|
||||
}
|
||||
|
||||
/// The pairs that pass through each other in `s`, with how deep (every pair, or the first
|
||||
/// found when `all` is false). `work` adds up the cost, refused past `budget`.
|
||||
pub(super) fn hits(&self, s: &Session, work: &mut usize, budget: usize, all: bool) -> Result<Vec<(Member, Member, f64)>> {
|
||||
let mut out = Vec::new();
|
||||
let meshes: Vec<Mesh> = self
|
||||
.bodies
|
||||
.iter()
|
||||
@@ -214,7 +241,7 @@ impl ContactGeometry {
|
||||
Ok(Mesh { positions, triangles: body.mesh.triangles.clone(), ..Mesh::default() })
|
||||
})
|
||||
.collect::<Result<_>>()?;
|
||||
for (a, b, aname, bname) in &self.pairs {
|
||||
for (a, b, oa, ob) in &self.pairs {
|
||||
let (Some(ma), Some(mb), Some(ba), Some(bb)) = (meshes.get(*a), meshes.get(*b), self.bodies.get(*a), self.bodies.get(*b)) else {
|
||||
return Err(limit("invalid contact geometry pair"));
|
||||
};
|
||||
@@ -235,14 +262,17 @@ impl ContactGeometry {
|
||||
.saturating_mul(mb.triangles.len())
|
||||
.saturating_add(mb.positions.len().saturating_add(mb.triangles.len().saturating_mul(4)).saturating_mul(ma.triangles.len()));
|
||||
*work = work.saturating_add(cost);
|
||||
if *work > MAX_WORK {
|
||||
if *work > budget {
|
||||
return Err(limit("motion exceeds contact sweep collision budget; simplify the contact set or use smaller increments"));
|
||||
}
|
||||
if let Some(depth) = super::mesh_overlap(ma, mb, 1e-4 * ba.size.max(bb.size).max(1.0)) {
|
||||
return Ok(Some((aname.clone(), bname.clone(), depth)));
|
||||
out.push((oa.clone(), ob.clone(), depth));
|
||||
if !all {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user