Rename the nodes 'Length' -> 'Magnitude', 'Flatten Path' -> 'Combine Paths', 'Vec2 Value' -> 'Combine Vec2', and add a new 'Vec2 Value' node (#4349)

* Rename the node 'Length' -> 'Magnitude'

* Rename the node 'Flatten Path' -> 'Combine Paths'

* Replace the node 'Vec2 Value' with 'Combine Vec2' and add a new 'Vec2 Value' that's actually a vec2

* Update demo artwork
This commit is contained in:
Keavon Chambers
2026-07-17 09:43:04 -07:00
committed by GitHub
parent 581bb05f5d
commit 0eab6aa93c
19 changed files with 78 additions and 47 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -433,17 +433,17 @@ impl<'a> ModifyInputsContext<'a> {
return None; return None;
}; };
// If inserting a 'Path' node, insert a 'Flatten Path' node if the type is `Graphic`. // If inserting a 'Path' node, insert a 'Combine Paths' node if the type is `Graphic`.
// TODO: Allow the 'Path' node to operate on `List` data by utilizing the reference (index or ID?) for each item. // TODO: Allow the 'Path' node to operate on `List` data by utilizing the reference (index or ID?) for each item.
if node_definition.identifier == "Path" { if node_definition.identifier == "Path" {
let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]); let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]);
if layer_input_type.compiled_element_name().as_deref() == Some("Graphic") { if layer_input_type.compiled_element_name().as_deref() == Some("Graphic") {
let Some(flatten_path_definition) = resolve_proto_node_type(graphene_std::vector_nodes::flatten_path::IDENTIFIER) else { let Some(combine_paths_definition) = resolve_proto_node_type(graphene_std::vector_nodes::combine_paths::IDENTIFIER) else {
log::error!("Flatten Path does not exist in ModifyInputsContext::existing_node_id"); log::error!("Combine Paths does not exist in ModifyInputsContext::existing_node_id");
return None; return None;
}; };
let node_id = NodeId::new(); let node_id = NodeId::new();
self.network_interface.insert_node(node_id, flatten_path_definition.default_node_template(), &[]); self.network_interface.insert_node(node_id, combine_paths_definition.default_node_template(), &[]);
self.network_interface.move_node_to_chain_start(&node_id, output_layer, &[], self.import); self.network_interface.move_node_to_chain_start(&node_id, output_layer, &[], self.import);
} }
} }
@@ -806,9 +806,9 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
inputs: vec![NodeInput::node(NodeId(4), 0), NodeInput::node(NodeId(3), 0)], inputs: vec![NodeInput::node(NodeId(4), 0), NodeInput::node(NodeId(3), 0)],
..Default::default() ..Default::default()
}, },
// 6: Flatten Path // 6: Combine Paths
DocumentNode { DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(vector::flatten_path::IDENTIFIER), implementation: DocumentNodeImplementation::ProtoNode(vector::combine_paths::IDENTIFIER),
inputs: vec![NodeInput::node(NodeId(5), 0)], inputs: vec![NodeInput::node(NodeId(5), 0)],
..Default::default() ..Default::default()
}, },
@@ -883,7 +883,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
}, },
..Default::default() ..Default::default()
}, },
// 6: Flatten Path // 6: Combine Paths
DocumentNodeMetadata { DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata { persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(35, 0)), node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(35, 0)),
@@ -1239,7 +1239,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
description: Cow::Borrowed( description: Cow::Borrowed(
"Decomposes the X and Y components of a vec2.\n\ "Decomposes the X and Y components of a vec2.\n\
\n\ \n\
The inverse of this node is \"Vec2 Value\", which can have either or both its X and Y parameters exposed as graph inputs.", The inverse of this node is **Combine Vec2**, which composes a vec2 from its X and Y components.",
), ),
properties: None, properties: None,
}, },
@@ -294,8 +294,8 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
], ],
}, },
NodeReplacement { NodeReplacement {
node: graphene_std::math_nodes::length::IDENTIFIER, node: graphene_std::math_nodes::magnitude::IDENTIFIER,
aliases: &["graphene_math_nodes::LengthNode", "graphene_core::ops::LenghtNode"], aliases: &["math_nodes::LengthNode", "graphene_math_nodes::LengthNode", "graphene_core::ops::LenghtNode"],
}, },
NodeReplacement { NodeReplacement {
node: graphene_std::math_nodes::less_than::IDENTIFIER, node: graphene_std::math_nodes::less_than::IDENTIFIER,
@@ -420,6 +420,8 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
node: graphene_std::math_nodes::as_u_64::IDENTIFIER, node: graphene_std::math_nodes::as_u_64::IDENTIFIER,
aliases: &["graphene_math_nodes::ToU64Node", "graphene_core::ops::ToU64Node", "math_nodes::ToU64Node"], aliases: &["graphene_math_nodes::ToU64Node", "graphene_core::ops::ToU64Node", "math_nodes::ToU64Node"],
}, },
// The old 'Vec2 Value' node took separate X and Y inputs, a role now filled by 'Combine Vec2', while the new 'Vec2 Value' node takes a single vec2 input.
// Old references (including these older aliases) are remapped here to `vec_2_value::IDENTIFIER` so the per-node migration in `migrate_node` can detect the leftover 3-input shape and convert it into a 'Combine Vec2' node.
NodeReplacement { NodeReplacement {
node: graphene_std::math_nodes::vec_2_value::IDENTIFIER, node: graphene_std::math_nodes::vec_2_value::IDENTIFIER,
aliases: &[ aliases: &[
@@ -803,7 +805,7 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
aliases: &["graphene_core::vector::vector_nodes::FillNode", "graphene_core::vector::FillNode"], aliases: &["graphene_core::vector::vector_nodes::FillNode", "graphene_core::vector::FillNode"],
}, },
NodeReplacement { NodeReplacement {
node: graphene_std::vector::flatten_path::IDENTIFIER, node: graphene_std::vector::combine_paths::IDENTIFIER,
aliases: &[ aliases: &[
"graphene_core::vector::vector_nodes::FlattenPathNode", "graphene_core::vector::vector_nodes::FlattenPathNode",
"graphene_core::vector::FlattenVectorElementsNode", "graphene_core::vector::FlattenVectorElementsNode",
@@ -2021,6 +2023,20 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path); .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path);
} }
// Convert the old 'Vec2 Value' node, identified by its leftover 3-input shape with separate X and Y inputs,
// into the 'Combine Vec2' node which now fills that role (the new 'Vec2 Value' node instead takes a single vec2 input)
if reference == DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::vec_2_value::IDENTIFIER) && inputs_count == 3 {
let combine_vec2_reference = DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::combine_vec_2::IDENTIFIER);
let mut node_template = resolve_document_node_type(&combine_vec2_reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
}
// Upgrade the Mirror node to add the `keep_original` boolean input // Upgrade the Mirror node to add the `keep_original` boolean input
if reference == DefinitionIdentifier::ProtoNode(graphene_std::graphic::mirror::IDENTIFIER) && inputs_count == 3 { if reference == DefinitionIdentifier::ProtoNode(graphene_std::graphic::mirror::IDENTIFIER) && inputs_count == 3 {
let mut node_template = resolve_document_node_type(&reference)?.default_node_template(); let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
@@ -101,21 +101,21 @@ pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIde
delete_children: false, delete_children: false,
}); });
// Add a Flatten Path node after the merge // Add a Combine Paths node after the merge
let flatten_node_id = NodeId::new(); let combine_paths_node_id = NodeId::new();
let flatten_node = document_node_definitions::resolve_proto_node_type(graphene_std::vector::flatten_path::IDENTIFIER) let combine_paths_node = document_node_definitions::resolve_proto_node_type(graphene_std::vector::combine_paths::IDENTIFIER)
.expect("Failed to create flatten node") .expect("Failed to create combine paths node")
.default_node_template(); .default_node_template();
responses.add(NodeGraphMessage::InsertNode { responses.add(NodeGraphMessage::InsertNode {
node_id: flatten_node_id, node_id: combine_paths_node_id,
node_template: Box::new(flatten_node), node_template: Box::new(combine_paths_node),
}); });
responses.add(NodeGraphMessage::MoveNodeToChainStart { responses.add(NodeGraphMessage::MoveNodeToChainStart {
node_id: flatten_node_id, node_id: combine_paths_node_id,
parent: first_layer, parent: first_layer,
}); });
// Add a path node after the flatten node // Add a path node after the combine paths node
let path_node_id = NodeId::new(); let path_node_id = NodeId::new();
let path_node = document_node_definitions::resolve_network_node_type("Path") let path_node = document_node_definitions::resolve_network_node_type("Path")
.expect("Failed to create path node") .expect("Failed to create path node")
@@ -421,7 +421,7 @@ impl ShapeState {
(point.as_handle().is_some() && self.ignore_handles) || (point.as_anchor().is_some() && self.ignore_anchors) (point.as_handle().is_some() && self.ignore_handles) || (point.as_anchor().is_some() && self.ignore_anchors)
} }
/// Applies a dummy vector modification to the layer. In the case where a group containing some vector data is selected, this triggers the creation of a Flatten Path node. /// Applies a dummy vector modification to the layer. In the case where a group containing some vector data is selected, this triggers the creation of a Combine Paths node.
fn add_dummy_modification_to_trigger_graph_reorganization(layer: LayerNodeIdentifier, start_point: PointId, _end_point: PointId, responses: &mut VecDeque<Message>) { fn add_dummy_modification_to_trigger_graph_reorganization(layer: LayerNodeIdentifier, start_point: PointId, _end_point: PointId, responses: &mut VecDeque<Message>) {
// Apply a zero-delta to one of the points to trigger reorganization // Apply a zero-delta to one of the points to trigger reorganization
let dummy_modification = VectorModificationType::ApplyPointDelta { let dummy_modification = VectorModificationType::ApplyPointDelta {
@@ -1623,7 +1623,7 @@ impl Render for List<Vector> {
} }
// If this item carries a snapshot of upstream graphic content (e.g. it was produced by Boolean Operation, // If this item carries a snapshot of upstream graphic content (e.g. it was produced by Boolean Operation,
// Flatten Path, Morph, or any other destructive merge), recurse into that snapshot so the editor can // Combine Paths, Morph, or any other destructive merge), recurse into that snapshot so the editor can
// surface the original child layers' click targets. // surface the original child layers' click targets.
let upstream_nested_layers = self.attribute_cloned_or_default::<List<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, index); let upstream_nested_layers = self.attribute_cloned_or_default::<List<Graphic>>(ATTR_EDITOR_MERGED_LAYERS, index);
if !upstream_nested_layers.is_empty() { if !upstream_nested_layers.is_empty() {
+1 -1
View File
@@ -5,7 +5,7 @@ use glam::{DVec2, IVec2, UVec2};
/// Obtains the X or Y component of a vec2. /// Obtains the X or Y component of a vec2.
/// ///
/// The inverse of this node is "Vec2 Value", which can have either or both its X and Y parameters exposed as graph inputs. /// The inverse of this node is **Combine Vec2**, which composes a vec2 from its X and Y components.
#[node_macro::node(name("Extract XY"), category("Math: Vector"))] #[node_macro::node(name("Extract XY"), category("Math: Vector"))]
fn extract_xy<T: Into<DVec2>>(_: impl Ctx, #[implementations(DVec2, IVec2, UVec2)] vector: Item<T>, axis: Item<XY>) -> Item<f64> { fn extract_xy<T: Into<DVec2>>(_: impl Ctx, #[implementations(DVec2, IVec2, UVec2)] vector: Item<T>, axis: Item<XY>) -> Item<f64> {
let vector = vector.into_element(); let vector = vector.into_element();
+1 -1
View File
@@ -967,7 +967,7 @@ pub async fn flatten_vector<T: IntoGraphicList>(_: impl Ctx, #[implementations(L
// TODO: we stash the pre-flattened list on the output so `List<Vector>::collect_metadata` can recurse into it, // TODO: we stash the pre-flattened list on the output so `List<Vector>::collect_metadata` can recurse into it,
// TODO: which conflates render output with editor metadata and forces the pre-compensation dance below. // TODO: which conflates render output with editor metadata and forces the pre-compensation dance below.
// TODO: The cleaner fix is to drive each layer's metadata from its own Monitor's captured `(Context, List<Graphic>)`, // TODO: The cleaner fix is to drive each layer's metadata from its own Monitor's captured `(Context, List<Graphic>)`,
// TODO: at which point this attribute (and the equivalents in Boolean Operation, Solidify Stroke, Flatten Path, // TODO: at which point this attribute (and the equivalents in Boolean Operation, Solidify Stroke, Combine Paths,
// TODO: Morph, Rasterize) become unnecessary. // TODO: Morph, Rasterize) become unnecessary.
if !output.is_empty() { if !output.is_empty() {
// Item 0 carries a composed transform inherited from the flattened input, but the merged_layers // Item 0 carries a composed transform inherited from the flattened input, but the merged_layers
+22 -6
View File
@@ -975,8 +975,8 @@ fn percentage_value(_: impl Ctx, _primary: (), percentage: Item<Percentage>) ->
/// Constructs a two-dimensional vector value which may be set to any XY pair. /// Constructs a two-dimensional vector value which may be set to any XY pair.
#[node_macro::node(category("Value"), name("Vec2 Value"))] #[node_macro::node(category("Value"), name("Vec2 Value"))]
fn vec2_value(_: impl Ctx, _primary: (), x: Item<f64>, y: Item<f64>) -> Item<DVec2> { fn vec2_value(_: impl Ctx, _primary: (), #[name("Vec2")] vec2: Item<DVec2>) -> Item<DVec2> {
Item::new_from_element(DVec2::new(*x.element(), *y.element())) vec2
} }
/// Constructs a color value which may be set to any color. /// Constructs a color value which may be set to any color.
@@ -1073,6 +1073,23 @@ fn footprint_value(_: impl Ctx, _primary: (), transform: Item<DAffine2>, #[defau
}) })
} }
/// Composes a vec2 from its X and Y components.
///
/// The inverse of this node is **Split Vec2**, which decomposes a vec2 back into its X and Y components.
#[node_macro::node(category("Math: Vector"), name("Combine Vec2"))]
fn combine_vec2(
_: impl Ctx,
_primary: (),
/// The X component of the vec2.
#[expose]
x: Item<f64>,
/// The Y component of the vec2.
#[expose]
y: Item<f64>,
) -> Item<DVec2> {
Item::new_from_element(DVec2::new(*x.element(), *y.element()))
}
/// The dot product operation (`·`) calculates the degree of similarity of a vec2 pair based on their angles and lengths. /// The dot product operation (`·`) calculates the degree of similarity of a vec2 pair based on their angles and lengths.
/// ///
/// Calculated as `‖a‖‖b‖cos(θ)`, it represents the product of their lengths (`‖a‖‖b‖`) scaled by the alignment of their directions (`cos(θ)`). /// Calculated as `‖a‖‖b‖cos(θ)`, it represents the product of their lengths (`‖a‖‖b‖`) scaled by the alignment of their directions (`cos(θ)`).
@@ -1152,10 +1169,9 @@ fn angle_to<T: ToPosition, U: ToPosition>(
Item::from_parts(result, attributes) Item::from_parts(result, attributes)
} }
// TODO: Rename to "Magnitude"
/// The magnitude operator (`‖x‖`) calculates the length of a vec2, which is the distance from the base to the tip of the arrow represented by the vector. /// The magnitude operator (`‖x‖`) calculates the length of a vec2, which is the distance from the base to the tip of the arrow represented by the vector.
#[node_macro::node(category("Math: Vector"))] #[node_macro::node(category("Math: Vector"))]
fn length(_: impl Ctx, vector: Item<DVec2>) -> Item<f64> { fn magnitude(_: impl Ctx, vector: Item<DVec2>) -> Item<f64> {
let (vector, attributes) = vector.into_parts(); let (vector, attributes) = vector.into_parts();
Item::from_parts(vector.length(), attributes) Item::from_parts(vector.length(), attributes)
@@ -1185,9 +1201,9 @@ mod test {
} }
#[test] #[test]
pub fn length_function() { pub fn magnitude_function() {
let vector = Item::new_from_element(DVec2::new(3., 4.)); let vector = Item::new_from_element(DVec2::new(3., 4.));
assert_eq!(length((), vector).into_element(), 5.); assert_eq!(magnitude((), vector).into_element(), 5.);
} }
#[test] #[test]
+4 -4
View File
@@ -321,7 +321,7 @@ mod test {
Item::new_from_element(count), Item::new_from_element(count),
) )
.await; .await;
let vector_list = List::new_from_item(vector_nodes::flatten_path(Footprint::default(), List::new_from_element(Graphic::Vector(repeated))).await); let vector_list = List::new_from_item(vector_nodes::combine_paths(Footprint::default(), List::new_from_element(Graphic::Vector(repeated))).await);
let vector = vector_list.element(0).unwrap(); let vector = vector_list.element(0).unwrap();
assert_eq!(vector.region_manipulator_groups().count(), 3); assert_eq!(vector.region_manipulator_groups().count(), 3);
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() { for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {
@@ -340,7 +340,7 @@ mod test {
Item::new_from_element(1), Item::new_from_element(1),
) )
.await; .await;
let vector_list = List::new_from_item(vector_nodes::flatten_path(Footprint::default(), List::new_from_element(Graphic::Vector(repeated))).await); let vector_list = List::new_from_item(vector_nodes::combine_paths(Footprint::default(), List::new_from_element(Graphic::Vector(repeated))).await);
let vector = vector_list.element(0).unwrap(); let vector = vector_list.element(0).unwrap();
assert_eq!(vector.region_manipulator_groups().count(), 1); assert_eq!(vector.region_manipulator_groups().count(), 1);
@@ -362,7 +362,7 @@ mod test {
Item::new_from_element(count), Item::new_from_element(count),
) )
.await; .await;
let vector_list = List::new_from_item(vector_nodes::flatten_path(Footprint::default(), List::new_from_element(Graphic::Vector(repeated))).await); let vector_list = List::new_from_item(vector_nodes::combine_paths(Footprint::default(), List::new_from_element(Graphic::Vector(repeated))).await);
let vector = vector_list.element(0).unwrap(); let vector = vector_list.element(0).unwrap();
assert_eq!(vector.region_manipulator_groups().count(), 8); assert_eq!(vector.region_manipulator_groups().count(), 8);
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() { for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {
@@ -381,7 +381,7 @@ mod test {
Item::new_from_element(8), Item::new_from_element(8),
) )
.await; .await;
let vector_list = List::new_from_item(vector_nodes::flatten_path(Footprint::default(), List::new_from_element(Graphic::Vector(repeated))).await); let vector_list = List::new_from_item(vector_nodes::combine_paths(Footprint::default(), List::new_from_element(Graphic::Vector(repeated))).await);
let vector = vector_list.element(0).unwrap(); let vector = vector_list.element(0).unwrap();
assert_eq!(vector.region_manipulator_groups().count(), 8); assert_eq!(vector.region_manipulator_groups().count(), 8);
+5 -6
View File
@@ -1560,9 +1560,8 @@ async fn map_points(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: Item<Vec
} }
/// Combines every vector path across the input into a single compound path. /// Combines every vector path across the input into a single compound path.
// TODO: Rename to "Combine Paths" with a document migration
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
pub async fn flatten_path<T: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<Vector>)] content: T) -> Item<Vector> { pub async fn combine_paths<T: IntoGraphicList>(_: impl Ctx, #[implementations(List<Graphic>, List<Vector>)] content: T) -> Item<Vector> {
let graphic_list = content.into_graphic_list(); let graphic_list = content.into_graphic_list();
let flattened = graphic_list.clone().into_flattened_list::<Vector>(); let flattened = graphic_list.clone().into_flattened_list::<Vector>();
@@ -3597,12 +3596,12 @@ mod test {
Item::new_from_element(0), Item::new_from_element(0),
) )
.await; .await;
let flatten_path = List::new_from_item(super::flatten_path(Footprint::default(), List::new_from_element(Graphic::Vector(copy_to_points))).await); let combined = List::new_from_item(super::combine_paths(Footprint::default(), List::new_from_element(Graphic::Vector(copy_to_points))).await);
let flattened_copy_to_points = flatten_path.element(0).unwrap(); let combined_copy_to_points = combined.element(0).unwrap();
assert_eq!(flattened_copy_to_points.region_manipulator_groups().count(), expected_points.len()); assert_eq!(combined_copy_to_points.region_manipulator_groups().count(), expected_points.len());
for (index, (_, manipulator_groups)) in flattened_copy_to_points.region_manipulator_groups().enumerate() { for (index, (_, manipulator_groups)) in combined_copy_to_points.region_manipulator_groups().enumerate() {
let offset = expected_points[index]; let offset = expected_points[index];
let manipulator_groups_anchors = manipulator_groups.iter().map(|manipulators| manipulators.anchor).collect::<Vec<DVec2>>(); let manipulator_groups_anchors = manipulator_groups.iter().map(|manipulators| manipulators.anchor).collect::<Vec<DVec2>>();
assert_eq!( assert_eq!(