M8: points moved by one offset still carry the regions' paint frames

Setting each point's own coordinate moved the points one at a time, so when every point of a layer moved by the same offset (`{"x": {"add": 10}}` on all of them) `ops::move_vertices` no longer saw the whole network move and left the regions' paint frames behind. Points with the same offset now move together.

Verified: `moving_every_point_by_one_offset_carries_the_regions_paint_frame` failed before this and passes; vectoredit tests 92 passed, junk sweep 49 passed, clippy and fmt clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Model-Effort: Opus 5.5 drove and wrote
This commit is contained in:
Trevor Brown
2026-11-08 07:57:54 -05:00
co-authored by Claude Opus 5.5
parent 767ebb67cc
commit 8edcc02aee
2 changed files with 37 additions and 2 deletions
+11 -2
View File
@@ -1017,9 +1017,18 @@ pub fn specs() -> Vec<CommandSpec> {
if moves.is_empty() {
return Err(bad("vector.edit.position", "select points"));
}
// Points with the same offset move together, so moving every point by one offset
// still carries the regions' paint frames, as a drag of all of them does.
let mut groups: Vec<(Vec2, Vec<usize>)> = Vec::new();
for (i, d) in moves {
match groups.iter_mut().find(|(g, _)| (*g - d).hypot() < 1e-9) {
Some((_, v)) => v.push(i),
None => groups.push((d, vec![i])),
}
}
edit(s.doc_mut()?, ve.id, |net| {
for (i, d) in &moves {
ops::move_vertices(net, &[*i], *d);
for (d, v) in &groups {
ops::move_vertices(net, v, *d);
}
})?;
Ok(json!({"ok": true}))
@@ -100,6 +100,32 @@ fn the_values_are_in_the_parents_coordinates() {
assert!(near(after[0].1, before[0].1));
}
#[test]
fn moving_every_point_by_one_offset_carries_the_regions_paint_frame() {
// `{add}` on every point is a plain translation: the region's paint frame goes with the
// points, as when they are dragged together.
let mut s = Session::new();
let id = rect(&mut s);
{
let node = s.doc_mut().unwrap().get_mut(id).unwrap();
let NodeKind::Vector { network } = &mut node.kind else { panic!("expected a vector") };
assert!(!network.regions.is_empty(), "the rectangle has a region");
for region in &mut network.regions {
region.paint_space = Some(figcraft_doc::RegionPaintSpace { width: 100.0, height: 100.0, transform: Affine::IDENTITY });
}
}
let frame = |s: &Session| {
let world = s.doc().unwrap().world_transform(id);
world * net(s, id).regions[0].paint_space.as_ref().unwrap().transform
};
let before = frame(&s);
let all: Vec<usize> = (0..net(&s, id).vertices.len()).collect();
ex(&mut s, "vector.edit.select", json!({"vertices": all}));
ex(&mut s, "vector.edit.position", json!({"x": {"add": 10}}));
let (want, got) = ((Affine::translate((10.0, 0.0)) * before).as_coeffs(), frame(&s).as_coeffs());
assert!(want.iter().zip(got).all(|(a, b)| near(*a, b)), "{got:?} vs {want:?}");
}
#[test]
fn junk_is_refused_and_changes_nothing() {
let mut s = Session::new();