From 21d299405954099db7812b951c8cae91ff19e5bf Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sun, 17 May 2026 10:56:40 -0700 Subject: [PATCH] Fix 'Solidify Strokes' node to produce dashed segments in an ordered sequence per open subpath (#3820) Fix the ordering of dashed segments of open paths with the 'Solidify Stroke' node --- node-graph/nodes/vector/src/vector_nodes.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/node-graph/nodes/vector/src/vector_nodes.rs b/node-graph/nodes/vector/src/vector_nodes.rs index acbb24bc16..fe502add91 100644 --- a/node-graph/nodes/vector/src/vector_nodes.rs +++ b/node-graph/nodes/vector/src/vector_nodes.rs @@ -1197,7 +1197,9 @@ async fn solidify_stroke(_: impl Ctx, #[ .with_dashes(dash_offset, dash_pattern) .with_miter_limit(miter_limit); - let stroke_options = kurbo::StrokeOpts::default(); + // Pick `stable_dash_order` per subpath: closed subpaths use the default merge so the seam-spanning dash matches Vello/SVG renderers, while open subpaths use stable order so dashes are emitted in path-length sequence + let stroke_options_default = kurbo::StrokeOpts::default(); + let stroke_options_stable = kurbo::StrokeOpts::default().stable_dash_order(true); // 0.25 is balanced between performace and accuracy of the curve. const STROKE_TOLERANCE: f64 = 0.25; @@ -1205,7 +1207,10 @@ async fn solidify_stroke(_: impl Ctx, #[ for mut path in bezpaths { path.apply_affine(Affine::new(stroke.transform.to_cols_array())); - let mut solidified = kurbo::stroke(path, &stroke_style, &stroke_options, STROKE_TOLERANCE); + let is_closed = matches!(path.elements().last(), Some(kurbo::PathEl::ClosePath)); + let stroke_options = if is_closed { &stroke_options_default } else { &stroke_options_stable }; + + let mut solidified = kurbo::stroke(path, &stroke_style, stroke_options, STROKE_TOLERANCE); if stroke.transform.matrix2.determinant() != 0. { solidified.apply_affine(Affine::new(stroke.transform.inverse().to_cols_array())); }