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:
parent
95bbc95606
commit
2d90bb0cbf
File diff suppressed because one or more lines are too long
|
|
@ -2012,29 +2012,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||||
description: Cow::Borrowed("TODO"),
|
description: Cow::Borrowed("TODO"),
|
||||||
properties: None,
|
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 {
|
DocumentNodeDefinition {
|
||||||
identifier: "Path",
|
identifier: "Path",
|
||||||
category: "Vector",
|
category: "Vector",
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,13 @@ use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType};
|
use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType};
|
||||||
use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};
|
use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};
|
||||||
|
|
||||||
|
use bezier_rs::Subpath;
|
||||||
|
use glam::IVec2;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput};
|
use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput};
|
||||||
use graphene_core::text::{Font, TypesettingConfig};
|
use graphene_core::text::{Font, TypesettingConfig};
|
||||||
use graphene_std::vector::style::{Fill, FillType, Gradient};
|
use graphene_std::vector::style::{Fill, FillType, Gradient};
|
||||||
|
use graphene_std::vector::{VectorData, VectorDataTable};
|
||||||
use interpreted_executor::dynamic_executor::IntrospectError;
|
use interpreted_executor::dynamic_executor::IntrospectError;
|
||||||
|
|
||||||
use std::sync::Arc;
|
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::raster::VibranceNode", "graphene_core::raster::adjustments::VibranceNode"),
|
||||||
("graphene_core::text::TextGeneratorNode", "graphene_core::text::TextNode"),
|
("graphene_core::text::TextGeneratorNode", "graphene_core::text::TextNode"),
|
||||||
("graphene_core::transform::SetTransformNode", "graphene_core::transform::ReplaceTransformNode"),
|
("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::EllipseGenerator", "graphene_core::vector::generator_nodes::EllipseNode"),
|
||||||
("graphene_core::vector::generator_nodes::LineGenerator", "graphene_core::vector::generator_nodes::LineNode"),
|
("graphene_core::vector::generator_nodes::LineGenerator", "graphene_core::vector::generator_nodes::LineNode"),
|
||||||
("graphene_core::vector::generator_nodes::PathGenerator", "graphene_core::vector::generator_nodes::PathNode"),
|
("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::RegularPolygonGenerator",
|
||||||
"graphene_core::vector::generator_nodes::RegularPolygonNode",
|
"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_core::vector::generator_nodes::StarGenerator", "graphene_core::vector::generator_nodes::StarNode"),
|
||||||
("graphene_std::executor::BlendGpuImageNode", "graphene_std::gpu_nodes::BlendGpuImageNode"),
|
("graphene_std::executor::BlendGpuImageNode", "graphene_std::gpu_nodes::BlendGpuImageNode"),
|
||||||
("graphene_std::raster::SampleNode", "graphene_std::raster::SampleImageNode"),
|
("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
|
// 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 {
|
if reference == "Text" && inputs_count != 8 {
|
||||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -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_type = resolve_document_node_type("Path").expect("Path node does not exist");
|
||||||
let path_node = path_node_type.default_node_template();
|
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 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)];
|
let nodes = vec![(NodeId(1), path_node), (NodeId(0), spline_node)];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -106,17 +106,6 @@ fn line<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primary:
|
||||||
VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end)))
|
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
|
// 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(""))]
|
#[node_macro::node(category(""))]
|
||||||
fn path<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> VectorDataTable {
|
fn path<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> VectorDataTable {
|
||||||
|
|
|
||||||
|
|
@ -864,8 +864,8 @@ async fn subpath_segment_lengths<F: 'n + Send>(
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(name("Splines from Points"), category("Vector"), path(graphene_core::vector))]
|
#[node_macro::node(name("Spline"), category("Vector"), path(graphene_core::vector))]
|
||||||
async fn splines_from_points<F: 'n + Send>(
|
async fn spline<F: 'n + Send>(
|
||||||
#[implementations(
|
#[implementations(
|
||||||
(),
|
(),
|
||||||
Footprint,
|
Footprint,
|
||||||
|
|
@ -1431,7 +1431,7 @@ mod test {
|
||||||
}
|
}
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn spline() {
|
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();
|
let spline = spline.one_item();
|
||||||
assert_eq!(spline.stroke_bezier_paths().count(), 1);
|
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.)]);
|
assert_eq!(spline.point_domain.positions(), &[DVec2::ZERO, DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)]);
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
pub const fn proto(name: &'static str) -> Self {
|
||||||
Self::ProtoNode(ProtoNodeIdentifier::new(name))
|
Self::ProtoNode(ProtoNodeIdentifier::new(name))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue