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-09-15 14:35:48 +02:00
committed by Dennis Kobert
parent 9556904aed
commit 4f49c000e8
14 changed files with 73 additions and 40 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
@@ -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_nested_type() == Some(&concrete!(List<Graphic>)) { if layer_input_type.compiled_nested_type() == Some(&concrete!(List<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);
} }
} }
@@ -840,9 +840,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()
}, },
@@ -917,7 +917,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)),
@@ -1285,7 +1285,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,
}, },
@@ -297,8 +297,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,
@@ -423,6 +423,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: &[
@@ -806,7 +808,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",
@@ -2068,6 +2070,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();
@@ -100,21 +100,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 {
@@ -42,9 +42,9 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
); );
// The path flattening's plain vector rows, served under its identifier. // The path flattening's plain vector rows, served under its identifier.
node_types.extend( node_types.extend(
graphene_std::vector::flatten_path_vector_entries() graphene_std::vector::combine_paths_vector_entries()
.into_iter() .into_iter()
.map(|entry| (graphene_std::vector::flatten_path::IDENTIFIER.clone(), entry)), .map(|entry| (graphene_std::vector::combine_paths::IDENTIFIER.clone(), entry)),
); );
// The solidify's plain vector rows, served under its identifier. // The solidify's plain vector rows, served under its identifier.
node_types.extend( node_types.extend(
@@ -1830,7 +1830,7 @@ fn collect_vector_metadata<S: LaneSource<Element = Vector>>(source: &S, metadata
} }
// 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.
if let Some(upstream_nested_layers) = source.attr::<EditorMergedLayers>(index).filter(|layers| !layers.is_empty()) { if let Some(upstream_nested_layers) = source.attr::<EditorMergedLayers>(index).filter(|layers| !layers.is_empty()) {
let mut upstream_footprint = footprint; let mut upstream_footprint = footprint;
+1 -1
View File
@@ -4,7 +4,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: T, axis: XY) -> f64 { fn extract_xy<T: Into<DVec2>>(_: impl Ctx, #[implementations(DVec2, IVec2, UVec2)] vector: T, axis: XY) -> f64 {
match axis { match axis {
+1 -1
View File
@@ -399,7 +399,7 @@ fn vector_row_count(level: GraphicLevel<'_>) -> usize {
// TODO: Flattening erases the upstream `Graphic` hierarchy that editor metadata collection walks to populate // TODO: Flattening erases the upstream `Graphic` hierarchy that editor metadata collection walks to populate
// TODO: `upstream_footprints` / `local_transforms` / `click_targets` per child layer, so the pre-flattened list // TODO: `upstream_footprints` / `local_transforms` / `click_targets` per child layer, so the pre-flattened list
// TODO: is stashed on row 0 for `collect_metadata` to recurse into (as Boolean Operation, Solidify Stroke, // TODO: is stashed on row 0 for `collect_metadata` to recurse into (as Boolean Operation, Solidify Stroke,
// TODO: Flatten Path, Morph and Rasterize do). Driving each layer's metadata from its own Monitor's captured // TODO: Combine Paths, Morph and Rasterize do). Driving each layer's metadata from its own Monitor's captured
// TODO: `(Context, List<Graphic>)` would make this attribute unnecessary. // TODO: `(Context, List<Graphic>)` would make this attribute unnecessary.
/// The parked merged-layers snapshot for row 0. Row 0 carries a composed /// The parked merged-layers snapshot for row 0. Row 0 carries a composed
/// transform the snapshot's own transforms already include, so the snapshot is /// transform the snapshot's own transforms already include, so the snapshot is
+22 -6
View File
@@ -802,8 +802,8 @@ fn percentage_value(_: impl Ctx, _primary: (), percentage: Percentage) -> f64 {
/// 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: f64, y: f64) -> DVec2 { fn vec2_value(_: impl Ctx, _primary: (), #[name("Vec2")] vec2: DVec2) -> DVec2 {
DVec2::new(x, y) vec2
} }
/// Constructs a color value which may be set to any color. /// Constructs a color value which may be set to any color.
@@ -896,6 +896,23 @@ fn footprint_value(_: impl Ctx, _primary: (), transform: DAffine2, #[default(100
} }
} }
/// 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: f64,
/// The Y component of the vec2.
#[expose]
y: f64,
) -> DVec2 {
DVec2::new(x, y)
}
/// 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(θ)`).
@@ -964,10 +981,9 @@ fn angle_to<T: ToPosition, U: ToPosition>(
if radians { angle } else { angle.to_degrees() } if radians { angle } else { angle.to_degrees() }
} }
// 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: DVec2) -> f64 { fn magnitude(_: impl Ctx, vector: DVec2) -> f64 {
vector.length() vector.length()
} }
@@ -991,9 +1007,9 @@ mod test {
} }
#[test] #[test]
pub fn length_function() { pub fn magnitude_function() {
let vector = DVec2::new(3., 4.); let vector = DVec2::new(3., 4.);
assert_eq!(length(&(), vector), 5.); assert_eq!(magnitude(&(), vector), 5.);
} }
#[test] #[test]
+7 -6
View File
@@ -2057,9 +2057,9 @@ fn flatten_path_core<'e>(
)) ))
} }
// TODO: Rename to "Combine Paths" and make this happen per-element instead of flattening every element into a single path. The migration for this should then become a Flatten Vector -> Combine Paths pair of nodes. // TODO: Make this happen per-element instead of flattening every element into a single path. The migration for this should then become a Flatten Vector -> Combine Paths pair of nodes.
#[node_macro::node(category("Vector"), path(graphene_core::vector))] #[node_macro::node(category("Vector"), path(graphene_core::vector))]
pub fn flatten_path<'e>( pub fn combine_paths<'e>(
ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy, ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
content: IList<Graphic<'static>>, content: IList<Graphic<'static>>,
) -> Result< ) -> Result<
@@ -2087,10 +2087,10 @@ pub fn flatten_path<'e>(
Ok((content.lane(carrier).map_element(element), transform, fill, stroke, layer_path, merged)) Ok((content.lane(carrier).map_element(element), transform, fill, stroke, layer_path, merged))
} }
/// The path flattening over a plain vector level, as [`flatten_path`]. /// The path flattening over a plain vector level, as [`combine_paths`].
/// Registered under the flatten path identifier. /// Registered under the combine paths identifier.
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
pub fn flatten_path_vector<'e>( pub fn combine_paths_vector<'e>(
ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy, ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
content: IList<Vector>, content: IList<Vector>,
) -> Result< ) -> Result<
@@ -2118,7 +2118,7 @@ pub fn flatten_path_vector<'e>(
Ok((content.lane(carrier).map_element(element), transform, fill, stroke, layer_path, merged)) Ok((content.lane(carrier).map_element(element), transform, fill, stroke, layer_path, merged))
} }
pub use _flatten_path_vector_mod::flatten_path_vector_entries; pub use _combine_paths_vector_mod::combine_paths_vector_entries;
/// Convert vector geometry into a polyline composed of evenly spaced points. /// Convert vector geometry into a polyline composed of evenly spaced points.
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector), properties("sample_polyline_properties"), memoize)] #[node_macro::node(category("Vector: Modifier"), path(core_types::vector), properties("sample_polyline_properties"), memoize)]
@@ -4137,6 +4137,7 @@ mod test {
assert_eq!(manipulator_groups_anchors[i], expected_bounding_box[i]); assert_eq!(manipulator_groups_anchors[i], expected_bounding_box[i]);
} }
} }
#[test] #[test]
fn sample_polyline() { fn sample_polyline() {
let path = BezPath::from_vec(vec![PathEl::MoveTo(Point::ZERO), PathEl::CurveTo(Point::ZERO, Point::new(100., 0.), Point::new(100., 0.))]); let path = BezPath::from_vec(vec![PathEl::MoveTo(Point::ZERO), PathEl::CurveTo(Point::ZERO, Point::new(100., 0.), Point::new(100., 0.))]);