From d2f8c9974422adf4f840f57e903084483ad5fdee Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Tue, 8 Jul 2025 18:13:05 -0700 Subject: [PATCH] Avoid panics during document migration --- .../utility_types/network_interface.rs | 2 +- .../messages/portfolio/document_migration.rs | 914 +++++++++--------- 2 files changed, 460 insertions(+), 456 deletions(-) diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 389557c0..404d3486 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -674,7 +674,7 @@ impl NodeNetworkInterface { let input_type = self.input_type(&InputConnector::node(*node_id, iterator_index), network_path).0; // Value inputs are stored as concrete, so they are compared to the nested type. Node inputs are stored as fn, so they are compared to the entire type. // For example a node input of (Footprint) -> VectorData would not be compatible with () -> VectorData - node_io.inputs[iterator_index].clone().nested_type() == &input_type || node_io.inputs[iterator_index] == input_type + node_io.inputs.get(iterator_index).map(|ty| ty.nested_type().clone()).as_ref() == Some(&input_type) || node_io.inputs.get(iterator_index) == Some(&input_type) }); if valid_implementation { node_io.inputs.get(*input_index).cloned() } else { None } }) diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index f3b018a6..e4db26d2 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -7,6 +7,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Inp use crate::messages::prelude::DocumentMessageHandler; use bezier_rs::Subpath; use glam::IVec2; +use graph_craft::document::DocumentNode; use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue}; use graphene_std::text::TypesettingConfig; use graphene_std::uuid::NodeId; @@ -217,473 +218,476 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_ .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()); - } + migrate_node(node_id, node, network_path, document, reset_node_definitions_on_open); + } +} + +fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) -> Option<()> { + if reset_node_definitions_on_open { + if let Some(Some(reference)) = document.network_interface.reference(node_id, network_path) { + let node_definition = resolve_document_node_type(reference)?; + 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()); + } + + // Only nodes that have not been modified and still refer to a definition can be updated + let reference = document.network_interface.reference(node_id, network_path).cloned().flatten()?; + 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)?.default_node_template(); + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + 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()) { + return None; } - // 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; + // 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)?; + 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"); + return None; }; - let reference = &reference; + let vector_data = VectorData::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false)); - let inputs_count = node.inputs.len(); + // Retrieve the output connectors linked to the "Spline" node's output port + let Some(spline_outputs) = document.network_interface.outward_wires(network_path)?.get(&OutputConnector::node(*node_id, 0)).cloned() else { + log::error!("Vec of InputConnector Spline node is connected to its output port 0."); + return None; + }; - // 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(); + // 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."); + return None; + }; - let align_input = NodeInput::value(TaggedValue::StrokeAlign(StrokeAlign::Center), false); - let paint_order_input = NodeInput::value(TaggedValue::PaintOrder(PaintOrder::StrokeAbove), false); + // Get the "Path" node definition and fill it in with the vector data and default vector modification + let Some(path_node_type) = resolve_document_node_type("Path") else { + log::error!("Path node does not exist."); + return None; + }; + 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)), + ]); - 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); + // Get the "Spline" node definition and wire it up with the "Path" node as input + let Some(spline_node_type) = resolve_document_node_type("Spline") else { + log::error!("Spline node does not exist."); + return None; + }; + 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))?; + let new_path_id = *new_ids.get(&NodeId(1))?; + + // 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); } + } - // 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 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)?.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)?; + + 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)?.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), 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)?.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), 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)?.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); + 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)?.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)?; + + let Some(&TaggedValue::DVec2(old_offset)) = old_inputs[1].as_value() else { return None }; + 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)?.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)?.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), 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); } + } - // 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" { - // Get the inputs, obtain the quantity value, and put the inputs back - let quantity_value = document - .network_interface - .input_from_connector(&InputConnector::Node { node_id: *node_id, input_index: 3 }, network_path) - .unwrap(); - - if let 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(); - - let mut upgraded = false; - - 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); - - upgraded = true; - } - } - - if !upgraded { - 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, &[]); - } + if reference == "Instance on Points" && inputs_count == 2 { + let mut node_template = resolve_document_node_type(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); + } + + if reference == "Morph" && inputs_count == 4 { + let mut node_template = resolve_document_node_type(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); + // 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)?.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); + // 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)?.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.replace_reference_name(node_id, network_path, "Flatten Path".to_string()); + } + + if reference == "Remove Handles" { + let mut node_template = resolve_document_node_type(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), 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")?.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), 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)?.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), + 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")?.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), + 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")?.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)?; + 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" { + // Get the inputs, obtain the quantity value, and put the inputs back + let quantity_value = document + .network_interface + .input_from_connector(&InputConnector::Node { node_id: *node_id, input_index: 3 }, network_path)?; + + if let 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)?; + 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)?; + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut new_node_template)?; + let index_3_value = old_inputs.get(3).cloned(); + + let mut upgraded = false; + + 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); + + upgraded = true; + } + } + + if !upgraded { + 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 (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())))?; + // 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); + return None; + }; + if layer_position.x == downstream_position.x { + document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]); + } + } + } + + Some(()) }