mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-08 04:50:18 +00:00
M2.1: recurse into groups and compound paths for path.average
This commit is contained in:
@@ -1001,8 +1001,21 @@ pub(super) fn anchor_targets(s: &Session) -> Result<AnchorTargets> {
|
||||
}
|
||||
|
||||
fn average(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let t = anchor_targets(s)?;
|
||||
let d0 = &s.doc()?.doc;
|
||||
let st = s.doc()?;
|
||||
let t: AnchorTargets = if st.selection.anchors.is_empty() {
|
||||
let editable = st.doc.editable();
|
||||
paths_in(&st.doc, &st.selection.objects)
|
||||
.into_iter()
|
||||
.filter(|id| editable(*id))
|
||||
.filter_map(|id| {
|
||||
let pd = st.doc.node(id)?.path_data()?;
|
||||
Some((id, pd.anchors().map(|(s, a, _)| (s, a)).collect()))
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
anchor_targets(s)?
|
||||
};
|
||||
let d0 = &st.doc;
|
||||
let pts: Vec<Point> = t
|
||||
.iter()
|
||||
.flat_map(|(id, v)| v.iter().filter_map(move |(si, ai)| d0.node(*id)?.path_data()?.subpaths.get(*si)?.anchors.get(*ai).map(|a| a.p)))
|
||||
|
||||
@@ -4165,3 +4165,86 @@ fn shape_counts_that_are_not_whole_numbers_are_errors() {
|
||||
assert!(s.execute("shape.spiral", &json!({"cx": 1, "cy": 1, "radius": 5, "segments": bad})).is_err(), "{bad}");
|
||||
}
|
||||
}
|
||||
|
||||
fn all_at(pts: &[Point], c: Point) -> bool {
|
||||
!pts.is_empty() && pts.iter().all(|p| (p.x - c.x).abs() < 1e-6 && (p.y - c.y).abs() < 1e-6)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn average_averages_anchors_in_selected_groups() {
|
||||
let mut s = session();
|
||||
let a = rect(&mut s, 0.0, 0.0, 100.0, 100.0);
|
||||
let b = rect(&mut s, 200.0, 200.0, 100.0, 100.0);
|
||||
s.execute("select.set", &json!({"ids": [a.0, b.0]})).unwrap();
|
||||
let g = s.execute("object.group", &json!({})).unwrap()["id"].as_u64().unwrap();
|
||||
s.execute("select.set", &json!({"ids": [g]})).unwrap();
|
||||
s.execute("path.average", &json!({"axis": "both"})).unwrap();
|
||||
let c = Point::new(150.0, 150.0);
|
||||
assert!(all_at(&anchor_points(&s, a), c), "{:?}", anchor_points(&s, a));
|
||||
assert!(all_at(&anchor_points(&s, b), c), "{:?}", anchor_points(&s, b));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn average_averages_anchors_in_a_selected_compound_path() {
|
||||
let mut s = session();
|
||||
let a = rect(&mut s, 0.0, 0.0, 100.0, 100.0);
|
||||
let b = rect(&mut s, 200.0, 200.0, 100.0, 100.0);
|
||||
s.execute("select.set", &json!({"ids": [a.0, b.0]})).unwrap();
|
||||
let cp = NodeId(s.execute("object.compoundPath.make", &json!({})).unwrap()["id"].as_u64().unwrap());
|
||||
s.execute("select.set", &json!({"ids": [cp.0]})).unwrap();
|
||||
s.execute("path.average", &json!({"axis": "both"})).unwrap();
|
||||
let st = s.doc().unwrap();
|
||||
let node = st.doc.node(cp).unwrap();
|
||||
assert!(matches!(node.kind, NodeKind::Compound { .. }), "{}", node.kind_label());
|
||||
let pts: Vec<Point> =
|
||||
node.children().unwrap().iter().flat_map(|c| c.path_data().unwrap().anchors().map(|(_, _, a)| a.p).collect::<Vec<_>>()).collect();
|
||||
assert_eq!(pts.len(), 8, "{pts:?}");
|
||||
assert!(all_at(&pts, Point::new(150.0, 150.0)), "{pts:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn average_with_nothing_selected_is_an_error() {
|
||||
let mut s = session();
|
||||
rect(&mut s, 0.0, 0.0, 100.0, 100.0);
|
||||
s.execute("select.set", &json!({"ids": []})).unwrap();
|
||||
assert!(s.execute("path.average", &json!({"axis": "both"})).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn average_leaves_sibling_geometry_unchanged_on_partial_selection() {
|
||||
let mut s = session();
|
||||
let a = rect(&mut s, 0.0, 0.0, 100.0, 100.0);
|
||||
let b = rect(&mut s, 200.0, 200.0, 100.0, 100.0);
|
||||
s.execute("select.set", &json!({"ids": [a.0, b.0]})).unwrap();
|
||||
let g = s.execute("object.group", &json!({})).unwrap()["id"].as_u64().unwrap();
|
||||
let orig_b = path(&s, b).clone();
|
||||
|
||||
// Select group, but add-select one anchor on descendant a
|
||||
s.execute("select.set", &json!({"ids": [g]})).unwrap();
|
||||
s.execute("select.anchors", &json!({"id": a.0, "anchors": [[0, 0]], "mode": "add"})).unwrap();
|
||||
|
||||
// Average should not move b's anchors
|
||||
s.execute("path.average", &json!({"axis": "both"})).unwrap();
|
||||
assert_eq!(path(&s, b), orig_b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn average_skips_locked_descendants_in_group() {
|
||||
let mut s = session();
|
||||
let a = rect(&mut s, 0.0, 0.0, 100.0, 100.0);
|
||||
let b = rect(&mut s, 200.0, 200.0, 100.0, 100.0);
|
||||
s.execute("select.set", &json!({"ids": [a.0, b.0]})).unwrap();
|
||||
let g = s.execute("object.group", &json!({})).unwrap()["id"].as_u64().unwrap();
|
||||
let orig_b = path(&s, b).clone();
|
||||
|
||||
// Lock descendant b
|
||||
s.execute("object.lock", &json!({"ids": [b.0]})).unwrap();
|
||||
|
||||
// Select group and run average
|
||||
s.execute("select.set", &json!({"ids": [g]})).unwrap();
|
||||
s.execute("path.average", &json!({"axis": "both"})).unwrap();
|
||||
|
||||
// b was locked so it remains unchanged; a is averaged on its own corners.
|
||||
assert_eq!(path(&s, b), orig_b);
|
||||
assert!(all_at(&anchor_points(&s, a), Point::new(50.0, 50.0)), "{:?}", anchor_points(&s, a));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user