From 55fa4c197c9f9c97219b0c8e50aa05b1024572ff Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sat, 13 Jun 2026 15:18:58 -0700 Subject: [PATCH 1/5] Replace the 'Vec2 to Point' node with an 'As Vector' node and automatic conversion --- .../node_graph/document_node_definitions.rs | 6 ++-- .../messages/portfolio/document_migration.rs | 3 +- .../interpreted-executor/src/node_registry.rs | 3 +- node-graph/libraries/core-types/src/ops.rs | 14 ++++++++- .../vector-types/src/vector/vector_types.rs | 30 +++++++++++++++++++ node-graph/nodes/vector/src/vector_nodes.rs | 14 +++------ 6 files changed, 54 insertions(+), 16 deletions(-) diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index f3912ecdb5..39de6145eb 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -787,9 +787,9 @@ fn document_node_definitions() -> HashMap) DocumentNode { - implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::vec_2_to_point::IDENTIFIER), + implementation: DocumentNodeImplementation::ProtoNode(vector::as_vector::IDENTIFIER), inputs: vec![NodeInput::node(NodeId(2), 0)], ..Default::default() }, @@ -858,7 +858,7 @@ fn document_node_definitions() -> HashMap] = &[ aliases: &["graphene_core::vector::TangentOnPathNode"], }, NodeReplacement { - node: graphene_std::vector::vec_2_to_point::IDENTIFIER, + node: graphene_std::vector::as_vector::IDENTIFIER, aliases: &[ "graphene_core::vector::vector_nodes::PositionToPointNode", "graphene_core::vector::PositionToPointNode", "graphene_core::vector::Vec2ToPointNode", + "core_types::vector::Vec2ToPointNode", ], }, ]; diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 7504ce0777..7f1c8deb56 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -33,7 +33,6 @@ fn node_registry() -> HashMap, to: List), - into_node!(from: List, to: List), into_node!(from: List>, to: List>), #[cfg(feature = "gpu")] into_node!(from: List>, to: List>), @@ -96,6 +95,8 @@ fn node_registry() -> HashMap, to: List), + convert_node!(from: DVec2, to: List), convert_node!(from: String, to: String), convert_node!(from: bool, to: String), convert_node!(from: DVec2, to: String), diff --git a/node-graph/libraries/core-types/src/ops.rs b/node-graph/libraries/core-types/src/ops.rs index 5ab39e35bd..9d812c9576 100644 --- a/node-graph/libraries/core-types/src/ops.rs +++ b/node-graph/libraries/core-types/src/ops.rs @@ -106,7 +106,19 @@ impl Convert for DVec2 { } } -// TODO: Add a DVec2 to List anchor point conversion implementation to replace the 'Vec2 to Point' node +/// Constructs `Self` from a single anchor point at the given position. Implemented by the vector crate's +/// path type so the `Convert` impl below can build a single-point path without core-types depending on +/// that crate (mirroring how [`ListConvert`] bridges per-item list conversions). +pub trait FromAnchorPosition { + fn from_anchor_position(position: DVec2) -> Self; +} + +// Converts a position into a vector path composed of a single anchor point +impl Convert, ()> for DVec2 { + async fn convert(self, _: Footprint, _: ()) -> List { + List::new_from_item(Item::new_from_element(T::from_anchor_position(self))) + } +} /// Implements the [`Convert`] trait for conversion between the cartesian product of Rust's primitive numeric types. macro_rules! impl_convert { diff --git a/node-graph/libraries/vector-types/src/vector/vector_types.rs b/node-graph/libraries/vector-types/src/vector/vector_types.rs index 96237b8ae6..ee0282b0cb 100644 --- a/node-graph/libraries/vector-types/src/vector/vector_types.rs +++ b/node-graph/libraries/vector-types/src/vector/vector_types.rs @@ -55,6 +55,23 @@ impl graphene_hash::CacheHash for Vector { } } +impl core_types::ops::FromAnchorPosition for Vector { + fn from_anchor_position(position: DVec2) -> Self { + let mut point_domain = PointDomain::new(); + point_domain.push(PointId::generate(), position); + + Self { point_domain, ..Default::default() } + } +} + +// Identity item conversion so `List` satisfies the blanket `Convert, ()> for List`, letting its +// auto-inserted input wrapper be a `ConvertNode` (which also accepts a `DVec2` anchor position) rather than an `IntoNode`. +impl core_types::ops::ListConvert for Vector { + fn convert_item(self) -> Vector { + self + } +} + impl Vector { /// Add a subpath to this vector path. pub fn append_subpath(&mut self, subpath: impl Borrow>, preserve_id: bool) { @@ -619,4 +636,17 @@ mod tests { let generated = vector.stroke_bezier_paths().collect::>(); assert_subpath_eq(&generated, &[curve, circle]); } + + // Verifies the `DVec2 -> List` conversion that replaced the former "Vec2 to Point" node yields a path + // with exactly one anchor point at the given position and no segments + #[test] + fn anchor_position_builds_single_point_path() { + use core_types::ops::FromAnchorPosition; + + let vector = Vector::from_anchor_position(DVec2::new(3., 4.)); + + assert_eq!(vector.point_domain.positions(), [DVec2::new(3., 4.)]); + assert_eq!(vector.point_domain.ids().len(), 1); + assert!(vector.segment_domain.ids().is_empty()); + } } diff --git a/node-graph/nodes/vector/src/vector_nodes.rs b/node-graph/nodes/vector/src/vector_nodes.rs index afe6987de6..c91d79fce8 100644 --- a/node-graph/nodes/vector/src/vector_nodes.rs +++ b/node-graph/nodes/vector/src/vector_nodes.rs @@ -1074,16 +1074,10 @@ async fn dimensions(_: impl Ctx, content: List) -> DVec2 { .unwrap_or_default() } -// TODO: Replace this node with an automatic type conversion implementation of the `Convert` trait -/// Converts a vec2 value into a vector path composed of a single anchor point. -/// -/// This is useful in conjunction with nodes that repeat it, followed by the "Points to Polyline" node to string together a path of the points. -#[node_macro::node(category("Vector"), name("Vec2 to Point"), path(core_types::vector))] -async fn vec2_to_point(_: impl Ctx, vec2: DVec2) -> List { - let mut point_domain = PointDomain::new(); - point_domain.push(PointId::generate(), vec2); - - List::new_from_item(Item::new_from_element(Vector { point_domain, ..Default::default() })) +/// Type-asserts a value to be vector data. +#[node_macro::node(category("Vector"), name("As Vector"), path(core_types::vector))] +fn as_vector(_: impl Ctx, value: List) -> List { + value } /// Creates a polyline from a series of vector points, replacing any existing segments and regions that may already exist. From 6899a31340a5d33f5dca530a21a56a8e69ded430 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sun, 14 Jun 2026 12:35:17 -0700 Subject: [PATCH 2/5] Accept a Vec2 as content for the Wrap Graphic node --- node-graph/libraries/graphic-types/src/graphic.rs | 11 +++++++++-- node-graph/nodes/graphic/src/graphic.rs | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/node-graph/libraries/graphic-types/src/graphic.rs b/node-graph/libraries/graphic-types/src/graphic.rs index 7d5267d352..9940a5888e 100644 --- a/node-graph/libraries/graphic-types/src/graphic.rs +++ b/node-graph/libraries/graphic-types/src/graphic.rs @@ -1,12 +1,12 @@ use core_types::bounds::{BoundingBox, RenderBoundingBox}; use core_types::graphene_hash::CacheHash; use core_types::list::{ATTR_FILL, ATTR_STROKE, Item, List}; -use core_types::ops::ListConvert; +use core_types::ops::{FromAnchorPosition, ListConvert}; use core_types::render_complexity::RenderComplexity; use core_types::uuid::NodeId; use core_types::{ATTR_CLIPPING_MASK, ATTR_EDITOR_LAYER_PATH, ATTR_GRADIENT_TYPE, ATTR_OPACITY, ATTR_OPACITY_FILL, ATTR_SPREAD_METHOD, ATTR_TRANSFORM, Color}; use dyn_any::DynAny; -use glam::DAffine2; +use glam::{DAffine2, DVec2}; use raster_types::{CPU, GPU, Raster}; use std::borrow::Cow; use vector_types::GradientStops; @@ -393,6 +393,13 @@ impl From for Graphic { Graphic::default() } } + +// DVec2 +impl From for Graphic { + fn from(position: DVec2) -> Self { + Graphic::Vector(List::new_from_element(Vector::from_anchor_position(position))) + } +} // Note: List conversions handled by blanket impl in gcore impl Graphic { diff --git a/node-graph/nodes/graphic/src/graphic.rs b/node-graph/nodes/graphic/src/graphic.rs index 17c64f8266..1326ed839f 100644 --- a/node-graph/nodes/graphic/src/graphic.rs +++ b/node-graph/nodes/graphic/src/graphic.rs @@ -549,6 +549,7 @@ pub async fn wrap_graphic + 'n>( List, List, DAffine2, + DVec2, )] content: T, ) -> List { From f166dce95b309ed62559d460d0c10f09a068ab6d Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sun, 14 Jun 2026 12:35:18 -0700 Subject: [PATCH 3/5] Rename the type-assertion nodes from 'To' to 'As' --- .../src/messages/portfolio/document_migration.rs | 14 +++++++------- node-graph/nodes/math/src/lib.rs | 12 ++++++------ node-graph/nodes/text/src/lib.rs | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index fc6c4ed385..21a509f2c3 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -404,16 +404,16 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[ aliases: &["graphene_math_nodes::TangentInverseNode", "graphene_core::ops::TangentInverseNode"], }, NodeReplacement { - node: graphene_std::math_nodes::to_f_64::IDENTIFIER, - aliases: &["graphene_math_nodes::ToF64Node", "graphene_core::ops::ToF64Node"], + node: graphene_std::math_nodes::as_f_64::IDENTIFIER, + aliases: &["graphene_math_nodes::ToF64Node", "graphene_core::ops::ToF64Node", "math_nodes::ToF64Node"], }, NodeReplacement { - node: graphene_std::math_nodes::to_u_32::IDENTIFIER, + node: graphene_std::math_nodes::as_u_32::IDENTIFIER, aliases: &["graphene_math_nodes::ToU32Node", "graphene_core::ops::ToU32Node", "math_nodes::ToU32Node"], }, NodeReplacement { - node: graphene_std::math_nodes::to_u_64::IDENTIFIER, - aliases: &["graphene_math_nodes::ToU64Node", "graphene_core::ops::ToU64Node"], + node: graphene_std::math_nodes::as_u_64::IDENTIFIER, + aliases: &["graphene_math_nodes::ToU64Node", "graphene_core::ops::ToU64Node", "math_nodes::ToU64Node"], }, NodeReplacement { node: graphene_std::math_nodes::vec_2_value::IDENTIFIER, @@ -676,8 +676,8 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[ aliases: &["graphene_core::logic::SwitchNode"], }, NodeReplacement { - node: graphene_std::text_nodes::to_string::IDENTIFIER, - aliases: &["graphene_core::logic::ToStringNode"], + node: graphene_std::text_nodes::as_string::IDENTIFIER, + aliases: &["graphene_core::logic::ToStringNode", "text_nodes::ToStringNode"], }, NodeReplacement { node: graphene_std::text_nodes::json::query_json::IDENTIFIER, diff --git a/node-graph/nodes/math/src/lib.rs b/node-graph/nodes/math/src/lib.rs index 3e864ee727..8e7f9785c1 100644 --- a/node-graph/nodes/math/src/lib.rs +++ b/node-graph/nodes/math/src/lib.rs @@ -421,22 +421,22 @@ fn random( // TODO: Test that these are no longer needed in all circumstances, then remove them and add a migration to convert these into Passthrough nodes. Note: these act more as type annotations than as identity functions. /// Convert a number to an integer of the type u32, which may be the required type for certain node inputs. -#[node_macro::node(name("To u32"), category("Debug"))] -fn to_u32(_: impl Ctx, value: u32) -> u32 { +#[node_macro::node(name("As u32"), category("Debug"))] +fn as_u32(_: impl Ctx, value: u32) -> u32 { value } // TODO: Test that these are no longer needed in all circumstances, then remove them and add a migration to convert these into Passthrough nodes. Note: these act more as type annotations than as identity functions. /// Convert a number to an integer of the type u64, which may be the required type for certain node inputs. -#[node_macro::node(name("To u64"), category("Debug"))] -fn to_u64(_: impl Ctx, value: u64) -> u64 { +#[node_macro::node(name("As u64"), category("Debug"))] +fn as_u64(_: impl Ctx, value: u64) -> u64 { value } // TODO: Test that these are no longer needed in all circumstances, then remove them and add a migration to convert these into Passthrough nodes. Note: these act more as type annotations than as identity functions. /// Convert an integer to a decimal number of the type f64, which may be the required type for certain node inputs. -#[node_macro::node(name("To f64"), category("Debug"))] -fn to_f64(_: impl Ctx, value: f64) -> f64 { +#[node_macro::node(name("As f64"), category("Debug"))] +fn as_f64(_: impl Ctx, value: f64) -> f64 { value } diff --git a/node-graph/nodes/text/src/lib.rs b/node-graph/nodes/text/src/lib.rs index 63afabc6b5..8642bb770f 100644 --- a/node-graph/nodes/text/src/lib.rs +++ b/node-graph/nodes/text/src/lib.rs @@ -188,7 +188,7 @@ fn string_value(_: impl Ctx, _primary: (), string: TextArea) -> String { /// Type-asserts a value to be a string. #[node_macro::node(category("Debug"))] -fn to_string(_: impl Ctx, value: String) -> String { +fn as_string(_: impl Ctx, value: String) -> String { value } From 5b6cfaf4bb3adc79c63a7c48581bc18c39e8708a Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sun, 14 Jun 2026 12:35:18 -0700 Subject: [PATCH 4/5] Fix the Transform node reversing a Vec2 input's direction --- node-graph/libraries/core-types/src/transform.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node-graph/libraries/core-types/src/transform.rs b/node-graph/libraries/core-types/src/transform.rs index 90193eadd4..60c32887e8 100644 --- a/node-graph/libraries/core-types/src/transform.rs +++ b/node-graph/libraries/core-types/src/transform.rs @@ -234,6 +234,6 @@ impl ApplyTransform for DVec2 { *self = modification.transform_point2(*self); } fn left_apply_transform(&mut self, modification: &DAffine2) { - *self = modification.inverse().transform_point2(*self); + *self = modification.transform_point2(*self); } } From f140dc818d781910eed583530eb10a4062335751 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sun, 14 Jun 2026 12:38:17 -0700 Subject: [PATCH 5/5] Fix text migration bug --- editor/src/messages/portfolio/document_migration.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index 21a509f2c3..a826225f16 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -1575,6 +1575,8 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], for i in 10..=12 { document.network_interface.set_input(&InputConnector::node(*node_id, i), old_inputs[i - 2].clone(), network_path); } + + inputs_count = 13; } // Upgrade Sine, Cosine, and Tangent nodes to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default