diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index 28e3ebdaa..7c9b90c34 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -99,19 +99,6 @@ pub enum FrontendMessage { #[serde(rename = "copyText")] copy_text: String, }, - // TODO: Eventually remove this document upgrade code - TriggerUpgradeDocumentToVectorManipulationFormat { - #[serde(rename = "documentId")] - document_id: DocumentId, - #[serde(rename = "documentName")] - document_name: String, - #[serde(rename = "documentIsAutoSaved")] - document_is_auto_saved: bool, - #[serde(rename = "documentIsSaved")] - document_is_saved: bool, - #[serde(rename = "documentSerializedContent")] - document_serialized_content: String, - }, TriggerVisitLink { url: String, }, diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs new file mode 100644 index 000000000..4b8f8a85f --- /dev/null +++ b/editor/src/messages/portfolio/document_migration.rs @@ -0,0 +1,699 @@ +// 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, 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::{Fill, FillType, Gradient, PaintOrder, StrokeAlign}; +use graphene_std::vector::{VectorData, VectorDataTable}; +use std::collections::HashMap; + +const TEXT_REPLACEMENTS: [(&str, &str); 2] = [ + ("graphene_core::vector::vector_nodes::SamplePointsNode", "graphene_core::vector::SamplePolylineNode"), + ("graphene_core::vector::vector_nodes::SubpathSegmentLengthsNode", "graphene_core::vector::SubpathSegmentLengthsNode"), +]; + +const REPLACEMENTS: [(&str, &str); 40] = [ + ("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"), + ("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::ops::ConstructVector2", "graphene_core::ops::CoordinateValueNode"), + ("graphene_core::ops::Vector2ValueNode", "graphene_core::ops::CoordinateValueNode"), + ("graphene_core::raster::BlackAndWhiteNode", "graphene_core::raster::adjustments::BlackAndWhiteNode"), + ("graphene_core::raster::BlendNode", "graphene_core::raster::adjustments::BlendNode"), + ("graphene_core::raster::ChannelMixerNode", "graphene_core::raster::adjustments::ChannelMixerNode"), + ("graphene_core::raster::adjustments::ColorOverlayNode", "graphene_core::raster::adjustments::ColorOverlayNode"), + ("graphene_core::raster::ExposureNode", "graphene_core::raster::adjustments::ExposureNode"), + ("graphene_core::raster::ExtractChannelNode", "graphene_core::raster::adjustments::ExtractChannelNode"), + ("graphene_core::raster::GradientMapNode", "graphene_core::raster::adjustments::GradientMapNode"), + ("graphene_core::raster::HueSaturationNode", "graphene_core::raster::adjustments::HueSaturationNode"), + ("graphene_core::vector::GenerateHandlesNode", "graphene_core::vector::AutoTangentsNode"), + ("graphene_core::vector::RemoveHandlesNode", "graphene_core::vector::AutoTangentsNode"), + ("graphene_core::raster::InvertNode", "graphene_core::raster::adjustments::InvertNode"), + ("graphene_core::raster::InvertRGBNode", "graphene_core::raster::adjustments::InvertNode"), + ("graphene_core::raster::LevelsNode", "graphene_core::raster::adjustments::LevelsNode"), + ("graphene_core::raster::LuminanceNode", "graphene_core::raster::adjustments::LuminanceNode"), + ("graphene_core::raster::ExtractOpaqueNode", "graphene_core::raster::adjustments::MakeOpaqueNode"), + ("graphene_core::raster::PosterizeNode", "graphene_core::raster::adjustments::PosterizeNode"), + ("graphene_core::raster::ThresholdNode", "graphene_core::raster::adjustments::ThresholdNode"), + ("graphene_core::raster::VibranceNode", "graphene_core::raster::adjustments::VibranceNode"), + ("graphene_core::text::TextGeneratorNode", "graphene_core::text::TextNode"), + ("graphene_core::transform::SetTransformNode", "graphene_core::transform::ReplaceTransformNode"), + ("graphene_core::vector::SplinesFromPointsNode", "graphene_core::vector::SplineNode"), + ("graphene_core::vector::generator_nodes::EllipseGenerator", "graphene_core::vector::generator_nodes::EllipseNode"), + ("graphene_core::vector::generator_nodes::LineGenerator", "graphene_core::vector::generator_nodes::LineNode"), + ("graphene_core::vector::generator_nodes::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"), +]; + +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 mut network = document.network_interface.document_network().clone(); + network.generate_node_paths(&[]); + + // Apply string replacements to each node + let node_ids: Vec<_> = network.recursive_nodes().map(|(&id, node)| (id, node.original_location.path.clone().unwrap())).collect(); + for (node_id, path) in &node_ids { + let network_path: Vec<_> = path.iter().copied().take(path.len() - 1).collect(); + + if let Some(DocumentNodeImplementation::ProtoNode(protonode_id)) = document + .network_interface + .nested_network(&network_path) + .unwrap() + .nodes + .get(node_id) + .map(|node| node.implementation.clone()) + { + for (old, new) in REPLACEMENTS { + let node_path_without_type_args = protonode_id.name.split('<').next(); + if node_path_without_type_args == Some(old) { + document + .network_interface + .replace_implementation(node_id, &network_path, DocumentNodeImplementation::ProtoNode(new.to_string().into())); + document.network_interface.set_manual_compostion(node_id, &network_path, Some(graph_craft::Type::Generic("T".into()))); + } + } + } + } + + if reset_node_definitions_on_open { + // This can be used, if uncommented, to upgrade demo artwork with outdated document node internals from their definitions. Delete when it's no longer needed. + // Used for upgrading old internal networks for demo artwork nodes. Will reset all node internals for any opened file + for node_id in &document + .network_interface + .document_network_metadata() + .persistent_metadata + .node_metadata + .keys() + .cloned() + .collect::>() + { + if let Some(reference) = document + .network_interface + .document_network_metadata() + .persistent_metadata + .node_metadata + .get(node_id) + .and_then(|node| node.persistent_metadata.reference.as_ref()) + { + let Some(node_definition) = resolve_document_node_type(reference) else { continue }; + let default_definition_node = node_definition.default_node_template(); + document.network_interface.replace_implementation(node_id, &[], default_definition_node.document_node.implementation); + document + .network_interface + .replace_implementation_metadata(node_id, &[], default_definition_node.persistent_node_metadata); + document.network_interface.set_manual_compostion(node_id, &[], default_definition_node.document_node.manual_composition); + } + } + } + + if document + .network_interface + .document_network_metadata() + .persistent_metadata + .node_metadata + .iter() + .any(|(node_id, node)| node.persistent_metadata.reference.as_ref().is_some_and(|reference| reference == "Output") && *node_id == NodeId(0)) + { + document.network_interface.delete_nodes(vec![NodeId(0)], true, &[]); + } + + let mut network = document.network_interface.document_network().clone(); + network.generate_node_paths(&[]); + + let node_ids: Vec<_> = network.recursive_nodes().map(|(&id, node)| (id, node.original_location.path.clone().unwrap())).collect(); + + // Apply upgrades to each node + for (node_id, path) in &node_ids { + let network_path: Vec<_> = path.iter().copied().take(path.len() - 1).collect(); + let network_path = &network_path; + + let Some(node) = document.network_interface.nested_network(network_path).unwrap().nodes.get(node_id).cloned() else { + log::error!("could not get node in deserialize_document"); + continue; + }; + + // 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(node_metadata) = document.network_interface.network_metadata(network_path).unwrap().persistent_metadata.node_metadata.get(node_id) else { + log::error!("could not get node metadata for node {node_id} in deserialize_document"); + continue; + }; + + let Some(ref reference) = node_metadata.persistent_metadata.reference.clone() else { + // TODO: Investigate if this should be an expected case, because currently it runs hundreds of times normally. + // TODO: Either delete the commented out error below if this is normal, or fix the underlying issue if this is not expected. + // log::error!("could not get reference in deserialize_document"); + continue; + }; + + let inputs_count = node.inputs.len(); + + // Upgrade Fill nodes to the format change in #1778 + if reference == "Fill" && inputs_count == 8 { + let node_definition = resolve_document_node_type(reference).unwrap(); + let document_node = node_definition.default_node_template().document_node; + document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); + + let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + + let Some(fill_type) = old_inputs[1].as_value().cloned() else { continue }; + let TaggedValue::FillType(fill_type) = fill_type else { continue }; + let Some(solid_color) = old_inputs[2].as_value().cloned() else { continue }; + let TaggedValue::OptionalColor(solid_color) = solid_color else { continue }; + let Some(gradient_type) = old_inputs[3].as_value().cloned() else { continue }; + let TaggedValue::GradientType(gradient_type) = gradient_type else { continue }; + let Some(start) = old_inputs[4].as_value().cloned() else { continue }; + let TaggedValue::DVec2(start) = start else { continue }; + let Some(end) = old_inputs[5].as_value().cloned() else { continue }; + let TaggedValue::DVec2(end) = end else { continue }; + let Some(transform) = old_inputs[6].as_value().cloned() else { continue }; + let TaggedValue::DAffine2(transform) = transform else { continue }; + let Some(positions) = old_inputs[7].as_value().cloned() else { continue }; + let TaggedValue::GradientStops(positions) = positions else { continue }; + + let fill = match (fill_type, solid_color) { + (FillType::Solid, None) => Fill::None, + (FillType::Solid, Some(color)) => Fill::Solid(color), + (FillType::Gradient, _) => Fill::Gradient(Gradient { + stops: positions, + gradient_type, + start, + end, + transform, + }), + }; + document + .network_interface + .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Fill(fill.clone()), false), network_path); + match fill { + Fill::None => { + document + .network_interface + .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::OptionalColor(None), false), network_path); + } + Fill::Solid(color) => { + document + .network_interface + .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::OptionalColor(Some(color)), false), network_path); + } + Fill::Gradient(gradient) => { + document + .network_interface + .set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Gradient(gradient), false), network_path); + } + } + } + + // Upgrade Stroke node to reorder parameters and add "Align" and "Paint Order" (#2644) + if reference == "Stroke" && inputs_count == 8 { + let node_definition = resolve_document_node_type(reference).unwrap(); + let document_node = node_definition.default_node_template().document_node; + document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); + document.network_interface.insert_input_properties_row(node_id, 8, network_path); + document.network_interface.insert_input_properties_row(node_id, 9, network_path); + + let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); + 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 != 8 { + let node_definition = resolve_document_node_type(reference).unwrap(); + let document_node = node_definition.default_node_template().document_node; + document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); + + let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); + + 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), + NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false), + network_path, + ); + document.network_interface.set_input( + &InputConnector::node(*node_id, 7), + NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), 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 node_definition = resolve_document_node_type(reference).unwrap(); + let document_node = node_definition.default_node_template().document_node; + document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); + + let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); + + 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 node_definition = resolve_document_node_type(reference).unwrap(); + let document_node = node_definition.default_node_template().document_node; + document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); + + let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); + + 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 node_definition = resolve_document_node_type(reference).unwrap(); + let document_node = node_definition.default_node_template().document_node; + document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); + + let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); + + 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 node_definition = resolve_document_node_type(reference).unwrap(); + let new_node_template = node_definition.default_node_template(); + let document_node = new_node_template.document_node; + document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); + document + .network_interface + .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); + + let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); + + 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 node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap(); + let new_image_node = node_definition.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, new_image_node.document_node.implementation); + + // 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 node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap(); + let new_noise_pattern_node = node_definition.default_node_template(); + document + .network_interface + .replace_implementation(node_id, network_path, new_noise_pattern_node.document_node.implementation); + + let old_inputs = document.network_interface.replace_inputs(node_id, new_noise_pattern_node.document_node.inputs.clone(), network_path); + + 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 node_definition = resolve_document_node_type(reference).unwrap(); + let new_node_template = node_definition.default_node_template(); + let document_node = new_node_template.document_node; + document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); + document + .network_interface + .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); + + let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); + + 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 node_definition = resolve_document_node_type(reference).unwrap(); + let new_node_template = node_definition.default_node_template(); + let document_node = new_node_template.document_node; + document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); + document + .network_interface + .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); + + let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); + + 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 node_definition = resolve_document_node_type(reference).unwrap(); + let new_node_template = node_definition.default_node_template(); + let document_node = new_node_template.document_node; + document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); + document + .network_interface + .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); + + let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); + + 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 node_definition = resolve_document_node_type("Flatten Path").unwrap(); + let new_node_template = node_definition.default_node_template(); + let document_node = new_node_template.document_node; + document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); + document + .network_interface + .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); + + let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); + + 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 node_definition = resolve_document_node_type("Auto-Tangents").unwrap(); + let new_node_template = node_definition.default_node_template(); + let document_node = new_node_template.document_node; + document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); + document + .network_interface + .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); + + let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); + + 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 node_definition = resolve_document_node_type("Auto-Tangents").unwrap(); + let new_node_template = node_definition.default_node_template(); + let document_node = new_node_template.document_node; + document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); + document + .network_interface + .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); + + let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); + + 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 node_definition = resolve_document_node_type("Merge by Distance").unwrap(); + let new_node_template = node_definition.default_node_template(); + let document_node = new_node_template.document_node; + document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); + document + .network_interface + .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); + + let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); + + 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 node_definition = resolve_document_node_type("Merge by Distance").unwrap(); + let new_node_template = node_definition.default_node_template(); + let document_node = new_node_template.document_node; + document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); + document + .network_interface + .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); + + let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); + + 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 node_definition = resolve_document_node_type("Sample Polyline").unwrap(); + let new_node_template = node_definition.default_node_template(); + let document_node = new_node_template.document_node; + document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); + document + .network_interface + .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); + + let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); + let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), 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), old_inputs[1].clone(), 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()); + } + } + + // 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, &[]); + } + } + } +} diff --git a/editor/src/messages/portfolio/mod.rs b/editor/src/messages/portfolio/mod.rs index 39b25404d..2203ac940 100644 --- a/editor/src/messages/portfolio/mod.rs +++ b/editor/src/messages/portfolio/mod.rs @@ -2,6 +2,7 @@ mod portfolio_message; mod portfolio_message_handler; pub mod document; +pub mod document_migration; pub mod menu_bar; pub mod spreadsheet; pub mod utility_types; diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index c09fbb92a..4939092b9 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -1,5 +1,5 @@ use super::document::utility_types::document_metadata::LayerNodeIdentifier; -use super::document::utility_types::network_interface::{self, InputConnector, OutputConnector}; +use super::document::utility_types::network_interface; use super::spreadsheet::SpreadsheetMessageHandler; use super::utility_types::{PanelType, PersistentData}; use crate::application::generate_uuid; @@ -11,21 +11,17 @@ use crate::messages::frontend::utility_types::FrontendDocumentDetails; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::DocumentMessageData; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; -use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT}; use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes; +use crate::messages::portfolio::document_migration::*; use crate::messages::preferences::SelectionMode; use crate::messages::prelude::*; use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType}; use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor}; -use bezier_rs::Subpath; -use glam::{DAffine2, DVec2, IVec2}; -use graph_craft::document::value::TaggedValue; -use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput}; +use glam::{DAffine2, DVec2}; +use graph_craft::document::NodeId; use graphene_std::renderer::Quad; -use graphene_std::text::{Font, TypesettingConfig}; -use graphene_std::vector::style::{Fill, FillType, Gradient, PaintOrder, StrokeAlign}; -use graphene_std::vector::{VectorData, VectorDataTable}; +use graphene_std::text::Font; use std::vec; pub struct PortfolioMessageData<'a> { @@ -429,30 +425,18 @@ impl MessageHandler> for PortfolioMes document_serialized_content, to_front, } => { - // TODO: Eventually remove this document upgrade code - // This big code block contains lots of hacky code for upgrading old documents to the new format - - // Upgrade a document being opened to use fresh copies of all nodes - let replace_implementations_from_definition = reset_node_definitions_on_open || document_serialized_content.contains("node_output_index"); - // Upgrade layer implementation from https://github.com/GraphiteEditor/Graphite/pull/1946 (see also `fn fix_nodes()` in `main.rs` of Graphene CLI) - let upgrade_from_before_returning_nested_click_targets = - document_serialized_content.contains("graphene_core::ConstructLayerNode") || document_serialized_content.contains("graphene_core::AddArtboardNode"); - let upgrade_vector_manipulation_format = document_serialized_content.contains("ManipulatorGroupIds") && !document_name.contains("__DO_NOT_UPGRADE__"); - let document_name = document_name.replace("__DO_NOT_UPGRADE__", ""); - - const TEXT_REPLACEMENTS: [(&str, &str); 2] = [ - ("graphene_core::vector::vector_nodes::SamplePointsNode", "graphene_core::vector::SamplePolylineNode"), - ("graphene_core::vector::vector_nodes::SubpathSegmentLengthsNode", "graphene_core::vector::SubpathSegmentLengthsNode"), - ]; - let document_serialized_content = TEXT_REPLACEMENTS - .iter() - .fold(document_serialized_content, |document_serialized_content, (old, new)| document_serialized_content.replace(old, new)); + // Upgrade the document being opened to use fresh copies of all nodes + let reset_node_definitions_on_open = reset_node_definitions_on_open || document_migration_reset_node_definition(&document_serialized_content); + // Upgrade the document being opened with string replacements on the original JSON + let document_serialized_content = document_migration_string_preprocessing(document_serialized_content); + // Deserialize the document let document = DocumentMessageHandler::deserialize_document(&document_serialized_content).map(|mut document| { document.name.clone_from(&document_name); document }); + // Display an error to the user if the document could not be opened let mut document = match document { Ok(document) => document, Err(e) => { @@ -467,683 +451,14 @@ impl MessageHandler> for PortfolioMes } }; - const REPLACEMENTS: [(&str, &str); 40] = [ - ("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"), - ("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::ops::ConstructVector2", "graphene_core::ops::CoordinateValueNode"), - ("graphene_core::ops::Vector2ValueNode", "graphene_core::ops::CoordinateValueNode"), - ("graphene_core::raster::BlackAndWhiteNode", "graphene_core::raster::adjustments::BlackAndWhiteNode"), - ("graphene_core::raster::BlendNode", "graphene_core::raster::adjustments::BlendNode"), - ("graphene_core::raster::ChannelMixerNode", "graphene_core::raster::adjustments::ChannelMixerNode"), - ("graphene_core::raster::adjustments::ColorOverlayNode", "graphene_core::raster::adjustments::ColorOverlayNode"), - ("graphene_core::raster::ExposureNode", "graphene_core::raster::adjustments::ExposureNode"), - ("graphene_core::raster::ExtractChannelNode", "graphene_core::raster::adjustments::ExtractChannelNode"), - ("graphene_core::raster::GradientMapNode", "graphene_core::raster::adjustments::GradientMapNode"), - ("graphene_core::raster::HueSaturationNode", "graphene_core::raster::adjustments::HueSaturationNode"), - ("graphene_core::vector::GenerateHandlesNode", "graphene_core::vector::AutoTangentsNode"), - ("graphene_core::vector::RemoveHandlesNode", "graphene_core::vector::AutoTangentsNode"), - ("graphene_core::raster::InvertNode", "graphene_core::raster::adjustments::InvertNode"), - ("graphene_core::raster::InvertRGBNode", "graphene_core::raster::adjustments::InvertNode"), - ("graphene_core::raster::LevelsNode", "graphene_core::raster::adjustments::LevelsNode"), - ("graphene_core::raster::LuminanceNode", "graphene_core::raster::adjustments::LuminanceNode"), - ("graphene_core::raster::ExtractOpaqueNode", "graphene_core::raster::adjustments::MakeOpaqueNode"), - ("graphene_core::raster::PosterizeNode", "graphene_core::raster::adjustments::PosterizeNode"), - ("graphene_core::raster::ThresholdNode", "graphene_core::raster::adjustments::ThresholdNode"), - ("graphene_core::raster::VibranceNode", "graphene_core::raster::adjustments::VibranceNode"), - ("graphene_core::text::TextGeneratorNode", "graphene_core::text::TextNode"), - ("graphene_core::transform::SetTransformNode", "graphene_core::transform::ReplaceTransformNode"), - ("graphene_core::vector::SplinesFromPointsNode", "graphene_core::vector::SplineNode"), - ("graphene_core::vector::generator_nodes::EllipseGenerator", "graphene_core::vector::generator_nodes::EllipseNode"), - ("graphene_core::vector::generator_nodes::LineGenerator", "graphene_core::vector::generator_nodes::LineNode"), - ("graphene_core::vector::generator_nodes::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"), - ]; - let mut network = document.network_interface.document_network().clone(); - network.generate_node_paths(&[]); - - let node_ids: Vec<_> = network.recursive_nodes().map(|(&id, node)| (id, node.original_location.path.clone().unwrap())).collect(); - - // Apply upgrades to each node - for (node_id, path) in &node_ids { - let network_path: Vec<_> = path.iter().copied().take(path.len() - 1).collect(); - - if let Some(DocumentNodeImplementation::ProtoNode(protonode_id)) = document - .network_interface - .nested_network(&network_path) - .unwrap() - .nodes - .get(node_id) - .map(|node| node.implementation.clone()) - { - for (old, new) in REPLACEMENTS { - let node_path_without_type_args = protonode_id.name.split('<').next(); - if node_path_without_type_args == Some(old) { - document - .network_interface - .replace_implementation(node_id, &network_path, DocumentNodeImplementation::ProtoNode(new.to_string().into())); - document.network_interface.set_manual_compostion(node_id, &network_path, Some(graph_craft::Type::Generic("T".into()))); - } - } - } - } - - // Upgrade all old nodes to support editable subgraphs introduced in #1750 - if replace_implementations_from_definition || upgrade_from_before_returning_nested_click_targets { - // This can be used, if uncommented, to upgrade demo artwork with outdated document node internals from their definitions. Delete when it's no longer needed. - // Used for upgrading old internal networks for demo artwork nodes. Will reset all node internals for any opened file - for node_id in &document - .network_interface - .document_network_metadata() - .persistent_metadata - .node_metadata - .keys() - .cloned() - .collect::>() - { - if let Some(reference) = document - .network_interface - .document_network_metadata() - .persistent_metadata - .node_metadata - .get(node_id) - .and_then(|node| node.persistent_metadata.reference.as_ref()) - { - let Some(node_definition) = resolve_document_node_type(reference) else { continue }; - let default_definition_node = node_definition.default_node_template(); - document.network_interface.replace_implementation(node_id, &[], default_definition_node.document_node.implementation); - document - .network_interface - .replace_implementation_metadata(node_id, &[], default_definition_node.persistent_node_metadata); - document.network_interface.set_manual_compostion(node_id, &[], default_definition_node.document_node.manual_composition); - } - } - } - - if document - .network_interface - .document_network_metadata() - .persistent_metadata - .node_metadata - .iter() - .any(|(node_id, node)| node.persistent_metadata.reference.as_ref().is_some_and(|reference| reference == "Output") && *node_id == NodeId(0)) - { - document.network_interface.delete_nodes(vec![NodeId(0)], true, &[]); - } - - let mut network = document.network_interface.document_network().clone(); - network.generate_node_paths(&[]); - - let node_ids: Vec<_> = network.recursive_nodes().map(|(&id, node)| (id, node.original_location.path.clone().unwrap())).collect(); - - // Apply upgrades to each node - for (node_id, path) in &node_ids { - let network_path: Vec<_> = path.iter().copied().take(path.len() - 1).collect(); - let network_path = &network_path; - - let Some(node) = document.network_interface.nested_network(network_path).unwrap().nodes.get(node_id).cloned() else { - log::error!("could not get node in deserialize_document"); - continue; - }; - - // 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(node_metadata) = document.network_interface.network_metadata(network_path).unwrap().persistent_metadata.node_metadata.get(node_id) else { - log::error!("could not get node metadata for node {node_id} in deserialize_document"); - continue; - }; - - let Some(ref reference) = node_metadata.persistent_metadata.reference.clone() else { - // TODO: Investigate if this should be an expected case, because currently it runs hundreds of times normally. - // TODO: Either delete the commented out error below if this is normal, or fix the underlying issue if this is not expected. - // log::error!("could not get reference in deserialize_document"); - continue; - }; - - let inputs_count = node.inputs.len(); - - // Upgrade Fill nodes to the format change in #1778 - if reference == "Fill" && inputs_count == 8 { - let node_definition = resolve_document_node_type(reference).unwrap(); - let document_node = node_definition.default_node_template().document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - - let Some(fill_type) = old_inputs[1].as_value().cloned() else { continue }; - let TaggedValue::FillType(fill_type) = fill_type else { continue }; - let Some(solid_color) = old_inputs[2].as_value().cloned() else { continue }; - let TaggedValue::OptionalColor(solid_color) = solid_color else { continue }; - let Some(gradient_type) = old_inputs[3].as_value().cloned() else { continue }; - let TaggedValue::GradientType(gradient_type) = gradient_type else { continue }; - let Some(start) = old_inputs[4].as_value().cloned() else { continue }; - let TaggedValue::DVec2(start) = start else { continue }; - let Some(end) = old_inputs[5].as_value().cloned() else { continue }; - let TaggedValue::DVec2(end) = end else { continue }; - let Some(transform) = old_inputs[6].as_value().cloned() else { continue }; - let TaggedValue::DAffine2(transform) = transform else { continue }; - let Some(positions) = old_inputs[7].as_value().cloned() else { continue }; - let TaggedValue::GradientStops(positions) = positions else { continue }; - - let fill = match (fill_type, solid_color) { - (FillType::Solid, None) => Fill::None, - (FillType::Solid, Some(color)) => Fill::Solid(color), - (FillType::Gradient, _) => Fill::Gradient(Gradient { - stops: positions, - gradient_type, - start, - end, - transform, - }), - }; - document - .network_interface - .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Fill(fill.clone()), false), network_path); - match fill { - Fill::None => { - document - .network_interface - .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::OptionalColor(None), false), network_path); - } - Fill::Solid(color) => { - document - .network_interface - .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::OptionalColor(Some(color)), false), network_path); - } - Fill::Gradient(gradient) => { - document - .network_interface - .set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Gradient(gradient), false), network_path); - } - } - } - - // Upgrade Stroke node to reorder parameters and add "Align" and "Paint Order" (#2644) - if reference == "Stroke" && inputs_count == 8 { - let node_definition = resolve_document_node_type(reference).unwrap(); - let document_node = node_definition.default_node_template().document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - document.network_interface.insert_input_properties_row(node_id, 8, network_path); - document.network_interface.insert_input_properties_row(node_id, 9, network_path); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - 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 != 8 { - let node_definition = resolve_document_node_type(reference).unwrap(); - let document_node = node_definition.default_node_template().document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - 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), - NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false), - network_path, - ); - document.network_interface.set_input( - &InputConnector::node(*node_id, 7), - NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), 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 node_definition = resolve_document_node_type(reference).unwrap(); - let document_node = node_definition.default_node_template().document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - 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 node_definition = resolve_document_node_type(reference).unwrap(); - let document_node = node_definition.default_node_template().document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - 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 node_definition = resolve_document_node_type(reference).unwrap(); - let document_node = node_definition.default_node_template().document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - 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 node_definition = resolve_document_node_type(reference).unwrap(); - let new_node_template = node_definition.default_node_template(); - let document_node = new_node_template.document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - document - .network_interface - .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - 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" && upgrade_from_before_returning_nested_click_targets { - 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 node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap(); - let new_image_node = node_definition.default_node_template(); - document.network_interface.replace_implementation(node_id, network_path, new_image_node.document_node.implementation); - - // 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 node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap(); - let new_noise_pattern_node = node_definition.default_node_template(); - document - .network_interface - .replace_implementation(node_id, network_path, new_noise_pattern_node.document_node.implementation); - - let old_inputs = document.network_interface.replace_inputs(node_id, new_noise_pattern_node.document_node.inputs.clone(), network_path); - - 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 node_definition = resolve_document_node_type(reference).unwrap(); - let new_node_template = node_definition.default_node_template(); - let document_node = new_node_template.document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - document - .network_interface - .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - 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 node_definition = resolve_document_node_type(reference).unwrap(); - let new_node_template = node_definition.default_node_template(); - let document_node = new_node_template.document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - document - .network_interface - .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - 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 node_definition = resolve_document_node_type(reference).unwrap(); - let new_node_template = node_definition.default_node_template(); - let document_node = new_node_template.document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - document - .network_interface - .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - 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 node_definition = resolve_document_node_type("Flatten Path").unwrap(); - let new_node_template = node_definition.default_node_template(); - let document_node = new_node_template.document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - document - .network_interface - .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - 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 node_definition = resolve_document_node_type("Auto-Tangents").unwrap(); - let new_node_template = node_definition.default_node_template(); - let document_node = new_node_template.document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - document - .network_interface - .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - 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 node_definition = resolve_document_node_type("Auto-Tangents").unwrap(); - let new_node_template = node_definition.default_node_template(); - let document_node = new_node_template.document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - document - .network_interface - .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - 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 node_definition = resolve_document_node_type("Merge by Distance").unwrap(); - let new_node_template = node_definition.default_node_template(); - let document_node = new_node_template.document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - document - .network_interface - .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - 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 node_definition = resolve_document_node_type("Merge by Distance").unwrap(); - let new_node_template = node_definition.default_node_template(); - let document_node = new_node_template.document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - document - .network_interface - .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - 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 node_definition = resolve_document_node_type("Sample Polyline").unwrap(); - let new_node_template = node_definition.default_node_template(); - let document_node = new_node_template.document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - document - .network_interface - .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), 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), old_inputs[1].clone(), 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()); - } - } - - // TODO: Eventually remove this document upgrade code - // Upgrade document to the new vector manipulation format introduced in #1676 - let document_serialized_content = document.serialize_document(); - if upgrade_vector_manipulation_format && !document_serialized_content.is_empty() { - responses.add(FrontendMessage::TriggerUpgradeDocumentToVectorManipulationFormat { - document_id, - document_name, - document_is_auto_saved, - document_is_saved, - document_serialized_content, - }); - return; - } - - // Ensure layers are positioned as stacks if they 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, &[]); - } - } - } + // Upgrade the document's nodes to be compatible with the latest version + document_migration_upgrades(&mut document, reset_node_definitions_on_open); + // Set the save state of the document based on what's given to us by the caller to this message document.set_auto_save_state(document_is_auto_saved); document.set_save_state(document_is_saved); + // Load the document into the portfolio so it opens in the editor self.load_document(document, document_id, responses, to_front); } PortfolioMessage::PasteIntoFolder { clipboard, parent, insert_index } => { diff --git a/frontend/src/messages.ts b/frontend/src/messages.ts index a2f07809e..399beb50c 100644 --- a/frontend/src/messages.ts +++ b/frontend/src/messages.ts @@ -940,15 +940,6 @@ export class TriggerAboutGraphiteLocalizedCommitDate extends JsMessage { readonly commitDate!: string; } -// TODO: Eventually remove this document upgrade code -export class TriggerUpgradeDocumentToVectorManipulationFormat extends JsMessage { - readonly documentId!: bigint; - readonly documentName!: string; - readonly documentIsAutoSaved!: boolean; - readonly documentIsSaved!: boolean; - readonly documentSerializedContent!: string; -} - // WIDGET PROPS export abstract class WidgetProps { @@ -1679,7 +1670,6 @@ export const messageMakers: Record = { TriggerSavePreferences, TriggerTextCommit, TriggerTextCopy, - TriggerUpgradeDocumentToVectorManipulationFormat, TriggerVisitLink, UpdateActiveDocument, UpdateBox, diff --git a/frontend/src/state-providers/portfolio.ts b/frontend/src/state-providers/portfolio.ts index 1cf63a281..f425ba17d 100644 --- a/frontend/src/state-providers/portfolio.ts +++ b/frontend/src/state-providers/portfolio.ts @@ -10,7 +10,6 @@ import { TriggerDownloadTextFile, TriggerImport, TriggerOpenDocument, - TriggerUpgradeDocumentToVectorManipulationFormat, UpdateActiveDocument, UpdateOpenDocumentsList, UpdateSpreadsheetState, @@ -104,11 +103,6 @@ export function createPortfolioState(editor: Editor) { // Fail silently if there's an error rasterizing the SVG, such as a zero-sized image } }); - editor.subscriptions.subscribeJsMessage(TriggerUpgradeDocumentToVectorManipulationFormat, async (triggerUpgradeDocumentToVectorManipulationFormat) => { - // TODO: Eventually remove this document upgrade code - const { documentId, documentName, documentIsAutoSaved, documentIsSaved, documentSerializedContent } = triggerUpgradeDocumentToVectorManipulationFormat; - editor.handle.triggerUpgradeDocumentToVectorManipulationFormat(documentId, documentName, documentIsAutoSaved, documentIsSaved, documentSerializedContent); - }); editor.subscriptions.subscribeJsMessage(UpdateSpreadsheetState, async (updateSpreadsheetState) => { update((state) => { diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index 88bc85bc3..80bfd7fce 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -11,7 +11,7 @@ use editor::consts::FILE_SAVE_SUFFIX; use editor::messages::input_mapper::utility_types::input_keyboard::ModifierKeys; use editor::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, ScrollDelta, ViewportBounds}; use editor::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use editor::messages::portfolio::document::utility_types::network_interface::{ImportOrExport, NodeTemplate}; +use editor::messages::portfolio::document::utility_types::network_interface::ImportOrExport; use editor::messages::portfolio::utility_types::Platform; use editor::messages::prelude::*; use editor::messages::tool::tool_messages::tool_prelude::WidgetId; @@ -734,227 +734,6 @@ impl EditorHandle { }; self.dispatch(message); } - - // #[wasm_bindgen(js_name = injectImaginatePollServerStatus)] - // pub fn inject_imaginate_poll_server_status(&self) { - // self.dispatch(PortfolioMessage::ImaginatePollServerStatus); - // } - - // TODO: Eventually remove this document upgrade code - #[wasm_bindgen(js_name = triggerUpgradeDocumentToVectorManipulationFormat)] - pub async fn upgrade_document_to_vector_manipulation_format( - &self, - document_id: u64, - document_name: String, - document_is_auto_saved: bool, - document_is_saved: bool, - document_serialized_content: String, - ) { - use editor::messages::portfolio::document::graph_operation::transform_utils::*; - use editor::messages::portfolio::document::graph_operation::utility_types::*; - use editor::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; - use editor::node_graph_executor::NodeRuntime; - use editor::node_graph_executor::replace_node_runtime; - use graph_craft::document::DocumentNodeImplementation; - use graph_craft::document::NodeInput; - use graph_craft::document::value::TaggedValue; - use graphene_std::vector::*; - - let (_, request_receiver) = std::sync::mpsc::channel(); - let (response_sender, _) = std::sync::mpsc::channel(); - let old_runtime = replace_node_runtime(NodeRuntime::new(request_receiver, response_sender)).await; - - let mut editor = Editor::new(); - let document_id = DocumentId(document_id); - editor.handle_message(PortfolioMessage::OpenDocumentFileWithId { - document_id, - document_name: document_name.clone(), - document_is_auto_saved, - document_is_saved, - document_serialized_content: document_serialized_content.clone(), - to_front: false, - }); - - let Some(document) = editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut() else { - warn!("Document wasn't loaded"); - return; - }; - for node in document - .network_interface - .document_network_metadata() - .persistent_metadata - .node_metadata - .iter() - .filter(|(_, d)| d.persistent_metadata.reference.as_ref().is_some_and(|reference| reference == "Artboard")) - .map(|(id, _)| *id) - .collect::>() - { - let Some(document_node) = document.network_interface.document_network().nodes.get(&node) else { - log::error!("Could not get document node in document network"); - return; - }; - if let Some(network) = document_node.implementation.get_network() { - let mut nodes_to_upgrade = Vec::new(); - for (node_id, _) in network.nodes.iter().collect::>() { - if document - .network_interface - .reference(node_id, &[]) - .is_some_and(|reference| *reference == Some("To Artboard".to_string())) - && document - .network_interface - .document_network() - .nodes - .get(node_id) - .is_some_and(|document_node| document_node.inputs.len() != 6) - { - nodes_to_upgrade.push(*node_id); - } - } - for node_id in nodes_to_upgrade { - document - .network_interface - .replace_implementation(&node_id, &[], DocumentNodeImplementation::proto("graphene_core::ToArtboardNode")); - document.network_interface.add_import(TaggedValue::IVec2(glam::IVec2::default()), false, 2, "", "", &[node_id]); - } - } - } - - let portfolio = &mut editor.dispatcher.message_handlers.portfolio_message_handler; - portfolio - .executor - .submit_node_graph_evaluation( - portfolio.documents.get_mut(&portfolio.active_document_id().unwrap()).unwrap(), - glam::UVec2::ONE, - Default::default(), - None, - true, - ) - .unwrap(); - editor::node_graph_executor::run_node_graph().await; - - let mut messages = VecDeque::new(); - if let Err(err) = editor.poll_node_graph_evaluation(&mut messages) { - log::warn!( - "While attempting to upgrade the old document format, the graph evaluation failed which is necessary for the upgrade process:\n{:#?}", - err - ); - - replace_node_runtime(old_runtime.unwrap()).await; - - let document_name = document_name.clone() + "__DO_NOT_UPGRADE__"; - self.dispatch(PortfolioMessage::OpenDocumentFileWithId { - document_id, - document_name, - document_is_auto_saved, - document_is_saved, - document_serialized_content, - to_front: false, - }); - return; - } - - let mut updated_nodes = HashSet::new(); - let document = editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap(); - document.network_interface.load_structure(); - for node in document - .network_interface - .document_network_metadata() - .persistent_metadata - .node_metadata - .iter() - .filter(|(_, d)| d.persistent_metadata.reference.as_ref().is_some_and(|reference| reference == "Merge")) - .map(|(id, _)| *id) - .collect::>() - { - let layer = LayerNodeIdentifier::new(node, &document.network_interface, &[]); - if layer.has_children(document.metadata()) { - continue; - } - - let bounds = LayerBounds::new(document.metadata(), layer); - - let mut responses = VecDeque::new(); - let mut shape = None; - - if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, &mut document.network_interface, &mut responses) { - let Some(transform_node_id) = modify_inputs.existing_node_id("Transform", true) else { - return; - }; - if !updated_nodes.insert(transform_node_id) { - return; - } - let Some(inputs) = modify_inputs.network_interface.document_network().nodes.get(&transform_node_id).map(|node| &node.inputs) else { - log::error!("Could not get transform node in document network"); - return; - }; - let transform = get_current_transform(inputs); - let upstream_transform = modify_inputs.network_interface.document_metadata().upstream_transform(transform_node_id); - let pivot_transform = glam::DAffine2::from_translation(upstream_transform.transform_point2(bounds.local_pivot(get_current_normalized_pivot(inputs)))); - - update_transform(&mut document.network_interface, &transform_node_id, pivot_transform * transform * pivot_transform.inverse()); - } - if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, &mut document.network_interface, &mut responses) { - let Some(shape_node_id) = modify_inputs.existing_node_id("Shape", true) else { - return; - }; - if !updated_nodes.insert(shape_node_id) { - return; - } - let Some(shape_node) = modify_inputs.network_interface.document_network().nodes.get(&shape_node_id) else { - log::error!("Could not get shape node in document network"); - return; - }; - let path_data = match &shape_node.inputs[0].as_value() { - Some(TaggedValue::Subpaths(translation)) => translation, - _ => &Vec::new(), - }; - - let colinear_manipulators = match &shape_node.inputs[1].as_value() { - Some(TaggedValue::PointIds(translation)) => translation, - _ => &Vec::new(), - }; - - let mut vector_data = VectorData::from_subpaths(path_data, false); - vector_data.colinear_manipulators = colinear_manipulators - .iter() - .filter_map(|&point| ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data)) - .collect(); - - shape = Some((shape_node_id, VectorModification::create_from_vector(&vector_data))); - } - - if let Some((node_id, modification)) = shape { - let node_type = resolve_document_node_type("Path").unwrap(); - let document_node = node_type - .node_template_input_override([None, Some(NodeInput::value(TaggedValue::VectorModification(Box::new(modification)), false))]) - .document_node; - - let node_metadata = document.network_interface.node_metadata(&node_id, &[]).cloned().unwrap_or_default(); - - document.network_interface.insert_node( - node_id, - NodeTemplate { - document_node, - persistent_node_metadata: node_metadata.persistent_metadata, - }, - &[], - ); - } - } - - let document_serialized_content = editor.dispatcher.message_handlers.portfolio_message_handler.active_document_mut().unwrap().serialize_document(); - - replace_node_runtime(old_runtime.unwrap()).await; - - self.dispatch(PortfolioMessage::OpenDocumentFileWithId { - document_id, - document_name, - document_is_auto_saved, - document_is_saved, - document_serialized_content, - to_front: false, - }); - } } // ============================================================================