From f610138959bef184a0181ba258fbe75c60d8e184 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Sat, 6 Sep 2025 23:38:36 -0700 Subject: [PATCH] Fix most known issues with migrations failing to open documents from the past year --- .../document/document_message_handler.rs | 2 +- .../portfolio/document/utility_types/error.rs | 8 +- .../utility_types/network_interface.rs | 6 +- .../messages/portfolio/document_migration.rs | 4 + .../messages/portfolio/portfolio_message.rs | 1 + .../portfolio/portfolio_message_handler.rs | 29 ++++--- frontend/wasm/src/editor_api.rs | 1 + node-graph/gbrush/src/brush_cache.rs | 16 ++-- node-graph/gcore/src/artboard.rs | 28 ++++++- node-graph/gcore/src/context_modification.rs | 2 + node-graph/gcore/src/graphic.rs | 63 ++++++++++++++- node-graph/gcore/src/misc.rs | 10 +-- node-graph/gcore/src/raster/image.rs | 81 ++++++++++++++++--- .../gcore/src/vector/vector_attributes.rs | 5 +- node-graph/gcore/src/vector/vector_types.rs | 41 ++++++++-- node-graph/graph-craft/src/document.rs | 24 +++++- node-graph/graph-craft/src/document/value.rs | 4 +- .../interpreted-executor/src/node_registry.rs | 50 +++++++++++- 18 files changed, 313 insertions(+), 62 deletions(-) diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 06a323dd74..64cce7f12a 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -1786,7 +1786,7 @@ impl DocumentMessageHandler { pub fn deserialize_document(serialized_content: &str) -> Result { let document_message_handler = serde_json::from_str::(serialized_content) .or_else(|e| { - log::warn!("failed to directly load document with the following error: {e}. Trying old DocumentMessageHandler"); + log::warn!("Failed to directly load document with the following error: {e}. Trying old DocumentMessageHandler."); // TODO: Eventually remove this document upgrade code #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct OldDocumentMessageHandler { diff --git a/editor/src/messages/portfolio/document/utility_types/error.rs b/editor/src/messages/portfolio/document/utility_types/error.rs index 96211441b0..5080f46381 100644 --- a/editor/src/messages/portfolio/document/utility_types/error.rs +++ b/editor/src/messages/portfolio/document/utility_types/error.rs @@ -16,7 +16,13 @@ pub enum EditorError { #[error("The operation caused a document error:\n{0:?}")] Document(String), - #[error("This document was created in an older version of the editor.\n\nBackwards compatibility is, regrettably, not present in the current alpha release.\n\nTechnical details:\n{0:?}")] + #[error( + "This document was created in an older version of the editor.\n\ + \n\ + Full backwards compatibility is not guaranteed in the current alpha release.\n\ + \n\ + If this document is critical, ask for support in Graphite's Discord community." + )] DocumentDeserialization(String), #[error("{0}")] diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 52cae9b6ca..d6c47223b9 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -568,7 +568,7 @@ impl NodeNetworkInterface { let skip_footprint = 1; let Some(input_type) = std::iter::once(node_types.call_argument.clone()).chain(node_types.inputs.clone()).nth(input_index + skip_footprint) else { - log::error!("Could not get type for {node_id_path:?}, input: {input_index}"); + // log::warn!("Could not get type for {node_id_path:?}, input: {input_index}"); return (concrete!(()), TypeSource::Error("could not get the protonode's input")); }; @@ -2629,7 +2629,7 @@ impl NodeNetworkInterface { InputConnector::Node { node_id, input_index } => { let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else { return }; let Some(input_metadata) = node_metadata.persistent_metadata.input_metadata.get_mut(*input_index) else { - log::error!("Node metadata must exist on node: {input:?}"); + // log::warn!("Node metadata must exist on node: {input:?}"); return; }; let wire_update = WirePathUpdate { @@ -2721,7 +2721,7 @@ impl NodeNetworkInterface { return; }; let Some(input_metadata) = node_metadata.persistent_metadata.input_metadata.get_mut(*input_index) else { - log::error!("Node metadata must exist on node: {input:?}"); + // log::warn!("Node metadata must exist on node: {input:?}"); return; }; input_metadata.transient_metadata.wire = TransientMetadata::Unloaded; diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index bb7cd54e32..599e06d209 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -113,6 +113,10 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[ node: graphene_std::math_nodes::root::IDENTIFIER, aliases: &["graphene_core::ops::RootNode"], }, + NodeReplacement { + node: graphene_std::math_nodes::absolute_value::IDENTIFIER, + aliases: &["graphene_core::ops::AbsoluteValueNode"], + }, NodeReplacement { node: graphene_std::math_nodes::logarithm::IDENTIFIER, aliases: &["graphene_core::ops::LogarithmNode"], diff --git a/editor/src/messages/portfolio/portfolio_message.rs b/editor/src/messages/portfolio/portfolio_message.rs index 75717bac42..763d73b629 100644 --- a/editor/src/messages/portfolio/portfolio_message.rs +++ b/editor/src/messages/portfolio/portfolio_message.rs @@ -79,6 +79,7 @@ pub enum PortfolioMessage { document_is_saved: bool, document_serialized_content: String, to_front: bool, + select_after_open: bool, }, ToggleResetNodesToDefinitionsOnOpen, PasteIntoFolder { diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 5fd1a89849..c83346c885 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -422,17 +422,16 @@ impl MessageHandler> for Portfolio document_path, document_serialized_content, } => { - let document_id = DocumentId(generate_uuid()); responses.add(PortfolioMessage::OpenDocumentFileWithId { - document_id, + document_id: DocumentId(generate_uuid()), document_name, document_path, document_is_auto_saved: false, document_is_saved: true, document_serialized_content, to_front: false, + select_after_open: true, }); - responses.add(PortfolioMessage::SelectDocument { document_id }); } PortfolioMessage::ToggleResetNodesToDefinitionsOnOpen => { self.reset_node_definitions_on_open = !self.reset_node_definitions_on_open; @@ -446,6 +445,7 @@ impl MessageHandler> for Portfolio document_is_saved, document_serialized_content, to_front, + select_after_open, } => { // 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); @@ -540,6 +540,10 @@ impl MessageHandler> for Portfolio // Load the document into the portfolio so it opens in the editor self.load_document(document, document_id, self.layers_panel_open, responses, to_front); + + if select_after_open { + responses.add(PortfolioMessage::SelectDocument { document_id }); + } } PortfolioMessage::PasteIntoFolder { clipboard, parent, insert_index } => { let mut all_new_ids = Vec::new(); @@ -954,14 +958,15 @@ impl MessageHandler> for Portfolio } PortfolioMessage::SubmitGraphRender { document_id, ignore_hash } => { let node_to_inspect = self.node_to_inspect(); - let result = self.executor.submit_node_graph_evaluation( - self.documents.get_mut(&document_id).expect("Tried to render non-existent document"), - document_id, - ipp.viewport_bounds.size().as_uvec2(), - timing_information, - node_to_inspect, - ignore_hash, - ); + let Some(document) = self.documents.get_mut(&document_id) else { + log::error!("Tried to render non-existent document"); + return; + }; + let viewport_resolution = ipp.viewport_bounds.size().as_uvec2(); + + let result = self + .executor + .submit_node_graph_evaluation(document, document_id, viewport_resolution, timing_information, node_to_inspect, ignore_hash); match result { Err(description) => { @@ -1173,7 +1178,7 @@ impl PortfolioMessageHandler { /// Returns an iterator over the open documents in order. pub fn ordered_document_iterator(&self) -> impl Iterator { - self.document_ids.iter().map(|id| self.documents.get(id).expect("document id was not found in the document hashmap")) + self.document_ids.iter().map(|id| self.documents.get(id).expect("Document id was not found in the document hashmap")) } fn document_index(&self, document_id: DocumentId) -> usize { diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index c0539ce61c..da1901815d 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -466,6 +466,7 @@ impl EditorHandle { document_is_saved, document_serialized_content, to_front, + select_after_open: false, }; self.dispatch(message); } diff --git a/node-graph/gbrush/src/brush_cache.rs b/node-graph/gbrush/src/brush_cache.rs index 02d3a3c423..01d0276529 100644 --- a/node-graph/gbrush/src/brush_cache.rs +++ b/node-graph/gbrush/src/brush_cache.rs @@ -10,21 +10,23 @@ use std::hash::Hasher; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::{Arc, Mutex}; -// TODO: This is a temporary hack, be sure to not reuse this when the brush is being rewritten. +// TODO: This is a temporary hack, be sure to not reuse this when the brush system is replaced/rewritten. static NEXT_BRUSH_CACHE_IMPL_ID: AtomicU64 = AtomicU64::new(0); #[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)] struct BrushCacheImpl { + #[serde(default = "new_unique_id")] unique_id: u64, // The full previous input that was cached. + #[serde(default)] prev_input: Vec, // The strokes that have been fully processed and blended into the background. - #[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_row")] + #[serde(default, deserialize_with = "graphene_core::raster::image::migrate_image_frame_row")] background: TableRow>, - #[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_row")] + #[serde(default, deserialize_with = "graphene_core::raster::image::migrate_image_frame_row")] blended_image: TableRow>, - #[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_row")] + #[serde(default, deserialize_with = "graphene_core::raster::image::migrate_image_frame_row")] last_stroke_texture: TableRow>, // A cache for brush textures. @@ -98,7 +100,7 @@ impl BrushCacheImpl { impl Default for BrushCacheImpl { fn default() -> Self { Self { - unique_id: NEXT_BRUSH_CACHE_IMPL_ID.fetch_add(1, Ordering::SeqCst), + unique_id: new_unique_id(), prev_input: Vec::new(), background: Default::default(), blended_image: Default::default(), @@ -120,6 +122,10 @@ impl Hash for BrushCacheImpl { } } +fn new_unique_id() -> u64 { + NEXT_BRUSH_CACHE_IMPL_ID.fetch_add(1, Ordering::SeqCst) +} + #[derive(Clone, Debug, Default)] pub struct BrushPlan { pub strokes: Vec, diff --git a/node-graph/gcore/src/artboard.rs b/node-graph/gcore/src/artboard.rs index a2fb0a779a..6a95ac356d 100644 --- a/node-graph/gcore/src/artboard.rs +++ b/node-graph/gcore/src/artboard.rs @@ -68,13 +68,22 @@ pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Re #[derive(serde::Serialize, serde::Deserialize)] #[serde(untagged)] - enum EitherFormat { + enum ArtboardFormat { ArtboardGroup(ArtboardGroup), + OldArtboardTable(OldTable), ArtboardTable(Table), } - Ok(match EitherFormat::deserialize(deserializer)? { - EitherFormat::ArtboardGroup(artboard_group) => { + #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] + pub struct OldTable { + #[serde(alias = "instances", alias = "instance")] + element: Vec, + transform: Vec, + alpha_blending: Vec, + } + + Ok(match ArtboardFormat::deserialize(deserializer)? { + ArtboardFormat::ArtboardGroup(artboard_group) => { let mut table = Table::new(); for (artboard, source_node_id) in artboard_group.artboards { table.push(TableRow { @@ -86,7 +95,18 @@ pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Re } table } - EitherFormat::ArtboardTable(artboard_table) => artboard_table, + ArtboardFormat::OldArtboardTable(old_table) => old_table + .element + .into_iter() + .zip(old_table.transform.into_iter().zip(old_table.alpha_blending)) + .map(|(element, (transform, alpha_blending))| TableRow { + element, + transform, + alpha_blending, + source_node_id: None, + }) + .collect(), + ArtboardFormat::ArtboardTable(artboard_table) => artboard_table, }) } diff --git a/node-graph/gcore/src/context_modification.rs b/node-graph/gcore/src/context_modification.rs index d6fb1d7d14..54c2eabf24 100644 --- a/node-graph/gcore/src/context_modification.rs +++ b/node-graph/gcore/src/context_modification.rs @@ -3,6 +3,7 @@ use crate::context::{CloneVarArgs, Context, ContextFeatures, Ctx, ExtractAll}; use crate::gradient::GradientStops; use crate::raster_types::{CPU, GPU, Raster}; use crate::table::Table; +use crate::transform::Footprint; use crate::uuid::NodeId; use crate::vector::Vector; use crate::{Graphic, OwnedContextImpl}; @@ -24,6 +25,7 @@ async fn context_modification( Context -> f64, Context -> String, Context -> DAffine2, + Context -> Footprint, Context -> DVec2, Context -> Vec, Context -> Vec, diff --git a/node-graph/gcore/src/graphic.rs b/node-graph/gcore/src/graphic.rs index f902826c99..324c93e33d 100644 --- a/node-graph/gcore/src/graphic.rs +++ b/node-graph/gcore/src/graphic.rs @@ -507,15 +507,34 @@ pub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Res elements: Vec<(Graphic, Option)>, } + #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] + pub struct OlderTable { + id: Vec, + #[serde(alias = "instances", alias = "instance")] + element: Vec, + } + + #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] + pub struct OldTable { + id: Vec, + #[serde(alias = "instances", alias = "instance")] + element: Vec, + transform: Vec, + alpha_blending: Vec, + } + #[derive(serde::Serialize, serde::Deserialize)] #[serde(untagged)] - enum EitherFormat { + enum GraphicFormat { OldGraphicGroup(OldGraphicGroup), + OlderTableOldGraphicGroup(OlderTable), + OldTableOldGraphicGroup(OldTable), + OldTableGraphicGroup(OldTable), Table(serde_json::Value), } - Ok(match EitherFormat::deserialize(deserializer)? { - EitherFormat::OldGraphicGroup(old) => { + Ok(match GraphicFormat::deserialize(deserializer)? { + GraphicFormat::OldGraphicGroup(old) => { let mut graphic_table = Table::new(); for (graphic, source_node_id) in old.elements { graphic_table.push(TableRow { @@ -527,7 +546,43 @@ pub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Res } graphic_table } - EitherFormat::Table(value) => { + GraphicFormat::OlderTableOldGraphicGroup(old) => old + .element + .into_iter() + .flat_map(|element| { + element.elements.into_iter().map(move |(graphic, source_node_id)| TableRow { + element: graphic, + transform: element.transform, + alpha_blending: element.alpha_blending, + source_node_id, + }) + }) + .collect(), + GraphicFormat::OldTableOldGraphicGroup(old) => old + .element + .into_iter() + .flat_map(|element| { + element.elements.into_iter().map(move |(graphic, source_node_id)| TableRow { + element: graphic, + transform: element.transform, + alpha_blending: element.alpha_blending, + source_node_id, + }) + }) + .collect(), + GraphicFormat::OldTableGraphicGroup(old) => old + .element + .into_iter() + .flat_map(|element| { + element.elements.into_iter().map(move |(graphic, source_node_id)| TableRow { + element: graphic, + transform: Default::default(), + alpha_blending: Default::default(), + source_node_id, + }) + }) + .collect(), + GraphicFormat::Table(value) => { // Try to deserialize as either table format if let Ok(old_table) = serde_json::from_value::>(value.clone()) { let mut graphic_table = Table::new(); diff --git a/node-graph/gcore/src/misc.rs b/node-graph/gcore/src/misc.rs index f0c452cdea..46ac98865a 100644 --- a/node-graph/gcore/src/misc.rs +++ b/node-graph/gcore/src/misc.rs @@ -69,21 +69,21 @@ pub fn migrate_color<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Resul #[derive(serde::Serialize, serde::Deserialize)] #[serde(untagged)] - enum EitherFormat { + enum ColorFormat { Color(Color), OptionalColor(Option), ColorTable(Table), } - Ok(match EitherFormat::deserialize(deserializer)? { - EitherFormat::Color(color) => Table::new_from_element(color), - EitherFormat::OptionalColor(color) => { + Ok(match ColorFormat::deserialize(deserializer)? { + ColorFormat::Color(color) => Table::new_from_element(color), + ColorFormat::OptionalColor(color) => { if let Some(color) = color { Table::new_from_element(color) } else { Table::new() } } - EitherFormat::ColorTable(color_table) => color_table, + ColorFormat::ColorTable(color_table) => color_table, }) } diff --git a/node-graph/gcore/src/raster/image.rs b/node-graph/gcore/src/raster/image.rs index c58d493af8..6549e584c5 100644 --- a/node-graph/gcore/src/raster/image.rs +++ b/node-graph/gcore/src/raster/image.rs @@ -218,7 +218,6 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> #[derive(Clone, Debug, Hash, PartialEq, DynAny)] enum RasterFrame { - /// A CPU-based bitmap image with a finite position and extent, equivalent to the SVG tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/image ImageFrame(Table>), } impl<'de> serde::Deserialize<'de> for RasterFrame { @@ -236,9 +235,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> #[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)] pub enum GraphicElement { - /// Equivalent to the SVG tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g GraphicGroup(Table), - /// A vector shape, equivalent to the SVG tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path VectorData(Table), RasterFrame(RasterFrame), } @@ -283,11 +280,73 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> enum FormatVersions { Image(Image), OldImageFrame(OldImageFrame), + OlderImageFrameTable(OlderTable>), + OldImageFrameTable(OldTable>), + OldImageTable(OldTable>), + OldRasterTable(OldTable>), ImageFrameTable(Table>), ImageTable(Table>), RasterTable(Table>), } + #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] + pub struct OldTable { + #[serde(alias = "instances", alias = "instance")] + element: Vec, + transform: Vec, + alpha_blending: Vec, + } + + #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] + pub struct OlderTable { + id: Vec, + #[serde(alias = "instances", alias = "instance")] + element: Vec, + } + + fn from_image_table(table: Table>) -> Table> { + Table::new_from_element(Raster::new_cpu(table.iter().next().unwrap().element.clone())) + } + + fn old_table_to_new_table(old_table: OldTable) -> Table { + old_table + .element + .into_iter() + .zip(old_table.transform.into_iter().zip(old_table.alpha_blending)) + .map(|(element, (transform, alpha_blending))| TableRow { + element, + transform, + alpha_blending, + source_node_id: None, + }) + .collect() + } + + fn older_table_to_new_table(old_table: OlderTable) -> Table { + old_table + .element + .into_iter() + .map(|element| TableRow { + element, + transform: DAffine2::IDENTITY, + alpha_blending: AlphaBlending::default(), + source_node_id: None, + }) + .collect() + } + + fn from_image_frame_table(image_frame: Table>) -> Table> { + Table::new_from_element(Raster::new_cpu( + image_frame + .iter() + .next() + .unwrap_or(Table::new_from_element(ImageFrame::default()).iter().next().unwrap()) + .element + .image + .clone(), + )) + } + Ok(match FormatVersions::deserialize(deserializer)? { FormatVersions::Image(image) => Table::new_from_element(Raster::new_cpu(image)), FormatVersions::OldImageFrame(OldImageFrame { image, transform, alpha_blending }) => { @@ -296,16 +355,12 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> *image_frame_table.iter_mut().next().unwrap().alpha_blending = alpha_blending; image_frame_table } - FormatVersions::ImageFrameTable(image_frame) => Table::new_from_element(Raster::new_cpu( - image_frame - .iter() - .next() - .unwrap_or(Table::new_from_element(ImageFrame::default()).iter().next().unwrap()) - .element - .image - .clone(), - )), - FormatVersions::ImageTable(table) => Table::new_from_element(Raster::new_cpu(table.iter().next().unwrap().element.clone())), + FormatVersions::OlderImageFrameTable(old_table) => from_image_frame_table(older_table_to_new_table(old_table)), + FormatVersions::OldImageFrameTable(old_table) => from_image_frame_table(old_table_to_new_table(old_table)), + FormatVersions::OldImageTable(old_table) => from_image_table(old_table_to_new_table(old_table)), + FormatVersions::OldRasterTable(old_table) => old_table_to_new_table(old_table), + FormatVersions::ImageFrameTable(image_frame) => from_image_frame_table(image_frame), + FormatVersions::ImageTable(table) => from_image_table(table), FormatVersions::RasterTable(table) => table, }) } diff --git a/node-graph/gcore/src/vector/vector_attributes.rs b/node-graph/gcore/src/vector/vector_attributes.rs index 461e3a1abf..33d87e6ce7 100644 --- a/node-graph/gcore/src/vector/vector_attributes.rs +++ b/node-graph/gcore/src/vector/vector_attributes.rs @@ -306,7 +306,10 @@ impl SegmentDomain { } pub fn push(&mut self, id: SegmentId, start: usize, end: usize, handles: BezierHandles, stroke: StrokeId) { - debug_assert!(!self.id.contains(&id), "Tried to push an existing point to a point domain"); + #[cfg(debug_assertions)] + if self.id.contains(&id) { + warn!("Tried to push an existing point to a point domain"); + } self.id.push(id); self.start_point.push(start); diff --git a/node-graph/gcore/src/vector/vector_types.rs b/node-graph/gcore/src/vector/vector_types.rs index 84b4acd365..7f26e40935 100644 --- a/node-graph/gcore/src/vector/vector_types.rs +++ b/node-graph/gcore/src/vector/vector_types.rs @@ -5,7 +5,7 @@ pub use super::vector_modification::*; use crate::bounds::{BoundingBox, RenderBoundingBox}; use crate::math::quad::Quad; use crate::subpath::{BezierHandles, ManipulatorGroup, Subpath}; -use crate::table::Table; +use crate::table::{Table, TableRow}; use crate::transform::Transform; use crate::vector::click_target::{ClickTargetType, FreePoint}; use crate::vector::misc::{HandleId, ManipulatorPointId}; @@ -490,18 +490,35 @@ pub fn migrate_vector<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Resu pub upstream_graphic_group: Option>, } + #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] + pub struct OldTable { + #[serde(alias = "instances", alias = "instance")] + element: Vec, + transform: Vec, + alpha_blending: Vec, + } + + #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] + pub struct OlderTable { + id: Vec, + #[serde(alias = "instances", alias = "instance")] + element: Vec, + } + #[derive(serde::Serialize, serde::Deserialize)] #[serde(untagged)] #[allow(clippy::large_enum_variant)] - enum EitherFormat { + enum VectorFormat { Vector(Vector), OldVectorData(OldVectorData), + OldVectorTable(OldTable), + OlderVectorTable(OlderTable), VectorTable(Table), } - Ok(match EitherFormat::deserialize(deserializer)? { - EitherFormat::Vector(vector) => Table::new_from_element(vector), - EitherFormat::OldVectorData(old) => { + Ok(match VectorFormat::deserialize(deserializer)? { + VectorFormat::Vector(vector) => Table::new_from_element(vector), + VectorFormat::OldVectorData(old) => { let mut vector_table = Table::new_from_element(Vector { style: old.style, colinear_manipulators: old.colinear_manipulators, @@ -514,7 +531,19 @@ pub fn migrate_vector<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Resu *vector_table.iter_mut().next().unwrap().alpha_blending = old.alpha_blending; vector_table } - EitherFormat::VectorTable(vector_table) => vector_table, + VectorFormat::OlderVectorTable(older_table) => older_table.element.into_iter().map(|element| TableRow { element, ..Default::default() }).collect(), + VectorFormat::OldVectorTable(old_table) => old_table + .element + .into_iter() + .zip(old_table.transform.into_iter().zip(old_table.alpha_blending)) + .map(|(element, (transform, alpha_blending))| TableRow { + element, + transform, + alpha_blending, + source_node_id: None, + }) + .collect(), + VectorFormat::VectorTable(vector_table) => vector_table, }) } diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 89f77dd821..4598737291 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -45,7 +45,7 @@ pub struct DocumentNode { #[cfg_attr(target_family = "wasm", serde(alias = "outputs"))] pub inputs: Vec, /// Type of the argument which this node can be evaluated with. - #[serde(alias = "manual_composition", default)] + #[serde(default, alias = "manual_composition", deserialize_with = "migrate_call_argument")] pub call_argument: Type, // A nested document network or a proto-node identifier. pub implementation: DocumentNodeImplementation, @@ -57,12 +57,12 @@ pub struct DocumentNode { /// However sometimes this is not desirable, for example in the case of a [`graphene_core::memo::MonitorNode`] that needs to be accessed outside of the graph. #[serde(default)] pub skip_deduplication: bool, - /// The path to this node and its inputs and outputs as of when [`NodeNetwork::generate_node_paths`] was called. - #[serde(skip)] - pub original_location: OriginalLocation, /// List of Extract and Inject annotations for the Context. #[serde(default)] pub context_features: ContextDependencies, + /// The path to this node and its inputs and outputs as of when [`NodeNetwork::generate_node_paths`] was called. + #[serde(skip)] + pub original_location: OriginalLocation, } /// Represents the original location of a node input/output when [`NodeNetwork::generate_node_paths`] was called, allowing the types and errors to be derived. @@ -1105,6 +1105,22 @@ impl<'a> Iterator for RecursiveNodeIter<'a> { } } +fn migrate_call_argument<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result { + use serde::Deserialize; + + #[derive(serde::Serialize, serde::Deserialize)] + #[serde(untagged)] + enum CallArg { + New(Type), + Old(Option), + } + + Ok(match CallArg::deserialize(deserializer)? { + CallArg::New(ty) => ty, + CallArg::Old(ty) => ty.unwrap_or_default(), + }) +} + #[cfg(test)] mod test { use super::*; diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 53a201ccc2..0db61bffc3 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -189,7 +189,7 @@ tagged_value! { #[serde(alias = "VectorData")] Vector(Table), #[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame"))] // TODO: Eventually remove this migration document upgrade code - #[serde(alias = "ImageFrame", alias = "RasterData")] + #[serde(alias = "ImageFrame", alias = "RasterData", alias = "Image")] Raster(Table>), #[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::graphic::migrate_graphic"))] // TODO: Eventually remove this migration document upgrade code #[serde(alias = "GraphicGroup", alias = "Group")] @@ -361,7 +361,7 @@ impl TaggedValue { x if x == TypeId::of::() => to_dvec2(string).map(TaggedValue::DVec2)?, x if x == TypeId::of::() => FromStr::from_str(string).map(TaggedValue::Bool).ok()?, x if x == TypeId::of::>() => to_color(string).map(|color| TaggedValue::Color(Table::new_from_element(color)))?, - x if x == TypeId::of::() => to_color(string).map(|color| TaggedValue::ColorNotInTable(color))?, + x if x == TypeId::of::() => to_color(string).map(TaggedValue::ColorNotInTable)?, x if x == TypeId::of::>() => TaggedValue::ColorNotInTable(to_color(string)?), x if x == TypeId::of::() => to_color(string).map(|color| TaggedValue::Fill(Fill::solid(color)))?, x if x == TypeId::of::() => to_reference_point(string).map(TaggedValue::ReferencePoint)?, diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 99486217f0..2bd69b8b39 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -22,6 +22,7 @@ use graphene_std::brush::brush_cache::BrushCache; use graphene_std::brush::brush_stroke::BrushStroke; use graphene_std::gradient::GradientStops; use graphene_std::table::Table; +use graphene_std::transform::Footprint; use graphene_std::uuid::NodeId; use graphene_std::vector::Vector; #[cfg(feature = "gpu")] @@ -153,13 +154,15 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => Arc]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WindowHandle]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option]), - async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WindowHandle]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => SurfaceFrame]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f64]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f32]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => u32]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => u64]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => DVec2]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => String]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => DAffine2]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Footprint]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]), #[cfg(feature = "gpu")] async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => &WasmEditorApi]), @@ -167,6 +170,51 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => WgpuSurface]), #[cfg(feature = "gpu")] async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table>]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Color]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => [f64; 4]]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Graphic]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => glam::f32::Vec2]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => glam::f32::Affine2]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::style::Stroke]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::style::Gradient]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::text::Font]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => BrushCache]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => DocumentNode]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::ContextFeatures]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::curve::Curve]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::transform::Footprint]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Box]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::style::Fill]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::blending::BlendMode]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::LuminanceCalculation]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::extract_xy::XY]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RedGreenBlue]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RedGreenBlueAlpha]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::animation::RealTimeMode]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::NoiseType]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::FractalType]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::CellularDistanceFunction]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::CellularReturnType]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::DomainWarpType]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RelativeAbsolute]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::SelectiveColorChoice]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::misc::GridType]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::misc::ArcType]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::misc::MergeByDistanceAlgorithm]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::misc::PointSpacingType]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::style::StrokeCap]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::style::StrokeJoin]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::style::StrokeAlign]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::style::PaintOrder]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::style::FillType]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::style::GradientType]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::transform::ReferencePoint]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::vector::misc::CentroidType]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_path_bool::BooleanOperation]), + async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_core::text::TextAlign]), // ================= // IMPURE MEMO NODES // =================