Add upgrade script to convert "Spline" node to "Path" -> "Spline from Points" (#2274)

* write document upgrade code to transfrom Spline node into Path -> Spline from Points

* fix only connecting to single output

* shift position of newly inserted Path -> Spline from Points node

* refactor

* remove all old Spline node code

* rename Spline from Points node to Spline

* Code cleanup

* Update the demo art to natively use the new Spline node

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Priyanshu 2025-02-13 05:15:30 +05:30 committed by GitHub
parent 95bbc95606
commit 2d90bb0cbf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 87 additions and 40 deletions

File diff suppressed because one or more lines are too long

View file

@ -2012,29 +2012,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("TODO"),
properties: None,
},
DocumentNodeDefinition {
identifier: "Spline",
category: "Vector: Shape",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::vector::generator_nodes::SplineNode"),
manual_composition: Some(concrete!(())),
inputs: vec![
NodeInput::value(TaggedValue::None, false),
// TODO: Modify the proto node generation macro to accept this default value, then remove this definition for Spline
NodeInput::value(TaggedValue::VecDVec2(vec![DVec2::new(0., -50.), DVec2::new(25., 0.), DVec2::new(0., 50.)]), false),
],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_properties: vec!["None".into(), PropertiesRow::with_override("Points", WidgetOverride::Custom("spline_input".to_string()))],
output_names: vec!["Vector".to_string()],
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
properties: None,
},
DocumentNodeDefinition {
identifier: "Path",
category: "Vector",

View file

@ -16,10 +16,13 @@ use crate::messages::prelude::*;
use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType};
use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};
use bezier_rs::Subpath;
use glam::IVec2;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput};
use graphene_core::text::{Font, TypesettingConfig};
use graphene_std::vector::style::{Fill, FillType, Gradient};
use graphene_std::vector::{VectorData, VectorDataTable};
use interpreted_executor::dynamic_executor::IntrospectError;
use std::sync::Arc;
@ -480,6 +483,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
("graphene_core::raster::VibranceNode", "graphene_core::raster::adjustments::VibranceNode"),
("graphene_core::text::TextGeneratorNode", "graphene_core::text::TextNode"),
("graphene_core::transform::SetTransformNode", "graphene_core::transform::ReplaceTransformNode"),
("graphene_core::vector::SplinesFromPointsNode", "graphene_core::vector::SplineNode"),
("graphene_core::vector::generator_nodes::EllipseGenerator", "graphene_core::vector::generator_nodes::EllipseNode"),
("graphene_core::vector::generator_nodes::LineGenerator", "graphene_core::vector::generator_nodes::LineNode"),
("graphene_core::vector::generator_nodes::PathGenerator", "graphene_core::vector::generator_nodes::PathNode"),
@ -488,7 +492,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
"graphene_core::vector::generator_nodes::RegularPolygonGenerator",
"graphene_core::vector::generator_nodes::RegularPolygonNode",
),
("graphene_core::vector::generator_nodes::SplineGenerator", "graphene_core::vector::generator_nodes::SplineNode"),
("graphene_core::vector::generator_nodes::StarGenerator", "graphene_core::vector::generator_nodes::StarNode"),
("graphene_std::executor::BlendGpuImageNode", "graphene_std::gpu_nodes::BlendGpuImageNode"),
("graphene_std::raster::SampleNode", "graphene_std::raster::SampleImageNode"),
@ -637,6 +640,77 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
}
}
// Rename the old "Splines from Points" node to "Spline" and upgrade it to the new "Spline" node
if reference == "Splines from Points" {
document.network_interface.set_reference(node_id, &[], Some("Spline".to_string()));
}
// Upgrade the old "Spline" node to the new "Spline" node
if reference == "Spline" {
// Retrieve the proto node identifier and verify it is the old "Spline" node, otherwise skip it if this is the new "Spline" node
let identifier = document.network_interface.implementation(node_id, &[]).and_then(|implementation| implementation.get_proto_node());
if identifier.map(|identifier| &identifier.name) != Some(&"graphene_core::vector::generator_nodes::SplineNode".into()) {
continue;
}
// Obtain the document node for the given node ID, extract the vector points, and create vector data from the list of points
let node = document.network_interface.document_node(node_id, &[]).unwrap();
let Some(TaggedValue::VecDVec2(points)) = node.inputs.get(1).and_then(|tagged_value| tagged_value.as_value()) else {
log::error!("The old Spline node's input at index 1 is not a TaggedValue::VecDVec2");
continue;
};
let vector_data = VectorData::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false));
// Retrieve the output connectors linked to the "Spline" node's output port
let spline_outputs = document
.network_interface
.outward_wires(&[])
.unwrap()
.get(&OutputConnector::node(*node_id, 0))
.expect("Vec of InputConnector Spline node is connected to its output port 0.")
.clone();
// Get the node's current position in the graph
let Some(node_position) = document.network_interface.position(node_id, &[]) else {
log::error!("Could not get position of spline node.");
continue;
};
// Get the "Path" node definition and fill it in with the vector data and default vector modification
let path_node_type = resolve_document_node_type("Path").expect("Path node does not exist.");
let path_node = path_node_type.node_template_input_override([
Some(NodeInput::value(TaggedValue::VectorData(VectorDataTable::new(vector_data)), true)),
Some(NodeInput::value(TaggedValue::VectorModification(Default::default()), false)),
]);
// Get the "Spline" node definition and wire it up with the "Path" node as input
let spline_node_type = resolve_document_node_type("Spline").expect("Spline node does not exist.");
let spline_node = spline_node_type.node_template_input_override([Some(NodeInput::node(NodeId(1), 0))]);
// Create a new node group with the "Path" and "Spline" nodes and generate new node IDs for them
let nodes = vec![(NodeId(1), path_node), (NodeId(0), spline_node)];
let new_ids = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect::<HashMap<_, _>>();
let new_spline_id = *new_ids.get(&NodeId(0)).unwrap();
let new_path_id = *new_ids.get(&NodeId(1)).unwrap();
// Remove the old "Spline" node from the document
document.network_interface.delete_nodes(vec![*node_id], false, &[]);
// Insert the new "Path" and "Spline" nodes into the network interface with generated IDs
document.network_interface.insert_node_group(nodes.clone(), new_ids, &[]);
// Reposition the new "Spline" node to match the original "Spline" node's position
document.network_interface.shift_node(&new_spline_id, node_position, &[]);
// Reposition the new "Path" node with an offset relative to the original "Spline" node's position
document.network_interface.shift_node(&new_path_id, node_position + IVec2::new(-7, 0), &[]);
// Redirect each output connection from the old node to the new "Spline" node's output port
for input_connector in spline_outputs {
document.network_interface.set_input(&input_connector, NodeInput::node(new_spline_id, 0), &[]);
}
}
// Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016
if reference == "Text" && inputs_count != 8 {
let node_definition = resolve_document_node_type(reference).unwrap();

View file

@ -283,7 +283,7 @@ impl Fsm for SplineToolFsmState {
let path_node_type = resolve_document_node_type("Path").expect("Path node does not exist");
let path_node = path_node_type.default_node_template();
let spline_node_type = resolve_document_node_type("Splines from Points").expect("Spline from Points node does not exist");
let spline_node_type = resolve_document_node_type("Spline").expect("Spline node does not exist");
let spline_node = spline_node_type.node_template_input_override([Some(NodeInput::node(NodeId(1), 0))]);
let nodes = vec![(NodeId(1), path_node), (NodeId(0), spline_node)];

View file

@ -106,17 +106,6 @@ fn line<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary:
VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end)))
}
#[node_macro::node(category("Vector: Shape"))]
fn spline<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), points: Vec<DVec2>) -> VectorDataTable {
let mut spline = VectorData::from_subpath(Subpath::new_cubic_spline(points));
for pair in spline.segment_domain.ids().windows(2) {
spline.colinear_manipulators.push([HandleId::end(pair[0]), HandleId::primary(pair[1])]);
}
VectorDataTable::new(spline)
}
// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node
#[node_macro::node(category(""))]
fn path<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> VectorDataTable {

View file

@ -864,8 +864,8 @@ async fn subpath_segment_lengths<F: 'n + Send>(
.collect()
}
#[node_macro::node(name("Splines from Points"), category("Vector"), path(graphene_core::vector))]
async fn splines_from_points<F: 'n + Send>(
#[node_macro::node(name("Spline"), category("Vector"), path(graphene_core::vector))]
async fn spline<F: 'n + Send>(
#[implementations(
(),
Footprint,
@ -1431,7 +1431,7 @@ mod test {
}
#[tokio::test]
async fn spline() {
let spline = splines_from_points(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await;
let spline = super::spline(Footprint::default(), &vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE * 100.))).await;
let spline = spline.one_item();
assert_eq!(spline.stroke_bezier_paths().count(), 1);
assert_eq!(spline.point_domain.positions(), &[DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]);

View file

@ -576,6 +576,13 @@ impl DocumentNodeImplementation {
}
}
pub fn get_proto_node(&self) -> Option<&ProtoNodeIdentifier> {
match self {
DocumentNodeImplementation::ProtoNode(p) => Some(p),
_ => None,
}
}
pub const fn proto(name: &'static str) -> Self {
Self::ProtoNode(ProtoNodeIdentifier::new(name))
}