Joint drives say when they move nothing; drive values in degrees; quiet replies (#1304)

joint.drive answers moved, and a reason (with unconnected) when the joint can move nothing; motion.play answers moved and unmoved; joint.list answers floating chains. joint.drive values come back in joint.list's shape (deg/mm). quiet: true trims joint answers and motion.play poses. A single value keeps a joint's other values. Drive Joints shows the floating case (drive.unconnected, all locales).
This commit is contained in:
Mateusz Sych authored and GitHub committed 2026-10-15 02:12:58 -04:00
1 parent fd19efc6fc
commit 7195e323bf
15 files changed
+326 -79

No files matched your search

+13 -9
View File
@@ -550,7 +550,7 @@ pub fn rows(app: &mut SolveApp, ui: &mut egui::Ui, k: &mut Asm, inputs: &mut [Se
}
// The preview drove the joint: where it stopped short, and why (a closed loop or a
// contact set), so the values asked for are not taken for where it is.
if let Some(why) = app.preview.stopped.as_ref().and_then(|r| drive_stop(&app.session, r)) {
if let Some(why) = app.preview.stopped.as_ref().and_then(drive_stop) {
row_label(ui, "");
ui.label(RichText::new(why).color(t.warning));
ui.end_row();
@@ -844,7 +844,9 @@ pub fn world_preview(s: &Session, cmds: &[(String, Value)], colors: Colors) -> O
let stopped = cmds
.iter()
.zip(&preview.replies)
.find(|((c, _), r)| c == "joint.drive" && (r["stopped_by_loop"].is_object() || r["stopped_by_contact"].is_object()))
.find(|((c, _), r)| {
c == "joint.drive" && (r["stopped_by_loop"].is_object() || r["stopped_by_contact"].is_object() || r["unconnected"].is_string())
})
.map(|(_, r)| r.clone());
let (before, after) = (preview.before, preview.after);
// A moved component looks like itself (no new surface), with its old place as the ghost.
@@ -861,18 +863,20 @@ pub fn world_preview(s: &Session, cmds: &[(String, Value)], colors: Colors) -> O
}
/// What a drive that stopped short of the asked values says: where the joint got to and why
/// (`None`: it went all the way). `reply` is `joint.drive`'s result.
pub fn drive_stop(s: &Session, reply: &Value) -> Option<String> {
let id = reply.get("joint").and_then(Value::as_u64)?;
let j = s.doc.assembly.joints.iter().find(|j| j.id == id)?;
/// (`None`: it went all the way). `reply` is `joint.drive`'s result. A drive whose joint is
/// joined to nothing grounded moves nothing, and says which part that is.
pub fn drive_stop(reply: &Value) -> Option<String> {
if let Some(part) = reply.get("unconnected").and_then(Value::as_str) {
return Some(crate::i18n::format("drive.unconnected", &[("part", part)]));
}
// The values come as joint.list gives them: degrees and millimetres.
let at: Vec<String> = reply
.get("values")
.and_then(Value::as_array)
.into_iter()
.flatten()
.enumerate()
.filter_map(|(i, v)| v.as_f64().map(|v| (i, v)))
.map(|(i, v)| if j.kind.is_angle(i) { num((v.to_degrees() * 10.0).round() / 10.0, "deg") } else { num((v * 100.0).round() / 100.0, "mm") })
.filter_map(|v| Some((v.get("value")?.as_f64()?, v.get("unit")?.as_str()?)))
.map(|(v, unit)| if unit == "deg" { num((v * 10.0).round() / 10.0, "deg") } else { num((v * 100.0).round() / 100.0, "mm") })
.collect();
let at = at.join(", ");
let name = |v: &Value| v.as_str().unwrap_or_default().to_string();