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:
1 parent
fd19efc6fc
commit
7195e323bf
15 files changed
+326
-79
No files matched your search
@@ -3181,7 +3181,7 @@ pub(crate) fn run_dialog(app: &mut SolveApp, d: &Dialog) -> Result<(), String> {
|
||||
Ok(reply) => {
|
||||
// A drive that stopped short says where, and why.
|
||||
if cmd == "joint.drive"
|
||||
&& let Some(why) = crate::dialogs_assembly::drive_stop(&app.session, &reply)
|
||||
&& let Some(why) = crate::dialogs_assembly::drive_stop(&reply)
|
||||
{
|
||||
app.set_status(why, false);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -415,7 +415,7 @@ fn drive_says_where_a_loop_stopped_it() {
|
||||
// Past its toggle position (about +50.4° from where it stands).
|
||||
ask(&mut d, "100 deg");
|
||||
let b = world_preview(&app.session, &apply_commands(&app, &d).unwrap(), colors).unwrap().unwrap();
|
||||
let why = b.stopped.as_ref().and_then(|r| drive_stop(&app.session, r)).unwrap();
|
||||
let why = b.stopped.as_ref().and_then(drive_stop).unwrap();
|
||||
assert!(why.starts_with("Stopped at 50.") && why.contains("can't close any further"), "{why}");
|
||||
// OK drives it there and says so.
|
||||
crate::dialogs::run_dialog(&mut app, &d).unwrap();
|
||||
@@ -431,6 +431,32 @@ fn drive_says_which_parts_a_contact_set_stopped() {
|
||||
app.run("joint.create", json!({"type": "slider", "a": at("A"), "b": at("B"), "flip": true, "name": "Rail"})).unwrap();
|
||||
app.run("contact.create", json!({"occurrences": ["A:1", "B:1"]})).unwrap();
|
||||
let r = app.run("joint.drive", json!({"joint": "Rail", "value": -20})).unwrap();
|
||||
let why = drive_stop(&app.session, &r).unwrap();
|
||||
let why = drive_stop(&r).unwrap();
|
||||
assert!(why.starts_with("Stopped at -1") && why.contains("A:1") && why.contains("B:1") && why.contains("would pass through"), "{why}");
|
||||
}
|
||||
|
||||
/// A drive whose joint is joined to nothing grounded moves nothing: the preview row and the
|
||||
/// status line name the part that floats.
|
||||
#[test]
|
||||
fn drive_says_when_its_joint_is_joined_to_nothing_grounded() {
|
||||
let mut app = two_boxes();
|
||||
for (name, x) in [("C", 100), ("D", 130)] {
|
||||
app.run("component.activate", json!({"component": "root"})).unwrap();
|
||||
app.run("component.create", json!({ "name": name })).unwrap();
|
||||
app.run("solid.box", json!({"length": 20, "width": 20, "height": 10, "corner": [x, 0, 0]})).unwrap();
|
||||
}
|
||||
let at = |o: &str, x: f64| json!({"occurrence": o, "point": [x, 10, 5], "z": [1, 0, 0]});
|
||||
app.run("joint.create", json!({"type": "slider", "a": at("A", 30.0), "b": at("B", 30.0), "flip": true, "name": "Rail"})).unwrap();
|
||||
app.run("joint.create", json!({"type": "revolute", "a": at("C", 130.0), "b": at("D", 130.0), "flip": true, "name": "Turn"})).unwrap();
|
||||
let turn = app.session.doc.assembly.joints.iter().find(|j| j.name == "Turn").unwrap().id;
|
||||
let mut d = drive_joint(&app, turn).unwrap();
|
||||
if let Kind::Assembly(Asm::Drive { values, .. }) = &mut d.kind {
|
||||
values[0] = "45 deg".into();
|
||||
}
|
||||
let colors = crate::preview::Colors { body: [1, 1, 1, 255], added: [2, 2, 2, 255], removed: [3, 3, 3, 100], edge: [0, 0, 0, 255] };
|
||||
let b = world_preview(&app.session, &apply_commands(&app, &d).unwrap(), colors).unwrap().unwrap();
|
||||
let why = b.stopped.as_ref().and_then(drive_stop).unwrap();
|
||||
assert!(why.starts_with("Moves nothing") && why.contains("C:1"), "{why}");
|
||||
crate::dialogs::run_dialog(&mut app, &d).unwrap();
|
||||
assert_eq!(app.status.clone().map(|(status, _, error)| (status, error)), Some((why, false)));
|
||||
}
|
||||
Reference in new issue
Block a user