From f0c920fa8e44fe71c9b00d5ae67f47c8e3cea79f Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 14 Nov 2026 11:15:41 -0800 Subject: [PATCH] Shape Builder: leave out Live Paint faces and edges with an opacity of 0 A visible, unlocked Live Paint face or edge whose own opacity is 0 draws nothing, as Expand and the edge stroke check already treat it, but the Shape Builder still took it as an input. It now leaves such faces and edges out; hidden and locked parts keep their current handling. --- crates/engine/src/tests/build.rs | 15 ++++++++++++--- crates/tools/src/builder/lookup.rs | 8 ++++---- docs/target-app-parity.md | 2 +- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/crates/engine/src/tests/build.rs b/crates/engine/src/tests/build.rs index d16bdb537d..c87b8c85d7 100644 --- a/crates/engine/src/tests/build.rs +++ b/crates/engine/src/tests/build.rs @@ -4124,7 +4124,8 @@ fn shape_builder_uses_the_filled_faces_of_a_live_paint_group() { /// The Shape Builder takes a Live Paint face or edge when any of its fill or stroke rows draws, /// whichever row is on top: a painted lower row under an unpainted top row counts, and a part -/// whose only painted row is hidden, transparent or (for an edge) 0 pt wide does not. +/// whose only painted row is hidden, transparent or (for an edge) 0 pt wide, or whose own opacity +/// is 0, does not. #[test] fn shape_builder_takes_the_live_paint_faces_and_edges_that_draw() { use vectorcraft_doc::{FillLayer, StrokeLayer}; @@ -4154,15 +4155,23 @@ fn shape_builder_takes_the_live_paint_faces_and_edges_that_draw() { let hidden_stroke = set_live_part(&mut s, g, None, Some((150.0, 50.0)), vec![hidden(stroke(red(), 2.0))], 1.0); let thin_stroke = set_live_part(&mut s, g, None, Some((100.0, 50.0)), vec![stroke(red(), 0.0)], 1.0); let clear_stroke = set_live_part(&mut s, g, None, Some((50.0, 50.0)), vec![clear(stroke(red(), 2.0))], 1.0); - let parts = [lower_fill, hidden_fill, clear_fill, lower_stroke, hidden_stroke, thin_stroke, clear_stroke]; + let clear_edge = set_live_part(&mut s, g, None, Some((75.0, 0.0)), vec![stroke(red(), 2.0)], 0.0); + let parts = [lower_fill, hidden_fill, clear_fill, lower_stroke, hidden_stroke, thin_stroke, clear_stroke, clear_edge]; assert_eq!(parts.iter().collect::>().len(), parts.len()); let d = s.doc().unwrap(); let (_, leaves) = vectorcraft_tools::builder::shape_builder_shapes(&d.doc, &[g], true); let taken: Vec = leaves.iter().map(|l| l.id).collect(); assert!(taken.contains(&lower_fill) && taken.contains(&lower_stroke), "a painted lower row draws: {taken:?}"); - for id in [hidden_fill, clear_fill, hidden_stroke, thin_stroke, clear_stroke] { + for id in [hidden_fill, clear_fill, hidden_stroke, thin_stroke, clear_stroke, clear_edge] { assert!(!taken.contains(&id), "{id:?} draws nothing: {taken:?}"); } + // A face with a painted fill whose own opacity is 0 is left out too. + let mut s = session(); + let g = live(&mut s); + let clear_face = set_live_part(&mut s, g, Some((75.0, 50.0)), None, vec![fill(red())], 0.0); + let d = s.doc().unwrap(); + let (_, leaves) = vectorcraft_tools::builder::shape_builder_shapes(&d.doc, &[g], true); + assert!(!leaves.iter().any(|l| l.id == clear_face), "a face with an opacity of 0 draws nothing"); } /// An edge that closes on itself is a line around its face, not a second shape over it. diff --git a/crates/tools/src/builder/lookup.rs b/crates/tools/src/builder/lookup.rs index 7a39214081..e8e261e5a4 100644 --- a/crates/tools/src/builder/lookup.rs +++ b/crates/tools/src/builder/lookup.rs @@ -505,8 +505,8 @@ fn collect_members<'a>(n: &'a Node, builder: bool, out: &mut Vec<&'a Node>) { NodeKind::Path { guide: true, .. } | NodeKind::Path { clipping: true, .. } => {} NodeKind::Path { .. } | NodeKind::Compound { .. } => out.push(n), NodeKind::Group { .. } if builder && is_live_paint(n) => { - out.extend(faces(n).into_iter().filter(|f| !f.visible || f.locked || f.appearance.has_drawn_fill()).map(|f| &**f)); - out.extend(edges(n).into_iter().filter(|e| !e.visible || e.locked || e.appearance.has_drawn_stroke()).map(|e| &**e)); + out.extend(faces(n).into_iter().filter(|f| !f.visible || f.locked || (f.opacity > 0.0 && f.appearance.has_drawn_fill())).map(|f| &**f)); + out.extend(edges(n).into_iter().filter(|e| !e.visible || e.locked || (e.opacity > 0.0 && e.appearance.has_drawn_stroke())).map(|e| &**e)); } NodeKind::Group { children, .. } | NodeKind::Layer { children, .. } => { for c in children { @@ -591,8 +591,8 @@ fn shapes_of<'a>(doc: &'a Document, roots: &[NodeId], builder: bool) -> (Vec(doc: &'a Document, roots: &[NodeId], open_filled_as_closed: bool) -> (Vec, Vec<&'a Node>) { let (mut shapes, leaves) = shapes_of(doc, roots, true); for (s, l) in shapes.iter_mut().zip(&leaves) { diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 35bb9c8159..c269e14fd9 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -1196,7 +1196,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur Keep selected in the Pencil and Paintbrush Tool Options is on by default: while it is on, a stroke selects the path it draws or continues and replaces the earlier selection. While it is off, a new stroke leaves nothing selected, the earlier selection included, as the reference application starts the stroke by dropping it (#5752); a continued or redrawn path is deselected while the rest of the selection stays (`path.freehand` `keepSelected`). With two open paths selected, a Pencil stroke from an end of one that ends within Edit selected paths' distance of an end of the other connects them into one path with the first path's appearance, in one undo step, as [Connect two paths with the Pencil tool](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/draw-shapes/connect-two-paths-with-the-pencil-tool.html) describes (`path.freehand` `connect`). Double-clicking the Shape Builder tool opens Shape Builder Tool Options ([Shape Builder tool options](https://web.archive.org/web/20260105172244/https://helpx.adobe.com/illustrator/using/building-new-shapes-using-shape.html)): with Consider Open Filled Paths as Closed off, a filled open path cuts regions as an unfilled one does; Pick Color From Color Swatches fills merged shapes with the current fill, the Left and Right arrow keys step it through the document's color swatches, and Cursor Swatch Preview shows the current fill and the swatches before and after it above the pointer; Selection Straight Line merges what the line from a drag's first point to its last crosses ([Setting Illustrator's Shape Builder Options](https://web.archive.org/web/20260615204453/https://creativepro.com/setting-illustrators-shape-builder-options/)); with Highlight Fill off, merge mode shades no regions (`shapeBuilder.merge` `openFilledAsClosed` and `pickColorFrom`). - As VectorCraft's own rule, Consider Open Filled Paths as Closed counts an open path as filled when any of its fill rows draws (shown, painted and above 0% opacity), whichever row is on top, as the canvas paints it: a painted fill under an unpainted top fill closes the path, and a painted fill that is hidden or at 0% opacity does not. The region map, merges, erasing and Clicking Stroke Splits the Path follow that rule, and the Shape Builder takes the faces and edges of a Live Paint group that one of their fill or stroke rows draws. Regression tests: `tests::build::shape_builder_closes_open_paths_by_the_fill_rows_that_draw`, `tests::build::shape_builder_stroke_split_closes_open_paths_by_the_fill_rows_that_draw` and `tests::build::shape_builder_takes_the_live_paint_faces_and_edges_that_draw`. + As VectorCraft's own rule, Consider Open Filled Paths as Closed counts an open path as filled when any of its fill rows draws (shown, painted and above 0% opacity), whichever row is on top, as the canvas paints it: a painted fill under an unpainted top fill closes the path, and a painted fill that is hidden or at 0% opacity does not. The region map, merges, erasing and Clicking Stroke Splits the Path follow that rule, and the Shape Builder takes the faces and edges of a Live Paint group that one of their fill or stroke rows draws and whose own opacity is above 0. Regression tests: `tests::build::shape_builder_closes_open_paths_by_the_fill_rows_that_draw`, `tests::build::shape_builder_stroke_split_closes_open_paths_by_the_fill_rows_that_draw` and `tests::build::shape_builder_takes_the_live_paint_faces_and_edges_that_draw`. With In Merge Mode, Clicking Stroke Splits the Path on in the same dialog, a click on a stroke in merge mode splits its path into the run of stroke under the pointer and the rest of the path, as the [Shape Builder tool options](https://web.archive.org/web/20260105172244/https://helpx.adobe.com/illustrator/using/building-new-shapes-using-shape.html) page describes; the run goes from the anchor or crossing point before the pointer to the one after it, and the pointer shows scissors over it. Highlight Stroke When Editable draws that run in the color chosen in its Color list (Red by default), and with Pick Color From Color Swatches the run takes the current fill as its stroke (`shapeBuilder.merge` `splitStroke`). As VectorCraft's own rule, OK in Shape Builder Tool Options gives an error for a highlight Color with an alpha part (`#rrggbbaa` or `#rgba`) and changes nothing. Regression test: `dialogs::shape_builder_options::tests::a_highlight_color_with_an_alpha_part_is_refused`. With Pick Color From Artwork, a merged shape takes the appearance, opacity and blending mode of the object where the drag starts, else of the object where it ends, else of the front-most selected object, as the [Shape Builder combine page](https://web.archive.org/web/20251006211643/https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/draw-shapes/combine-shapes-with-shape-builder-tool.html) describes. A click in an area that no selected object covers, such as a hole other shapes enclose, takes the front-most selected object's look (`shapeBuilder.merge` `points`: the first point is where the drag starts, and the last point is where it ends).