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 Dennis Kobert
parent 095f169b2f
commit 83d24251d7
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

View File

@@ -433,17 +433,17 @@ impl<'a> ModifyInputsContext<'a> {
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.
if node_definition.identifier == "Path" {
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>)) {
let Some(flatten_path_definition) = resolve_proto_node_type(graphene_std::vector_nodes::flatten_path::IDENTIFIER) else {
log::error!("Flatten Path does not exist in ModifyInputsContext::existing_node_id");
let Some(combine_paths_definition) = resolve_proto_node_type(graphene_std::vector_nodes::combine_paths::IDENTIFIER) else {
log::error!("Combine Paths does not exist in ModifyInputsContext::existing_node_id");
return None;
};
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);
}
}

View File

@@ -840,9 +840,9 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
inputs: vec![NodeInput::node(NodeId(4), 0), NodeInput::node(NodeId(3), 0)],
..Default::default()
},
// 6: Flatten Path
// 6: Combine Paths
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(vector::flatten_path::IDENTIFIER),
implementation: DocumentNodeImplementation::ProtoNode(vector::combine_paths::IDENTIFIER),
inputs: vec![NodeInput::node(NodeId(5), 0)],
..Default::default()
},
@@ -917,7 +917,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
},
..Default::default()
},
// 6: Flatten Path
// 6: Combine Paths
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(35, 0)),
@@ -1285,7 +1285,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
description: Cow::Borrowed(
"Decomposes the X and Y components of a vec2.\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,
},

View File

@@ -297,8 +297,8 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
],
},
NodeReplacement {
node: graphene_std::math_nodes::length::IDENTIFIER,
aliases: &["graphene_math_nodes::LengthNode", "graphene_core::ops::LenghtNode"],
node: graphene_std::math_nodes::magnitude::IDENTIFIER,
aliases: &["math_nodes::LengthNode", "graphene_math_nodes::LengthNode", "graphene_core::ops::LenghtNode"],
},
NodeReplacement {
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,
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 {
node: graphene_std::math_nodes::vec_2_value::IDENTIFIER,
aliases: &[
@@ -806,7 +808,7 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
aliases: &["graphene_core::vector::vector_nodes::FillNode", "graphene_core::vector::FillNode"],
},
NodeReplacement {
node: graphene_std::vector::flatten_path::IDENTIFIER,
node: graphene_std::vector::combine_paths::IDENTIFIER,
aliases: &[
"graphene_core::vector::vector_nodes::FlattenPathNode",
"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);
}
// 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
if reference == DefinitionIdentifier::ProtoNode(graphene_std::graphic::mirror::IDENTIFIER) && inputs_count == 3 {
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();

View File

@@ -100,21 +100,21 @@ pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIde
delete_children: false,
});
// Add a Flatten Path node after the merge
let flatten_node_id = NodeId::new();
let flatten_node = document_node_definitions::resolve_proto_node_type(graphene_std::vector::flatten_path::IDENTIFIER)
.expect("Failed to create flatten node")
// Add a Combine Paths node after the merge
let combine_paths_node_id = NodeId::new();
let combine_paths_node = document_node_definitions::resolve_proto_node_type(graphene_std::vector::combine_paths::IDENTIFIER)
.expect("Failed to create combine paths node")
.default_node_template();
responses.add(NodeGraphMessage::InsertNode {
node_id: flatten_node_id,
node_template: Box::new(flatten_node),
node_id: combine_paths_node_id,
node_template: Box::new(combine_paths_node),
});
responses.add(NodeGraphMessage::MoveNodeToChainStart {
node_id: flatten_node_id,
node_id: combine_paths_node_id,
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 = document_node_definitions::resolve_network_node_type("Path")
.expect("Failed to create path node")

View File

@@ -421,7 +421,7 @@ impl ShapeState {
(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>) {
// Apply a zero-delta to one of the points to trigger reorganization
let dummy_modification = VectorModificationType::ApplyPointDelta {

View File

@@ -42,9 +42,9 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
);
// The path flattening's plain vector rows, served under its identifier.
node_types.extend(
graphene_std::vector::flatten_path_vector_entries()
graphene_std::vector::combine_paths_vector_entries()
.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.
node_types.extend(

View File

@@ -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,
// 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.
if let Some(upstream_nested_layers) = source.attr::<EditorMergedLayers>(index).filter(|layers| !layers.is_empty()) {
let mut upstream_footprint = footprint;

View File

@@ -4,7 +4,7 @@ use glam::{DVec2, IVec2, UVec2};
/// 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"))]
fn extract_xy<T: Into<DVec2>>(_: impl Ctx, #[implementations(DVec2, IVec2, UVec2)] vector: T, axis: XY) -> f64 {
match axis {

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: `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: 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.
/// 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

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.
#[node_macro::node(category("Value"), name("Vec2 Value"))]
fn vec2_value(_: impl Ctx, _primary: (), x: f64, y: f64) -> DVec2 {
DVec2::new(x, y)
fn vec2_value(_: impl Ctx, _primary: (), #[name("Vec2")] vec2: DVec2) -> DVec2 {
vec2
}
/// 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.
///
/// 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() }
}
// 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.
#[node_macro::node(category("Math: Vector"))]
fn length(_: impl Ctx, vector: DVec2) -> f64 {
fn magnitude(_: impl Ctx, vector: DVec2) -> f64 {
vector.length()
}
@@ -991,9 +1007,9 @@ mod test {
}
#[test]
pub fn length_function() {
pub fn magnitude_function() {
let vector = DVec2::new(3., 4.);
assert_eq!(length(&(), vector), 5.);
assert_eq!(magnitude(&(), vector), 5.);
}
#[test]

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))]
pub fn flatten_path<'e>(
pub fn combine_paths<'e>(
ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
content: IList<Graphic<'static>>,
) -> Result<
@@ -2087,10 +2087,10 @@ pub fn flatten_path<'e>(
Ok((content.lane(carrier).map_element(element), transform, fill, stroke, layer_path, merged))
}
/// The path flattening over a plain vector level, as [`flatten_path`].
/// Registered under the flatten path identifier.
/// The path flattening over a plain vector level, as [`combine_paths`].
/// Registered under the combine paths identifier.
#[node_macro::node(category(""))]
pub fn flatten_path_vector<'e>(
pub fn combine_paths_vector<'e>(
ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
content: IList<Vector>,
) -> Result<
@@ -2118,7 +2118,7 @@ pub fn flatten_path_vector<'e>(
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.
#[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]);
}
}
#[test]
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.))]);