Files
cadcraft/crates/engine/tests/grip_selection.rs
T
Valentin KleinandClaude Opus 5.5 0fef879509 Grip edits act on the whole selection (fixes #320) (#322)
Stretching a grip now drags the grips of the other selected objects that sit
on the same point, so connected lines stay connected, and the Move, Rotate,
Scale and Mirror grip modes transform every selected object about the hot
grip, as in AutoCAD. grip.move/grip.stretch take the other objects in
`handles`; with only `handle` they edit that object alone, as before.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 16:54:48 -04:00

61 lines
2.4 KiB
Rust

//! Grip editing acts on the whole selection, as in AutoCAD: a stretched grip drags the
//! coincident grips of the other selected objects with it, and Move/Rotate/Scale/Mirror
//! transform every selected object.
use cadcraft_engine::Session;
use cadcraft_engine::doc::{EntityKind, Handle};
use cadcraft_engine::geom::Vec2;
use cadcraft_engine::grips::GripMode;
use serde_json::json;
fn line(s: &mut Session, a: [f64; 2], b: [f64; 2]) -> Handle {
let r = s.execute("line", &json!({ "points": [a, b] })).unwrap();
Handle::parse_hex(r["handles"][0].as_str().unwrap()).unwrap()
}
fn ends(s: &Session, h: Handle) -> (Vec2, Vec2) {
match &s.doc().unwrap().entity(h).unwrap().kind {
EntityKind::Line(l) => (l.a.xy(), l.b.xy()),
k => panic!("not a line: {k:?}"),
}
}
fn near(p: Vec2, x: f64, y: f64) -> bool {
p.near(Vec2::new(x, y), 1e-9)
}
#[test]
fn grip_edits_act_on_the_selection() {
// Two connected lines and a third one elsewhere, all selected.
let mut s = Session::new();
let a = line(&mut s, [0.0, 0.0], [10.0, 0.0]);
let b = line(&mut s, [10.0, 0.0], [10.0, 10.0]);
let c = line(&mut s, [20.0, 0.0], [30.0, 0.0]);
s.set_selection(vec![a, b, c]);
let undo = s.state().unwrap().undo.len();
// Stretching the shared corner grip of `a` moves `b`'s start with it; `c` stays.
s.grip_edit(a, 2, Vec2::new(12.0, 3.0), GripMode::Stretch).unwrap();
assert!(near(ends(&s, a).1, 12.0, 3.0));
assert!(near(ends(&s, b).0, 12.0, 3.0) && near(ends(&s, b).1, 10.0, 10.0), "{:?}", ends(&s, b));
assert!(near(ends(&s, c).0, 20.0, 0.0) && near(ends(&s, c).1, 30.0, 0.0));
assert_eq!(s.state().unwrap().undo.len(), undo + 1, "one undo step");
// Move about a grip of `a`: every selected object moves.
s.grip_edit(a, 0, Vec2::new(1.0, 1.0), GripMode::Move).unwrap();
assert!(near(ends(&s, a).0, 1.0, 1.0));
assert!(near(ends(&s, b).1, 11.0, 11.0));
assert!(near(ends(&s, c).0, 21.0, 1.0));
// Rotate 90° about the same grip: `c` turns too.
s.grip_edit(a, 0, Vec2::new(1.0, 5.0), GripMode::Rotate).unwrap();
assert!(near(ends(&s, c).0, 1.0, 21.0), "{:?}", ends(&s, c));
// An object that isn't selected is only edited through its own grip.
s.set_selection(vec![a]);
let before = ends(&s, b);
let g = s.grips_of(a).unwrap()[2];
s.grip_edit(a, 2, g + Vec2::new(1.0, 0.0), GripMode::Stretch).unwrap();
assert_eq!(ends(&s, b), before);
}