mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-14 05:21:13 +00:00
M4: Flatten bakes a vector's point corner radius into its path (#2195)
Flatten used the vector's network as stored, so a rounded point kept its radius and the box kept the cut-off sharp vertex. It now takes the network as the layer draws it (VectorNetwork::rounded_smoothed through geometry::drawn_network): each rounded point becomes the arc's end points joined by curves, every radius reads 0, and the layer's box is re-fitted to the baked geometry. Shapes' own radii were already baked; a rounded point beside a curved segment keeps today's behaviour. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Model-Effort: Opus 5.5 drove; Opus 5.5 briefed; Sonnet 5.5 wrote at medium
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
be991533b0
commit
641cc41594
@@ -496,7 +496,9 @@ fn collect_flatten_parts(
|
||||
}
|
||||
let part = match &node.kind {
|
||||
NodeKind::Vector { network } => {
|
||||
let mut part = network.clone();
|
||||
// Rounded points bake into the path (the arc's end points joined by curves), drawn the way
|
||||
// the layer draws them, so the shape looks the same and the box fits the real geometry.
|
||||
let mut part = figcraft_doc::geometry::drawn_network(network, node.corners.smoothing).into_owned();
|
||||
if !part.regions.is_empty() && part.regions.iter().all(|region| region.fills.is_none() && region.paint_space.is_none()) {
|
||||
let winding = if part.regions.iter().any(|region| region.winding == WindingRule::EvenOdd) {
|
||||
WindingRule::EvenOdd
|
||||
|
||||
@@ -829,3 +829,68 @@ fn vector_points_take_the_requested_size() {
|
||||
let e = s.execute("vector.create", &json!({"points": points, "width": 16, "height": -1})).unwrap_err().to_string();
|
||||
assert!(e.contains("positive"), "{e}");
|
||||
}
|
||||
|
||||
/// A vector from `points` with a corner radius per point (`radii`), via `vector.create`.
|
||||
fn rounded_vector(s: &mut Session, points: &[(f64, f64)], radii: &[f64], closed: bool) -> NodeId {
|
||||
let pts: Vec<Value> = points.iter().map(|p| json!([p.0, p.1])).collect();
|
||||
let id = id_of(&ex(s, "vector.create", json!({"points": pts, "closed": closed}))["id"]).unwrap();
|
||||
if let NodeKind::Vector { network } = &mut s.doc_mut().unwrap().get_mut(id).unwrap().kind {
|
||||
for (v, r) in network.vertices.iter_mut().zip(radii) {
|
||||
v.radius = *r;
|
||||
}
|
||||
}
|
||||
id
|
||||
}
|
||||
|
||||
/// #2195: Flatten bakes a vector's point radii into its path: the rounded vertex becomes the
|
||||
/// arc's end points joined by curves, the layer stays a vector, the box fits the real geometry.
|
||||
#[test]
|
||||
fn flatten_bakes_a_vectors_point_radius() {
|
||||
// An open V with the apex rounded.
|
||||
let mut s = Session::new();
|
||||
let v = rounded_vector(&mut s, &[(0.0, 0.0), (184.0, 245.0), (368.0, 0.0)], &[0.0, 60.0, 0.0], false);
|
||||
let before = net(&s, v);
|
||||
let drawn = before.rounded().unwrap().stroke_path().perimeter(1e-4);
|
||||
let h_before = s.doc().unwrap().node(v).unwrap().height;
|
||||
let out = ex(&mut s, "vector.flatten", json!({"ids": [v]}));
|
||||
let f = id_of(&out["id"]).unwrap();
|
||||
let after = net(&s, f);
|
||||
assert_eq!(after.vertices.len(), 4, "the apex became two points: 3 + 1");
|
||||
assert!(after.vertices.iter().all(|p| p.radius == 0.0), "no radius left");
|
||||
let len = after.stroke_path().perimeter(1e-4);
|
||||
assert!((len - drawn).abs() < 0.5, "the outline is the same: {len} vs {drawn}");
|
||||
let h = s.doc().unwrap().node(f).unwrap().height;
|
||||
let bb = after.bounds().unwrap();
|
||||
assert!(h < h_before && (h - 245.0).abs() > 1.0, "the box shrinks from 245: {h}");
|
||||
assert!((h - bb.height()).abs() < 1e-6 && bb.y0.abs() < 1e-6, "the box is the baked path's bounds: {h} vs {bb:?}");
|
||||
println!("open V: points {} -> {}, height {h_before} -> {h}", before.vertices.len(), after.vertices.len());
|
||||
s.undo().unwrap();
|
||||
assert!(s.doc().unwrap().node(v).is_some() && s.doc().unwrap().node(f).is_none(), "one undo step");
|
||||
assert_eq!(net(&s, v).vertices[1].radius, 60.0);
|
||||
|
||||
// A closed triangle rounded at every point.
|
||||
let mut s = Session::new();
|
||||
let v = rounded_vector(&mut s, &[(0.0, 0.0), (368.0, 0.0), (184.0, 245.0)], &[40.0; 3], true);
|
||||
let drawn = net(&s, v).rounded().unwrap();
|
||||
let (len, area) = (drawn.stroke_path().perimeter(1e-4), drawn.fill_path().area().abs());
|
||||
let f = id_of(&ex(&mut s, "vector.flatten", json!({"ids": [v]}))["id"]).unwrap();
|
||||
let after = net(&s, f);
|
||||
assert_eq!(after.vertices.len(), 6, "each corner became two points");
|
||||
assert!(after.vertices.iter().all(|p| p.radius == 0.0));
|
||||
assert_eq!(after.regions.len(), 1, "still one filled region");
|
||||
assert!((after.stroke_path().perimeter(1e-4) - len).abs() < 0.5);
|
||||
assert!((after.fill_path().area().abs() - area).abs() < 0.5, "same fill area");
|
||||
let n = s.doc().unwrap().node(f).unwrap().clone();
|
||||
let bb = after.bounds().unwrap();
|
||||
assert!(n.height < 245.0 && (n.height - bb.height()).abs() < 1e-6 && (n.width - bb.width()).abs() < 1e-6);
|
||||
println!("triangle: points 3 -> {}, height 245 -> {}", after.vertices.len(), n.height);
|
||||
|
||||
// Guard: no radius, nothing changes.
|
||||
let mut s = Session::new();
|
||||
let v = rounded_vector(&mut s, &[(0.0, 0.0), (184.0, 245.0), (368.0, 0.0)], &[0.0; 3], false);
|
||||
let before = net(&s, v);
|
||||
let f = id_of(&ex(&mut s, "vector.flatten", json!({"ids": [v]}))["id"]).unwrap();
|
||||
assert_eq!(net(&s, f).vertices.len(), 3);
|
||||
assert_eq!(s.doc().unwrap().node(f).unwrap().height, 245.0);
|
||||
assert_eq!(net(&s, f).vertices, before.vertices);
|
||||
}
|
||||
|
||||
+3
-2
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user