// TODO: Eventually remove this document upgrade code // This file contains lots of hacky code for upgrading old documents to the new format use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector}; use crate::messages::prelude::DocumentMessageHandler; use bezier_rs::Subpath; use glam::IVec2; use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue}; use graphene_std::text::TypesettingConfig; use graphene_std::uuid::NodeId; use graphene_std::vector::style::{PaintOrder, StrokeAlign}; use graphene_std::vector::{VectorData, VectorDataTable}; use std::collections::HashMap; const TEXT_REPLACEMENTS: &[(&str, &str)] = &[ ("graphene_core::vector::vector_nodes::SamplePointsNode", "graphene_core::vector::SamplePolylineNode"), ("graphene_core::vector::vector_nodes::SubpathSegmentLengthsNode", "graphene_core::vector::SubpathSegmentLengthsNode"), ]; const REPLACEMENTS: &[(&str, &str)] = &[ ("graphene_core::AddArtboardNode", "graphene_core::graphic_element::AppendArtboardNode"), ("graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"), ("graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"), ("graphene_core::ToGraphicGroupNode", "graphene_core::graphic_element::ToGroupNode"), // math_nodes ("graphene_core::ops::MathNode", "graphene_math_nodes::MathNode"), ("graphene_core::ops::AddNode", "graphene_math_nodes::AddNode"), ("graphene_core::ops::SubtractNode", "graphene_math_nodes::SubtractNode"), ("graphene_core::ops::MultiplyNode", "graphene_math_nodes::MultiplyNode"), ("graphene_core::ops::DivideNode", "graphene_math_nodes::DivideNode"), ("graphene_core::ops::ModuloNode", "graphene_math_nodes::ModuloNode"), ("graphene_core::ops::ExponentNode", "graphene_math_nodes::ExponentNode"), ("graphene_core::ops::RootNode", "graphene_math_nodes::RootNode"), ("graphene_core::ops::LogarithmNode", "graphene_math_nodes::LogarithmNode"), ("graphene_core::ops::SineNode", "graphene_math_nodes::SineNode"), ("graphene_core::ops::CosineNode", "graphene_math_nodes::CosineNode"), ("graphene_core::ops::TangentNode", "graphene_math_nodes::TangentNode"), ("graphene_core::ops::SineInverseNode", "graphene_math_nodes::SineInverseNode"), ("graphene_core::ops::CosineInverseNode", "graphene_math_nodes::CosineInverseNode"), ("graphene_core::ops::TangentInverseNode", "graphene_math_nodes::TangentInverseNode"), ("graphene_core::ops::RandomNode", "graphene_math_nodes::RandomNode"), ("graphene_core::ops::ToU32Node", "graphene_math_nodes::ToU32Node"), ("graphene_core::ops::ToU64Node", "graphene_math_nodes::ToU64Node"), ("graphene_core::ops::ToF64Node", "graphene_math_nodes::ToF64Node"), ("graphene_core::ops::RoundNode", "graphene_math_nodes::RoundNode"), ("graphene_core::ops::FloorNode", "graphene_math_nodes::FloorNode"), ("graphene_core::ops::CeilingNode", "graphene_math_nodes::CeilingNode"), ("graphene_core::ops::MinNode", "graphene_math_nodes::MinNode"), ("graphene_core::ops::MaxNode", "graphene_math_nodes::MaxNode"), ("graphene_core::ops::ClampNode", "graphene_math_nodes::ClampNode"), ("graphene_core::ops::EqualsNode", "graphene_math_nodes::EqualsNode"), ("graphene_core::ops::NotEqualsNode", "graphene_math_nodes::NotEqualsNode"), ("graphene_core::ops::LessThanNode", "graphene_math_nodes::LessThanNode"), ("graphene_core::ops::GreaterThanNode", "graphene_math_nodes::GreaterThanNode"), ("graphene_core::ops::LogicalOrNode", "graphene_math_nodes::LogicalOrNode"), ("graphene_core::ops::LogicalAndNode", "graphene_math_nodes::LogicalAndNode"), ("graphene_core::ops::LogicalNotNode", "graphene_math_nodes::LogicalNotNode"), ("graphene_core::ops::BoolValueNode", "graphene_math_nodes::BoolValueNode"), ("graphene_core::ops::NumberValueNode", "graphene_math_nodes::NumberValueNode"), ("graphene_core::ops::PercentageValueNode", "graphene_math_nodes::PercentageValueNode"), ("graphene_core::ops::CoordinateValueNode", "graphene_math_nodes::CoordinateValueNode"), ("graphene_core::ops::ConstructVector2", "graphene_math_nodes::CoordinateValueNode"), ("graphene_core::ops::Vector2ValueNode", "graphene_math_nodes::CoordinateValueNode"), ("graphene_core::ops::ColorValueNode", "graphene_math_nodes::ColorValueNode"), ("graphene_core::ops::GradientValueNode", "graphene_math_nodes::GradientValueNode"), ("graphene_core::ops::StringValueNode", "graphene_math_nodes::StringValueNode"), ("graphene_core::ops::DotProductNode", "graphene_math_nodes::DotProductNode"), // debug ("graphene_core::ops::SizeOfNode", "graphene_core::debug::SizeOfNode"), ("graphene_core::ops::SomeNode", "graphene_core::debug::SomeNode"), ("graphene_core::ops::UnwrapNode", "graphene_core::debug::UnwrapNode"), ("graphene_core::ops::CloneNode", "graphene_core::debug::CloneNode"), // ??? ("graphene_core::ops::ExtractXyNode", "graphene_core::extract_xy::ExtractXyNode"), ("graphene_core::logic::LogicAndNode", "graphene_core::ops::LogicAndNode"), ("graphene_core::logic::LogicNotNode", "graphene_core::ops::LogicNotNode"), ("graphene_core::logic::LogicOrNode", "graphene_core::ops::LogicOrNode"), ("graphene_core::raster::BlendModeNode", "graphene_core::blending_nodes::BlendModeNode"), ("graphene_core::raster::OpacityNode", "graphene_core::blending_nodes::OpacityNode"), ("graphene_core::raster::BlendingNode", "graphene_core::blending_nodes::BlendingNode"), ("graphene_core::vector::GenerateHandlesNode", "graphene_core::vector::AutoTangentsNode"), ("graphene_core::vector::RemoveHandlesNode", "graphene_core::vector::AutoTangentsNode"), // raster::adjustments ("graphene_core::raster::adjustments::LuminanceNode", "graphene_raster_nodes::adjustments::LuminanceNode"), ("graphene_core::raster::LuminanceNode", "graphene_raster_nodes::adjustments::LuminanceNode"), ("graphene_core::raster::adjustments::ExtractChannelNode", "graphene_raster_nodes::adjustments::ExtractChannelNode"), ("graphene_core::raster::ExtractChannelNode", "graphene_raster_nodes::adjustments::ExtractChannelNode"), ("graphene_core::raster::adjustments::MakeOpaqueNode", "graphene_raster_nodes::adjustments::MakeOpaqueNode"), ("graphene_core::raster::ExtractOpaqueNode", "graphene_raster_nodes::adjustments::MakeOpaqueNode"), ( "graphene_core::raster::adjustments::BrightnessContrastNode", "graphene_raster_nodes::adjustments::BrightnessContrastNode", ), ("graphene_core::raster::adjustments::LevelsNode", "graphene_raster_nodes::adjustments::LevelsNode"), ("graphene_core::raster::LevelsNode", "graphene_raster_nodes::adjustments::LevelsNode"), ("graphene_core::raster::adjustments::BlackAndWhiteNode", "graphene_raster_nodes::adjustments::BlackAndWhiteNode"), ("graphene_core::raster::BlackAndWhiteNode", "graphene_raster_nodes::adjustments::BlackAndWhiteNode"), ("graphene_core::raster::adjustments::HueSaturationNode", "graphene_raster_nodes::adjustments::HueSaturationNode"), ("graphene_core::raster::HueSaturationNode", "graphene_raster_nodes::adjustments::HueSaturationNode"), ("graphene_core::raster::adjustments::InvertNode", "graphene_raster_nodes::adjustments::InvertNode"), ("graphene_core::raster::InvertNode", "graphene_raster_nodes::adjustments::InvertNode"), ("graphene_core::raster::InvertRGBNode", "graphene_raster_nodes::adjustments::InvertNode"), ("graphene_core::raster::adjustments::ThresholdNode", "graphene_raster_nodes::adjustments::ThresholdNode"), ("graphene_core::raster::ThresholdNode", "graphene_raster_nodes::adjustments::ThresholdNode"), ("graphene_core::raster::adjustments::BlendNode", "graphene_raster_nodes::adjustments::BlendNode"), ("graphene_core::raster::BlendNode", "graphene_raster_nodes::adjustments::BlendNode"), ("graphene_core::raster::BlendColorPairNode", "graphene_raster_nodes::adjustments::BlendColorPairNode"), ("graphene_core::raster::adjustments::BlendColorsNode", "graphene_raster_nodes::adjustments::BlendColorsNode"), ("graphene_core::raster::BlendColorsNode", "graphene_raster_nodes::adjustments::BlendColorsNode"), ("graphene_core::raster::adjustments::GradientMapNode", "graphene_raster_nodes::adjustments::GradientMapNode"), ("graphene_core::raster::GradientMapNode", "graphene_raster_nodes::adjustments::GradientMapNode"), ("graphene_core::raster::adjustments::VibranceNode", "graphene_raster_nodes::adjustments::VibranceNode"), ("graphene_core::raster::VibranceNode", "graphene_raster_nodes::adjustments::VibranceNode"), ("graphene_core::raster::adjustments::ChannelMixerNode", "graphene_raster_nodes::adjustments::ChannelMixerNode"), ("graphene_core::raster::ChannelMixerNode", "graphene_raster_nodes::adjustments::ChannelMixerNode"), ("graphene_core::raster::adjustments::SelectiveColorNode", "graphene_raster_nodes::adjustments::SelectiveColorNode"), ("graphene_core::raster::adjustments::PosterizeNode", "graphene_raster_nodes::adjustments::PosterizeNode"), ("graphene_core::raster::PosterizeNode", "graphene_raster_nodes::adjustments::PosterizeNode"), ("graphene_core::raster::adjustments::ExposureNode", "graphene_raster_nodes::adjustments::ExposureNode"), ("graphene_core::raster::ExposureNode", "graphene_raster_nodes::adjustments::ExposureNode"), ("graphene_core::raster::adjustments::ColorOverlayNode", "graphene_raster_nodes::adjustments::ColorOverlayNode"), ("graphene_raster_nodes::generate_curves::ColorOverlayNode", "graphene_raster_nodes::adjustments::ColorOverlayNode"), // raster ("graphene_core::raster::adjustments::GenerateCurvesNode", "graphene_raster_nodes::generate_curves::GenerateCurvesNode"), ("graphene_std::dehaze::DehazeNode", "graphene_raster_nodes::dehaze::DehazeNode"), ("graphene_std::filter::BlurNode", "graphene_raster_nodes::filter::BlurNode"), ( "graphene_std::image_color_palette::ImageColorPaletteNode", "graphene_raster_nodes::image_color_palette::ImageColorPaletteNode", ), ("graphene_std::raster::SampleImageNode", "graphene_raster_nodes::std_nodes::SampleImageNode"), ("graphene_std::raster::CombineChannelsNode", "graphene_raster_nodes::std_nodes::CombineChannelsNode"), ("graphene_std::raster::MaskNode", "graphene_raster_nodes::std_nodes::MaskNode"), ("graphene_std::raster::ExtendImageToBoundsNode", "graphene_raster_nodes::std_nodes::ExtendImageToBoundsNode"), ("graphene_std::raster::EmptyImageNode", "graphene_raster_nodes::std_nodes::EmptyImageNode"), ("graphene_std::raster::ImageValueNode", "graphene_raster_nodes::std_nodes::ImageValueNode"), ("graphene_std::raster::NoisePatternNode", "graphene_raster_nodes::std_nodes::NoisePatternNode"), ("graphene_std::raster::MandelbrotNode", "graphene_raster_nodes::std_nodes::MandelbrotNode"), // text ("graphene_core::text::TextGeneratorNode", "graphene_core::text::TextNode"), // transform ("graphene_core::transform::SetTransformNode", "graphene_core::transform_nodes::ReplaceTransformNode"), ("graphene_core::transform::ReplaceTransformNode", "graphene_core::transform_nodes::ReplaceTransformNode"), ("graphene_core::transform::TransformNode", "graphene_core::transform_nodes::TransformNode"), ("graphene_core::transform::BoundlessFootprintNode", "graphene_core::transform_nodes::BoundlessFootprintNode"), ("graphene_core::transform::FreezeRealTimeNode", "graphene_core::transform_nodes::FreezeRealTimeNode"), // ??? ("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::RectangleGenerator", "graphene_core::vector::generator_nodes::RectangleNode"), ( "graphene_core::vector::generator_nodes::RegularPolygonGenerator", "graphene_core::vector::generator_nodes::RegularPolygonNode", ), ("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"), ("graphene_core::transform::CullNode", "graphene_core::ops::IdentityNode"), ("graphene_std::raster::MaskImageNode", "graphene_std::raster::MaskNode"), ("graphene_core::vector::FlattenVectorElementsNode", "graphene_core::vector::FlattenPathNode"), ("graphene_std::vector::BooleanOperationNode", "graphene_path_bool::BooleanOperationNode"), // brush ("graphene_std::brush::BrushStampGeneratorNode", "graphene_brush::brush::BrushStampGeneratorNode"), ("graphene_std::brush::BlitNode", "graphene_brush::brush::BlitNode"), ("graphene_std::brush::BrushNode", "graphene_brush::brush::BrushNode"), ]; pub fn document_migration_string_preprocessing(document_serialized_content: String) -> String { TEXT_REPLACEMENTS .iter() .fold(document_serialized_content, |document_serialized_content, (old, new)| document_serialized_content.replace(old, new)) } pub fn document_migration_reset_node_definition(document_serialized_content: &str) -> bool { // Upgrade a document being opened to use fresh copies of all nodes if document_serialized_content.contains("node_output_index") { return true; } // Upgrade layer implementation from https://github.com/GraphiteEditor/Graphite/pull/1946 (see also `fn fix_nodes()` in `main.rs` of Graphene CLI) if document_serialized_content.contains("graphene_core::ConstructLayerNode") || document_serialized_content.contains("graphene_core::AddArtboardNode") { return true; } false } pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) { let network = document.network_interface.document_network().clone(); // Apply string replacements to each node for (node_id, node, network_path) in network.recursive_nodes() { if let DocumentNodeImplementation::ProtoNode(protonode_id) = &node.implementation { for (old, new) in REPLACEMENTS { let node_path_without_type_args = protonode_id.name.split('<').next(); let mut default_template = NodeTemplate::default(); default_template.document_node.implementation = DocumentNodeImplementation::ProtoNode(new.to_string().into()); if node_path_without_type_args == Some(old) { document.network_interface.replace_implementation(node_id, &network_path, &mut default_template); document.network_interface.set_manual_compostion(node_id, &network_path, Some(graph_craft::Type::Generic("T".into()))); } } } } // Apply upgrades to each unmodified node. let nodes = document .network_interface .document_network() .recursive_nodes() .map(|(node_id, node, path)| (*node_id, node.clone(), path)) .collect::)>>(); for (node_id, node, network_path) in &nodes { if reset_node_definitions_on_open { if let Some(Some(reference)) = document.network_interface.reference(node_id, network_path) { let Some(node_definition) = resolve_document_node_type(reference) else { continue }; document.network_interface.replace_implementation(node_id, network_path, &mut node_definition.default_node_template()); } } // Upgrade old nodes to use `Context` instead of `()` or `Footprint` for manual composition if node.manual_composition == Some(graph_craft::concrete!(())) || node.manual_composition == Some(graph_craft::concrete!(graphene_std::transform::Footprint)) { document .network_interface .set_manual_compostion(node_id, network_path, graph_craft::concrete!(graphene_std::Context).into()); } let Some(Some(reference)) = document.network_interface.reference(node_id, network_path).cloned() else { // Only nodes that have not been modified and still refer to a definition can be updated continue; }; let reference = &reference; let inputs_count = node.inputs.len(); // Upgrade Stroke node to reorder parameters and add "Align" and "Paint Order" (#2644) if reference == "Stroke" && inputs_count == 8 { let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); let align_input = NodeInput::value(TaggedValue::StrokeAlign(StrokeAlign::Center), false); let paint_order_input = NodeInput::value(TaggedValue::PaintOrder(PaintOrder::StrokeAbove), false); 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); document.network_interface.set_input(&InputConnector::node(*node_id, 3), align_input, network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[6].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[7].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 7), paint_order_input, network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 8), old_inputs[3].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 9), old_inputs[4].clone(), network_path); } // 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, network_path, 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, network_path) .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, network_path).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(network_path) .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, network_path) 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::>(); 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, network_path); // Insert the new "Path" and "Spline" nodes into the network interface with generated IDs document.network_interface.insert_node_group(nodes.clone(), new_ids, network_path); // Reposition the new "Spline" node to match the original "Spline" node's position document.network_interface.shift_node(&new_spline_id, node_position, network_path); // 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), network_path); // 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), network_path); } } // 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 != 9 { let mut template = resolve_document_node_type(reference).unwrap().default_node_template(); document.network_interface.replace_implementation(node_id, network_path, &mut template); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut template).unwrap(); 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); document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[3].clone(), network_path); document.network_interface.set_input( &InputConnector::node(*node_id, 4), if inputs_count == 6 { old_inputs[4].clone() } else { NodeInput::value(TaggedValue::F64(TypesettingConfig::default().line_height_ratio), false) }, network_path, ); document.network_interface.set_input( &InputConnector::node(*node_id, 5), if inputs_count == 6 { old_inputs[5].clone() } else { NodeInput::value(TaggedValue::F64(TypesettingConfig::default().character_spacing), false) }, network_path, ); document.network_interface.set_input( &InputConnector::node(*node_id, 6), if inputs_count >= 7 { old_inputs[6].clone() } else { NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false) }, network_path, ); document.network_interface.set_input( &InputConnector::node(*node_id, 7), if inputs_count >= 8 { old_inputs[7].clone() } else { NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false) }, network_path, ); document.network_interface.set_input( &InputConnector::node(*node_id, 8), if inputs_count >= 9 { old_inputs[8].clone() } else { NodeInput::value(TaggedValue::F64(TypesettingConfig::default().tilt), false) }, network_path, ); } // Upgrade Sine, Cosine, and Tangent nodes to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default if (reference == "Sine" || reference == "Cosine" || reference == "Tangent") && inputs_count == 1 { let mut node_template = resolve_document_node_type(reference).unwrap().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).unwrap(); 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), NodeInput::value(TaggedValue::Bool(true), false), network_path); } // Upgrade the Modulo node to include a boolean input for whether the output should be always positive, which was previously not an option if reference == "Modulo" && inputs_count == 2 { let mut node_template = resolve_document_node_type(reference).unwrap().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).unwrap(); 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), NodeInput::value(TaggedValue::Bool(false), false), network_path); } // Upgrade the Mirror node to add the `keep_original` boolean input if reference == "Mirror" && inputs_count == 3 { let mut node_template = resolve_document_node_type(reference).unwrap().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).unwrap(); 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); document .network_interface .set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Bool(true), false), network_path); } // Upgrade the Mirror node to add the `reference_point` input and change `offset` from `DVec2` to `f64` if reference == "Mirror" && inputs_count == 4 { let mut node_template = resolve_document_node_type(reference).unwrap().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).unwrap(); let Some(&TaggedValue::DVec2(old_offset)) = old_inputs[1].as_value() else { return }; let old_offset = if old_offset.x.abs() > old_offset.y.abs() { old_offset.x } else { old_offset.y }; 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), NodeInput::value(TaggedValue::ReferencePoint(graphene_std::transform::ReferencePoint::Center), false), network_path, ); document .network_interface .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::F64(old_offset), false), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[2].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[3].clone(), network_path); } // Upgrade artboard name being passed as hidden value input to "To Artboard" if reference == "Artboard" && reset_node_definitions_on_open { let label = document.network_interface.display_name(node_id, network_path); document .network_interface .set_input(&InputConnector::node(NodeId(0), 1), NodeInput::value(TaggedValue::String(label), false), &[*node_id]); } if reference == "Image" && inputs_count == 1 { let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); document.network_interface.replace_implementation(node_id, network_path, &mut node_template); // Insert a new empty input for the image document.network_interface.add_import(TaggedValue::None, false, 0, "Empty", "", &[*node_id]); document.network_interface.set_reference(node_id, network_path, Some("Image".to_string())); } if reference == "Noise Pattern" && inputs_count == 15 { let mut node_template = resolve_document_node_type(reference).unwrap().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).unwrap(); document .network_interface .set_input(&InputConnector::node(*node_id, 0), NodeInput::value(TaggedValue::None, false), network_path); for (i, input) in old_inputs.iter().enumerate() { document.network_interface.set_input(&InputConnector::node(*node_id, i + 1), input.clone(), network_path); } } if reference == "Instance on Points" && inputs_count == 2 { let mut node_template = resolve_document_node_type(reference).unwrap().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).unwrap(); 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); } if reference == "Morph" && inputs_count == 4 { let mut node_template = resolve_document_node_type(reference).unwrap().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).unwrap(); 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); // We have removed the last input, so we don't add index 3 } if reference == "Brush" && inputs_count == 4 { let mut node_template = resolve_document_node_type(reference).unwrap().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).unwrap(); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); // We have removed the second input ("bounds"), so we don't add index 1 and we shift the rest of the inputs down by one document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[2].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[3].clone(), network_path); } if reference == "Flatten Vector Elements" { let mut node_template = resolve_document_node_type(reference).unwrap().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).unwrap(); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.replace_reference_name(node_id, network_path, "Flatten Path".to_string()); } if reference == "Remove Handles" { let mut node_template = resolve_document_node_type(reference).unwrap().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).unwrap(); 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), NodeInput::value(TaggedValue::F64(0.), false), network_path); document .network_interface .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path); document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string()); } if reference == "Generate Handles" { let mut node_template = resolve_document_node_type("Auto-Tangents").unwrap().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).unwrap(); 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), NodeInput::value(TaggedValue::Bool(true), false), network_path); document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string()); } if reference == "Merge by Distance" && inputs_count == 2 { let mut node_template = resolve_document_node_type(reference).unwrap().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).unwrap(); 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), NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Topological), false), network_path, ); } if reference == "Spatial Merge by Distance" { let mut node_template = resolve_document_node_type("Merge by Distance").unwrap().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).unwrap(); 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), NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Spatial), false), network_path, ); document.network_interface.replace_reference_name(node_id, network_path, "Merge by Distance".to_string()); } if reference == "Sample Points" && inputs_count == 5 { let mut node_template = resolve_document_node_type("Sample Polyline").unwrap().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).unwrap(); let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false); let new_quantity_value = NodeInput::value(TaggedValue::U32(100), false); 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), new_spacing_value, network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[1].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[2].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[4].clone(), network_path); document.network_interface.replace_reference_name(node_id, network_path, "Sample Polyline".to_string()); } // Make the "Quantity" parameter a u32 instead of f64 if reference == "Sample Polyline" { let node_definition = resolve_document_node_type("Sample Polyline").unwrap(); let mut new_node_template = node_definition.default_node_template(); // Get the inputs, obtain the quantity value, and put the inputs back let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut new_node_template).unwrap(); let quantity_value = old_inputs.get(3).cloned(); if let Some(NodeInput::Value { tagged_value, exposed }) = quantity_value { if let TaggedValue::F64(value) = *tagged_value { let new_quantity_value = NodeInput::value(TaggedValue::U32(value as u32), exposed); document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path); } } } // Make the "Grid" node, if its input of index 3 is a DVec2 for "angles" instead of a u32 for the "columns" input that now succeeds "angles", move the angle to index 5 (after "columns" and "rows") if reference == "Grid" && inputs_count == 6 { let node_definition = resolve_document_node_type(reference).unwrap(); let mut new_node_template = node_definition.default_node_template(); let mut current_node_template = document.network_interface.create_node_template(node_id, network_path).unwrap(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut new_node_template).unwrap(); let index_3_value = old_inputs.get(3).cloned(); if let Some(NodeInput::Value { tagged_value, exposed: _ }) = index_3_value { if matches!(*tagged_value, TaggedValue::DVec2(_)) { // Move index 3 to the end 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); document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path); } else { // Swap it back if we're not changing anything let _ = document.network_interface.replace_inputs(node_id, network_path, &mut current_node_template); } } } // Ensure layers are positioned as stacks if they are upstream siblings of another layer document.network_interface.load_structure(); let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::>(); for layer in all_layers { let Some((downstream_node, input_index)) = document .network_interface .outward_wires(&[]) .and_then(|outward_wires| outward_wires.get(&OutputConnector::node(layer.to_node(), 0))) .and_then(|outward_wires| outward_wires.first()) .and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index()))) else { continue; }; // If the downstream node is a layer and the input is the first input and the current layer is not in a stack if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) { // Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files let (Some(layer_position), Some(downstream_position)) = (document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[])) else { log::error!("Could not get position for layer {:?} or downstream node {} when opening file", layer.to_node(), downstream_node); continue; }; if layer_position.x == downstream_position.x { document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]); } } } } }