Motion analysis: scripting handles, depth wording, one hit per member pair a sample

This commit is contained in:
MateuszSychXpl committed 2026-10-16 17:52:39 +02:00
1 parent 8fdf6a61a2
commit f5871608f4
5 files changed
+27 -12

No files matched your search

+8 -3
View File
@@ -228,10 +228,11 @@ impl ContactGeometry {
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`.
/// The pairs of set members whose bodies pass through each other in `s`, with how deep the
/// first two bodies found go (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 mut out: Vec<(Member, Member, f64)> = Vec::new();
let meshes: Vec<Mesh> = self
.bodies
.iter()
@@ -242,6 +243,10 @@ impl ContactGeometry {
})
.collect::<Result<_>>()?;
for (a, b, oa, ob) in &self.pairs {
// Members already found colliding need no more of their bodies tested.
if out.iter().any(|(x, y, _)| (x.0, y.0) == (oa.0, ob.0) || (x.0, y.0) == (ob.0, oa.0)) {
continue;
}
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"));
};
+2 -1
View File
@@ -31,7 +31,8 @@ pub const INTERFERENCE_DOC: &str = "study: id|name; occurrences?: [ids or names]
default every occurrence), with against?: [ids or names] (then each of `occurrences` is checked only against these, such \
as an arm against the parts it must miss), or contact_sets?: true (the pairs of the design's contact sets); samples? \
(2…1000, default the study's steps + 1) → collisions: [{a, b, first_step, last_step, steps: [[from, to]…] (the step ranges where they overlap), \
depth_mm (the deepest), deepest_step, volume_mm3 (their overlap there; null when it could not be worked out)}], clear, \
depth_mm (how far into each other, the most at deepest_step), volume_mm3 (their overlap there; null when it was not \
cheap to work out)}], clear, \
pairs (checked), samples (taken), truncated (the work budget ran out; checked_to: the last step checked), stopped_at, \
stopped_by_loop. The study plays as motion.play plays it but contact sets do not stop it, on a copy (the design is not \
changed). Parts are tested at the samples only: one that passes wholly through another between two samples is missed";
+12 -4
View File
@@ -163,6 +163,9 @@ fn chart(ui: &mut egui::Ui, xs: &[f64], ys: &[f64], unit: &str, total: u32, now:
p.text(r.right_top() + vec2(-4.0, 2.0), Align2::RIGHT_TOP, format!("{} {unit}", short(v)), font, t.text);
}
}
// Where steps 0 and `total` fall, for a scenario or an agent to click between.
crate::scenario::publish_handle("motion:chart_start", pos2(x(0.0), plot.center().y));
crate::scenario::publish_handle("motion:chart_end", pos2(x(total), plot.center().y));
let cx = x(now);
p.line_segment([pos2(cx, r.top()), pos2(cx, r.bottom())], Stroke::new(1.5, t.warning));
let pos = resp.interact_pointer_pos().filter(|_| resp.clicked() || resp.dragged())?;
@@ -238,7 +241,8 @@ pub fn rows(
combo(ui, "ms_quantity", &QUANTITIES, &mut a.quantity);
ui.end_row();
row_label(ui, "Duration");
ui.add(egui::TextEdit::singleline(&mut a.duration).hint_text(crate::i18n::tr("motion.duration_hint")));
let r = ui.add(egui::TextEdit::singleline(&mut a.duration).hint_text(crate::i18n::tr("motion.duration_hint")));
crate::scenario::publish_handle("dialog:Duration", r.rect.center());
ui.end_row();
let params = study.clone().and_then(|st| plot_params(s, what, &st, &a.duration, inputs).map(|p| (st, p)));
let key = key_of(&params);
@@ -295,7 +299,9 @@ pub fn rows(
if sets && parts.is_empty() {
combo(ui, "ms_among", &["All components", "Contact sets"], &mut a.among);
}
if ui.add_enabled(check.is_some(), egui::Button::new(crate::i18n::tr_label("Check collisions"))).clicked()
let button = ui.add_enabled(check.is_some(), egui::Button::new(crate::i18n::tr_label("Check collisions")));
crate::scenario::publish_handle("motion:check", button.rect.center());
if button.clicked()
&& let Some((st, mut p)) = check
{
let mut sc = app.session.scratch();
@@ -313,7 +319,7 @@ pub fn rows(
if hits.is_empty() {
ui.label(RichText::new(crate::i18n::tr("motion.no_collisions")).color(t.text_dim));
}
for c in &hits {
for (i, c) in hits.iter().enumerate() {
let steps: Vec<String> = c["steps"]
.as_array()
.into_iter()
@@ -326,7 +332,9 @@ pub fn rows(
let name = |k: &str| c[k].as_str().unwrap_or_default().to_string();
let text = crate::i18n::format("motion.collision", &[("a", &name("a")), ("b", &name("b")), ("steps", &steps.join(", "))]);
// A click shows where they first collide.
if ui.link(RichText::new(text).color(t.error)).clicked()
let link = ui.link(RichText::new(text).color(t.error));
crate::scenario::publish_handle(&format!("motion:collision:{i}"), link.rect.center());
if link.clicked()
&& let Some(first) = c["first_step"].as_f64()
{
(*step, *playing) = (first, false);