Skip to content

Commit 2d90bb0

Browse files
indierustyKeavon
andauthored
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 <[email protected]>
1 parent 95bbc95 commit 2d90bb0

File tree

7 files changed

+87
-40
lines changed

7 files changed

+87
-40
lines changed

demo-artwork/procedural-string-lights.graphite

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs

-23
Original file line numberDiff line numberDiff line change
@@ -2012,29 +2012,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
20122012
description: Cow::Borrowed("TODO"),
20132013
properties: None,
20142014
},
2015-
DocumentNodeDefinition {
2016-
identifier: "Spline",
2017-
category: "Vector: Shape",
2018-
node_template: NodeTemplate {
2019-
document_node: DocumentNode {
2020-
implementation: DocumentNodeImplementation::proto("graphene_core::vector::generator_nodes::SplineNode"),
2021-
manual_composition: Some(concrete!(())),
2022-
inputs: vec![
2023-
NodeInput::value(TaggedValue::None, false),
2024-
// TODO: Modify the proto node generation macro to accept this default value, then remove this definition for Spline
2025-
NodeInput::value(TaggedValue::VecDVec2(vec![DVec2::new(0., -50.), DVec2::new(25., 0.), DVec2::new(0., 50.)]), false),
2026-
],
2027-
..Default::default()
2028-
},
2029-
persistent_node_metadata: DocumentNodePersistentMetadata {
2030-
input_properties: vec!["None".into(), PropertiesRow::with_override("Points", WidgetOverride::Custom("spline_input".to_string()))],
2031-
output_names: vec!["Vector".to_string()],
2032-
..Default::default()
2033-
},
2034-
},
2035-
description: Cow::Borrowed("TODO"),
2036-
properties: None,
2037-
},
20382015
DocumentNodeDefinition {
20392016
identifier: "Path",
20402017
category: "Vector",

editor/src/messages/portfolio/portfolio_message_handler.rs

+75-1
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@ use crate::messages::prelude::*;
1616
use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType};
1717
use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};
1818

19+
use bezier_rs::Subpath;
20+
use glam::IVec2;
1921
use graph_craft::document::value::TaggedValue;
2022
use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput};
2123
use graphene_core::text::{Font, TypesettingConfig};
2224
use graphene_std::vector::style::{Fill, FillType, Gradient};
25+
use graphene_std::vector::{VectorData, VectorDataTable};
2326
use interpreted_executor::dynamic_executor::IntrospectError;
2427

2528
use std::sync::Arc;
@@ -480,6 +483,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
480483
("graphene_core::raster::VibranceNode", "graphene_core::raster::adjustments::VibranceNode"),
481484
("graphene_core::text::TextGeneratorNode", "graphene_core::text::TextNode"),
482485
("graphene_core::transform::SetTransformNode", "graphene_core::transform::ReplaceTransformNode"),
486+
("graphene_core::vector::SplinesFromPointsNode", "graphene_core::vector::SplineNode"),
483487
("graphene_core::vector::generator_nodes::EllipseGenerator", "graphene_core::vector::generator_nodes::EllipseNode"),
484488
("graphene_core::vector::generator_nodes::LineGenerator", "graphene_core::vector::generator_nodes::LineNode"),
485489
("graphene_core::vector::generator_nodes::PathGenerator", "graphene_core::vector::generator_nodes::PathNode"),
@@ -488,7 +492,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
488492
"graphene_core::vector::generator_nodes::RegularPolygonGenerator",
489493
"graphene_core::vector::generator_nodes::RegularPolygonNode",
490494
),
491-
("graphene_core::vector::generator_nodes::SplineGenerator", "graphene_core::vector::generator_nodes::SplineNode"),
492495
("graphene_core::vector::generator_nodes::StarGenerator", "graphene_core::vector::generator_nodes::StarNode"),
493496
("graphene_std::executor::BlendGpuImageNode", "graphene_std::gpu_nodes::BlendGpuImageNode"),
494497
("graphene_std::raster::SampleNode", "graphene_std::raster::SampleImageNode"),
@@ -637,6 +640,77 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
637640
}
638641
}
639642

