From e405858bc7de848644d2f87a0920e0cd6732c904 Mon Sep 17 00:00:00 2001 From: Chetan Sahney Date: Sat, 14 Mar 2026 11:31:52 +0530 Subject: [PATCH 1/4] fix: populate colinear_manipulators for Circle, Arc, and Spiral (#3891) --- .../nodes/vector/src/generator_nodes.rs | 100 +++++++++++++----- 1 file changed, 71 insertions(+), 29 deletions(-) diff --git a/node-graph/nodes/vector/src/generator_nodes.rs b/node-graph/nodes/vector/src/generator_nodes.rs index 66c8009e02..27d5949bb6 100644 --- a/node-graph/nodes/vector/src/generator_nodes.rs +++ b/node-graph/nodes/vector/src/generator_nodes.rs @@ -42,42 +42,69 @@ impl CornerRadius for [f64; 4] { /// Generates a circle shape with a chosen radius. #[node_macro::node(category("Vector: Shape"))] fn circle( - _: impl Ctx, - _primary: (), - #[unit(" px")] - #[default(50.)] - radius: f64, + _: impl Ctx, + _primary: (), + #[unit(" px")] + #[default(50.)] + radius: f64, ) -> Table { - let radius = radius.abs(); - Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius)))) + let radius = radius.abs(); + // 1. Create the vector + let mut circle = Vector::from_subpath(subpath::Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))); + + // 2. Register the 4 pairs of colinear handles + let len = circle.segment_domain.ids().len(); + for i in 0..len { + circle.colinear_manipulators.push([ + HandleId::end(circle.segment_domain.ids()[i]), + HandleId::primary(circle.segment_domain.ids()[(i + 1) % len]) + ]); + } + + Table::new_from_element(circle) } /// Generates an arc shape forming a portion of a circle which may be open, closed, or a pie slice. #[node_macro::node(category("Vector: Shape"))] fn arc( - _: impl Ctx, - _primary: (), - #[unit(" px")] - #[default(50.)] - radius: f64, - start_angle: Angle, - #[default(270.)] - #[range((0., 360.))] - sweep_angle: Angle, - arc_type: ArcType, + _: impl Ctx, + _primary: (), + #[unit(" px")] + #[default(50.)] + radius: f64, + start_angle: Angle, + #[default(270.)] + #[range((0., 360.))] + sweep_angle: Angle, + arc_type: ArcType, ) -> Table { - Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_arc( - radius, - start_angle / 360. * std::f64::consts::TAU, - sweep_angle / 360. * std::f64::consts::TAU, - match arc_type { - ArcType::Open => subpath::ArcType::Open, - ArcType::Closed => subpath::ArcType::Closed, - ArcType::PieSlice => subpath::ArcType::PieSlice, - }, - ))) + // 1. Create the arc vector + let mut arc_vector = Vector::from_subpath(subpath::Subpath::new_arc( + radius, + start_angle / 360. * std::f64::consts::TAU, + sweep_angle / 360. * std::f64::consts::TAU, + match arc_type { + ArcType::Open => subpath::ArcType::Open, + ArcType::Closed => subpath::ArcType::Closed, + ArcType::PieSlice => subpath::ArcType::PieSlice, + }, + )); + + // 2. Link handles only if both adjacent segments are cubic beziers + let len = arc_vector.segment_domain.ids().len(); + for i in 0..len.saturating_sub(1) { + if arc_vector.segment_domain.handles()[i].is_cubic() && arc_vector.segment_domain.handles()[i + 1].is_cubic() { + arc_vector.colinear_manipulators.push([ + HandleId::end(arc_vector.segment_domain.ids()[i]), + HandleId::primary(arc_vector.segment_domain.ids()[i + 1]) + ]); + } + } + + Table::new_from_element(arc_vector) } +/// Generates a spiral shape that winds from an inner to an outer radius. /// Generates a spiral shape that winds from an inner to an outer radius. #[node_macro::node(category("Vector: Shape"), properties("spiral_properties"))] fn spiral( @@ -90,14 +117,29 @@ fn spiral( #[default(25)] outer_radius: f64, #[default(90.)] angular_resolution: f64, ) -> Table { - Table::new_from_element(Vector::from_subpath(subpath::Subpath::new_spiral( + // 1. Create the spiral vector + let mut spiral_vector = Vector::from_subpath(subpath::Subpath::new_spiral( inner_radius, outer_radius, turns, start_angle.to_radians(), angular_resolution.to_radians(), spiral_type, - ))) + )); + + // 2. NEW: Link consecutive curved segments + let len = spiral_vector.segment_domain.ids().len(); + for i in 0..len.saturating_sub(1) { + // Ensure both segments meeting at the anchor point are cubic beziers + if spiral_vector.segment_domain.handles()[i].is_cubic() && spiral_vector.segment_domain.handles()[i + 1].is_cubic() { + spiral_vector.colinear_manipulators.push([ + HandleId::end(spiral_vector.segment_domain.ids()[i]), + HandleId::primary(spiral_vector.segment_domain.ids()[i + 1]) + ]); + } + } + + Table::new_from_element(spiral_vector) } /// Generates an ellipse shape (an oval or stretched circle) with the chosen radii. From 9c40108717482d001c32c5209e30fbb5bf10d321 Mon Sep 17 00:00:00 2001 From: Chetan Sahney <84283700+Chetansahney@users.noreply.github.com> Date: Sat, 14 Mar 2026 14:28:19 +0530 Subject: [PATCH 2/4] Update node-graph/nodes/vector/src/generator_nodes.rs Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- node-graph/nodes/vector/src/generator_nodes.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/node-graph/nodes/vector/src/generator_nodes.rs b/node-graph/nodes/vector/src/generator_nodes.rs index 27d5949bb6..ce5a90b430 100644 --- a/node-graph/nodes/vector/src/generator_nodes.rs +++ b/node-graph/nodes/vector/src/generator_nodes.rs @@ -53,11 +53,13 @@ fn circle( let mut circle = Vector::from_subpath(subpath::Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))); // 2. Register the 4 pairs of colinear handles - let len = circle.segment_domain.ids().len(); + // 2. Register the 4 pairs of colinear handles + let ids = circle.segment_domain.ids(); + let len = ids.len(); for i in 0..len { circle.colinear_manipulators.push([ - HandleId::end(circle.segment_domain.ids()[i]), - HandleId::primary(circle.segment_domain.ids()[(i + 1) % len]) + HandleId::end(ids[i]), + HandleId::primary(ids[(i + 1) % len]), ]); } From abe8f7f41f2bbd6ea89b344d0d20f7da72bfe8d6 Mon Sep 17 00:00:00 2001 From: Chetan Sahney Date: Sat, 14 Mar 2026 14:47:27 +0530 Subject: [PATCH 3/4] cleaned up code(Removed extra lines) --- node-graph/nodes/vector/src/generator_nodes.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/node-graph/nodes/vector/src/generator_nodes.rs b/node-graph/nodes/vector/src/generator_nodes.rs index 27d5949bb6..fd3d7da86b 100644 --- a/node-graph/nodes/vector/src/generator_nodes.rs +++ b/node-graph/nodes/vector/src/generator_nodes.rs @@ -104,7 +104,6 @@ fn arc( Table::new_from_element(arc_vector) } -/// Generates a spiral shape that winds from an inner to an outer radius. /// Generates a spiral shape that winds from an inner to an outer radius. #[node_macro::node(category("Vector: Shape"), properties("spiral_properties"))] fn spiral( From 82b25fc92377f20df3a9e3b610c66029a949bbe5 Mon Sep 17 00:00:00 2001 From: Chetan Sahney Date: Sat, 14 Mar 2026 15:01:13 +0530 Subject: [PATCH 4/4] final cleaned up code (Removed extra lines and improved the comments) --- node-graph/nodes/vector/src/generator_nodes.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/node-graph/nodes/vector/src/generator_nodes.rs b/node-graph/nodes/vector/src/generator_nodes.rs index 1489119323..ac47f4a057 100644 --- a/node-graph/nodes/vector/src/generator_nodes.rs +++ b/node-graph/nodes/vector/src/generator_nodes.rs @@ -52,8 +52,7 @@ fn circle( // 1. Create the vector let mut circle = Vector::from_subpath(subpath::Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))); - // 2. Register the 4 pairs of colinear handles - // 2. Register the 4 pairs of colinear handles + // Created the collinear_manipulators so that all handles are linked, making it easier to edit the circle as a circle instead of a 4 point shape. let ids = circle.segment_domain.ids(); let len = ids.len(); for i in 0..len { @@ -80,7 +79,6 @@ fn arc( sweep_angle: Angle, arc_type: ArcType, ) -> Table { - // 1. Create the arc vector let mut arc_vector = Vector::from_subpath(subpath::Subpath::new_arc( radius, start_angle / 360. * std::f64::consts::TAU, @@ -118,7 +116,7 @@ fn spiral( #[default(25)] outer_radius: f64, #[default(90.)] angular_resolution: f64, ) -> Table { - // 1. Create the spiral vector + let mut spiral_vector = Vector::from_subpath(subpath::Subpath::new_spiral( inner_radius, outer_radius, @@ -128,7 +126,7 @@ fn spiral( spiral_type, )); - // 2. NEW: Link consecutive curved segments + let len = spiral_vector.segment_domain.ids().len(); for i in 0..len.saturating_sub(1) { // Ensure both segments meeting at the anchor point are cubic beziers