Keep New Body and Offset Face previews moving during distance drags (#1238)
New Body extrusions and Offset Face previews show successful intermediate distances from the same setup while a distance drag runs, then compute the latest one (tool previews keep streaming as before; errors still need exact inputs). The drag arrow and value box are painted from this frame's pointer, removing a frame of lag. Drag regression tests and a perf note added.
This commit is contained in:
1 parent
29e72806f6
commit
a18bedddf6
6 files changed
+294
-23
No files matched your search
@@ -0,0 +1,149 @@
|
||||
//! Completed distances must reach the viewport before a continuously moving pointer stops.
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
fn colors() -> Colors {
|
||||
Colors { body: [1, 1, 1, 255], added: [2, 2, 2, 255], removed: [3, 3, 3, 100], edge: [0, 0, 0, 255] }
|
||||
}
|
||||
|
||||
fn fixture() -> Session {
|
||||
let mut s = Session::new(solidcraft_engine::doc::Document::new("Preview drag"));
|
||||
s.execute("sketch.create", &json!({"plane":"XY", "name":"Profile"})).unwrap();
|
||||
s.execute("sketch.rectangle.two_point", &json!({"p0":[0,0], "p1":[10,10]})).unwrap();
|
||||
s.execute("sketch.finish", &json!({})).unwrap();
|
||||
s
|
||||
}
|
||||
|
||||
fn extrude(distance: i32) -> Vec<(String, Value)> {
|
||||
vec![("solid.extrude".into(), json!({"sketch":"Profile", "distance":distance, "operation":"new"}))]
|
||||
}
|
||||
|
||||
fn check_drag(s: &Session, commands: impl Fn(i32) -> Vec<(String, Value)>, base_z: f64) {
|
||||
let original = serde_json::to_value(&*s.doc).unwrap();
|
||||
let undo = s.undo.len();
|
||||
let mut pv = PreviewState::default();
|
||||
for (finished, wanted) in [(10, 20), (20, 30), (30, 30)] {
|
||||
let old = commands(finished);
|
||||
let new = commands(wanted);
|
||||
let old_key = job_key(&old, s.revision, colors());
|
||||
let key = job_key(&new, s.revision, colors());
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
pv.running = Some((old_key, rx));
|
||||
pv.running_tool = as_new_body(&old).is_some();
|
||||
pv.running_stream_key = stream_key(&old, s.revision, colors());
|
||||
let built = compute_tool(s, &old, colors()).unwrap();
|
||||
tx.send(Ok(built)).unwrap();
|
||||
pv.poll(key, stream_key(&new, s.revision, colors()));
|
||||
assert!(pv.active && pv.error.is_none(), "completed geometry must show during the drag: {:?}", pv.error);
|
||||
assert!((pv.bounds.max.z - base_z - f64::from(finished)).abs() < 1e-8);
|
||||
assert_eq!(pv.shown_key, Some(old_key));
|
||||
assert_eq!(pv.shown_key == Some(key), finished == wanted, "only the final distance satisfies the latest request");
|
||||
assert!(pv.running.is_none());
|
||||
}
|
||||
assert_eq!(serde_json::to_value(&*s.doc).unwrap(), original);
|
||||
assert_eq!(s.undo.len(), undo);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_body_extrude_keeps_moving_during_a_drag() {
|
||||
check_drag(&fixture(), extrude, 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn edited_new_body_extrude_keeps_moving_during_a_drag() {
|
||||
let mut s = fixture();
|
||||
let feature = s.execute("solid.extrude", &extrude(5)[0].1).unwrap()["feature"].as_u64().unwrap();
|
||||
check_drag(
|
||||
&s,
|
||||
|distance| vec![("timeline.redefine".into(), json!({"feature":feature,"command":"solid.extrude","params":extrude(distance)[0].1}))],
|
||||
0.0,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn face_offset_keeps_moving_during_a_drag() {
|
||||
let mut s = Session::new(solidcraft_engine::doc::Document::new("Face offset drag"));
|
||||
s.execute("solid.box", &json!({"length":10,"width":10,"height":10})).unwrap();
|
||||
check_drag(&s, |distance| vec![("solid.offset_face".into(), json!({"faces":[[5,5,10]],"distance":distance}))], 10.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn join_tool_keeps_moving_without_waiting_for_its_boolean() {
|
||||
let mut s = fixture();
|
||||
s.execute("solid.box", &json!({"length":10,"width":10,"height":5})).unwrap();
|
||||
check_drag(&s, |distance| vec![("solid.extrude".into(), json!({"sketch":"Profile","distance":distance,"operation":"join"}))], 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn distance_stream_keeps_feature_selection_and_extent_context() {
|
||||
let old = extrude(10);
|
||||
let key = stream_key(&old, 7, colors());
|
||||
assert!(key.is_some());
|
||||
assert_eq!(key, stream_key(&extrude(20), 7, colors()));
|
||||
assert_ne!(key, stream_key(&old, 8, colors()));
|
||||
let mut recolored = colors();
|
||||
recolored.added = [40, 50, 60, 255];
|
||||
assert_ne!(key, stream_key(&old, 7, recolored));
|
||||
for (field, value) in [
|
||||
("sketch", json!("Other")),
|
||||
("profiles", json!([1])),
|
||||
("face", json!([0, 0, 5])),
|
||||
("operation", json!("cut")),
|
||||
("direction", json!("negative")),
|
||||
("distance2", json!(5)),
|
||||
("to", json!([0, 0, 50])),
|
||||
("taper", json!(5)),
|
||||
("start_offset", json!(3)),
|
||||
("participants", json!(["Other"])),
|
||||
] {
|
||||
let mut changed = old.clone();
|
||||
changed[0].1[field] = value;
|
||||
assert_ne!(key, stream_key(&changed, 7, colors()), "changed {field} cannot reuse the old result");
|
||||
}
|
||||
for field in ["distance2", "to_offset"] {
|
||||
let mut from = old.clone();
|
||||
from[0].1[field] = json!(5);
|
||||
let mut to = from.clone();
|
||||
to[0].1[field] = json!(9);
|
||||
assert_eq!(stream_key(&from, 7, colors()), stream_key(&to, 7, colors()));
|
||||
}
|
||||
let edit =
|
||||
|feature, distance| vec![("timeline.redefine".into(), json!({"feature":feature,"command":"solid.extrude","params":extrude(distance)[0].1}))];
|
||||
assert_eq!(stream_key(&edit(2, 10), 7, colors()), stream_key(&edit(2, 20), 7, colors()));
|
||||
assert_ne!(stream_key(&edit(2, 10), 7, colors()), stream_key(&edit(3, 20), 7, colors()));
|
||||
assert_ne!(key, stream_key(&edit(2, 10), 7, colors()));
|
||||
let offset = |distance| vec![("solid.offset_face".into(), json!({"faces":[[5,5,10]],"distance":distance}))];
|
||||
assert!(stream_key(&offset(1), 7, colors()).is_some());
|
||||
assert_eq!(stream_key(&offset(1), 7, colors()), stream_key(&offset(5), 7, colors()));
|
||||
assert!(stream_key(&[], 7, colors()).is_none());
|
||||
assert!(stream_key(&[("solid.fillet".into(), json!({"radius":5}))], 7, colors()).is_none());
|
||||
let mut mixed = old;
|
||||
mixed.push(("select.clear".into(), json!({})));
|
||||
assert!(stream_key(&mixed, 7, colors()).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn distance_results_reject_other_setups_and_obsolete_errors() {
|
||||
let s = fixture();
|
||||
let old = extrude(10);
|
||||
let current = extrude(20);
|
||||
for (revision, failed) in [(s.revision + 1, false), (s.revision, true)] {
|
||||
let mut built = compute_tool(&s, &old, colors()).unwrap();
|
||||
if failed {
|
||||
built.error = Some("old distance failed".into());
|
||||
}
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let mut pv = PreviewState {
|
||||
running: Some((job_key(&old, s.revision, colors()), rx)),
|
||||
running_stream_key: stream_key(&old, s.revision, colors()),
|
||||
..Default::default()
|
||||
};
|
||||
tx.send(Ok(built)).unwrap();
|
||||
pv.poll(job_key(¤t, revision, colors()), stream_key(¤t, revision, colors()));
|
||||
assert!(!pv.active && pv.error.is_none() && pv.shown_key.is_none());
|
||||
}
|
||||
let mut join = current.clone();
|
||||
join[0].1["operation"] = json!("join");
|
||||
assert!(as_new_body(&join).is_some());
|
||||
assert!(stream_key(&join, s.revision, colors()).is_none(), "tool previews retain their upstream streaming path");
|
||||
}
|
||||
Reference in new issue
Block a user