643+
// Rename the old "Splines from Points" node to "Spline" and upgrade it to the new "Spline" node
644+
if reference == "Splines from Points" {
645+
document.network_interface.set_reference(node_id, &[], Some("Spline".to_string()));
646+
}
647+
648+
// Upgrade the old "Spline" node to the new "Spline" node
649+
if reference == "Spline" {
650+
// Retrieve the proto node identifier and verify it is the old "Spline" node, otherwise skip it if this is the new "Spline" node
651+
let identifier = document.network_interface.implementation(node_id, &[]).and_then(|implementation| implementation.get_proto_node());
652+
if identifier.map(|identifier| &identifier.name) != Some(&"graphene_core::vector::generator_nodes::SplineNode".into()) {
653+
continue;
654+
}
655+
656+
// Obtain the document node for the given node ID, extract the vector points, and create vector data from the list of points
657+
let node = document.network_interface.document_node(node_id, &[]).unwrap();
658+
let Some(TaggedValue::VecDVec2(points)) = node.inputs.get(1).and_then(|tagged_value| tagged_value.as_value()) else {
659+
log::error!("The old Spline node's input at index 1 is not a TaggedValue::VecDVec2");
660+
continue;
661+
};
662+
let vector_data = VectorData::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false));
663+
664+
// Retrieve the output connectors linked to the "Spline" node's output port
665+
let spline_outputs = document
666+
.network_interface
667+
.outward_wires(&[])
668+
.unwrap()
669+
.get(&OutputConnector::node(*node_id, 0))
670+
.expect("Vec of InputConnector Spline node is connected to its output port 0.")
671+
.clone();
672+
673+
// Get the node's current position in the graph
674+
let Some(node_position) = document.network_interface.position(node_id, &[]) else {
675+
log::error!("Could not get position of spline node.");
676+
continue;
677+
};
678+
679+
// Get the "Path" node definition and fill it in with the vector data and default vector modification
680+
let path_node_type = resolve_document_node_type("Path").expect("Path node does not exist.");
681+
let path_node = path_node_type.node_template_input_override([
682+
Some(NodeInput::value(TaggedValue::VectorData(VectorDataTable::new(vector_data)), true)),
683+
Some(NodeInput::value(TaggedValue::VectorModification(Default::default()), false)),
684+
]);
685+
686+
// Get the "Spline" node definition and wire it up with the "Path" node as input
687+
let spline_node_type = resolve_document_node_type("Spline").expect("Spline node does not exist.");
688+
let spline_node = spline_node_type.node_template_input_override([Some(NodeInput::node(NodeId(1), 0))]);
689+
690+
// Create a new node group with the "Path" and "Spline" nodes and generate new node IDs for them
691+
let nodes = vec![(NodeId(1), path_node), (NodeId(0), spline_node)];
692+
let new_ids = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect::<HashMap<_, _>>();
693+
let new_spline_id = *new_ids.get(&NodeId(0)).unwrap();
694+
let new_path_id = *new_ids.get(&NodeId(1)).unwrap();
695+
696+
// Remove the old "Spline" node from the document
697+
document.network_interface.delete_nodes(vec![*node_id], false, &[]);
698+
699+
// Insert the new "Path" and "Spline" nodes into the network interface with generated IDs
700+
document.network_interface.insert_node_group(nodes.clone(), new_ids, &[]);
701+
702+
// Reposition the new "Spline" node to match the original "Spline" node's position
703+
document.network_interface.shift_node(&new_spline_id, node_position, &[]);
704+
705+
// Reposition the new "Path" node with an offset relative to the original "Spline" node's position
706+
document.network_interface.shift_node(&new_path_id, node_position + IVec2::new(-7, 0), &[]);
707+
708+
// Redirect each output connection from the old node to the new "Spline" node's output port
709+
for input_connector in spline_outputs {
710+
document.network_interface.set_input(&input_connector, NodeInput::node(new_spline_id, 0), &[]);
711+
}
712+
}
713+
640714
// Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016
641715
if reference == "Text" && inputs_count != 8 {
642716
let node_definition = resolve_document_node_type(reference).unwrap();

editor/src/messages/tool/tool_messages/spline_tool.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ impl Fsm for SplineToolFsmState {
283283

284284
let path_node_type = resolve_document_node_type("Path").expect("Path node does not exist");
285285
let path_node = path_node_type.default_node_template();
286-
let spline_node_type = resolve_document_node_type("Splines from Points").expect("Spline from Points node does not exist");
286+
let spline_node_type = resolve_document_node_type("Spline").expect("Spline node does not exist");
287287
let spline_node = spline_node_type.node_template_input_override([Some(NodeInput::node(NodeId(1), 0))]);
288288
let nodes = vec![(NodeId(1), path_node), (NodeId(0), spline_node)];
289289

node-graph/gcore/src/vector/generator_nodes.rs

-11
Original file line numberDiff line numberDiff line change
@@ -106,17 +106,6 @@ fn line<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary:
106106
VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end)))
107107
}
108108

109-
#[node_macro::node(category("Vector: Shape"))]
110-
fn spline<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary: (), points: Vec<DVec2>) -> VectorDataTable {
111-
let mut spline = VectorData::from_subpath(Subpath::new_cubic_spline(points));
112-
113-
for pair in spline.segment_domain.ids().windows(2) {
114-
spline.colinear_manipulators.push([HandleId::end(pair[0]), HandleId::primary(pair[1])]);
115-
}
116-
117-
VectorDataTable::new(spline)
118-
}
119-
120109
// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node
121110
#[node_macro::node(category(""))]
122111
fn path<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> VectorDataTable {

node-graph/gcore/src/vector/vector_nodes.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -864,8 +864,8 @@ async fn subpath_segment_lengths<F: 'n + Send>(
864864
.collect()
865865
}
866866

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

node-graph/graph-craft/src/document.rs

+7
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,13 @@ impl DocumentNodeImplementation {
576576
}
577577
}
578578

579+
pub fn get_proto_node(&self) -> Option<&ProtoNodeIdentifier> {
580+
match self {
581+
DocumentNodeImplementation::ProtoNode(p) => Some(p),
582+
_ => None,
583+
}
584+
}
585+
579586
pub const fn proto(name: &'static str) -> Self {
580587
Self::ProtoNode(ProtoNodeIdentifier::new(name))
581588
}

0 commit comments

Comments
 (0)