diff --git a/node-graph/libraries/graphic-types/src/graphic.rs b/node-graph/libraries/graphic-types/src/graphic.rs index 444352046e..001c0c33a2 100644 --- a/node-graph/libraries/graphic-types/src/graphic.rs +++ b/node-graph/libraries/graphic-types/src/graphic.rs @@ -104,201 +104,94 @@ impl From> for Graphic { } } -// Local trait to convert types to Table (avoids orphan rule issues) -pub trait IntoGraphicTable { - fn into_graphic_table(self) -> Table; +/// Deeply flattens a graphic table, collecting only elements matching a specific variant (extracted by `extract_variant`) +/// and discarding all other non-matching content. Recursion through `Graphic::Graphic` sub-tables composes transforms and opacity. +fn flatten_graphic_table(content: Table, extract_variant: fn(Graphic) -> Option>) -> Table { + fn compose_alpha_blending(parent: AlphaBlending, child: AlphaBlending) -> AlphaBlending { + AlphaBlending { + blend_mode: child.blend_mode, + opacity: parent.opacity * child.opacity, + fill: child.fill, + clip: child.clip, + } + } - /// Deeply flattens any vector content within a graphic table, discarding non-vector content, and returning a table of only vector elements. - fn into_flattened_vector_table(self) -> Table - where - Self: std::marker::Sized, - { - let content = self.into_graphic_table(); - - // TODO: Avoid mutable reference, instead return a new Table? - fn flatten_table(output_vector_table: &mut Table, current_graphic_table: Table) { - for current_graphic_row in current_graphic_table.iter() { - let current_graphic = current_graphic_row.element.clone(); - let source_node_id = *current_graphic_row.source_node_id; - - match current_graphic { - // If we're allowed to recurse, flatten any tables we encounter - Graphic::Graphic(mut current_graphic_table) => { - // Apply the parent graphic's transform to all child elements - for graphic in current_graphic_table.iter_mut() { - *graphic.transform = *current_graphic_row.transform * *graphic.transform; - } + fn flatten_recursive(output: &mut Table, current_graphic_table: Table, extract_variant: fn(Graphic) -> Option>) { + for current_graphic_row in current_graphic_table.into_iter() { + let source_node_id = current_graphic_row.source_node_id; - flatten_table(output_vector_table, current_graphic_table); + match current_graphic_row.element { + // Recurse into nested graphic tables, composing the parent's transform onto each child + Graphic::Graphic(mut sub_table) => { + for graphic in sub_table.iter_mut() { + *graphic.transform = current_graphic_row.transform * *graphic.transform; + *graphic.alpha_blending = compose_alpha_blending(current_graphic_row.alpha_blending, *graphic.alpha_blending); } - // Push any leaf Vector elements we encounter - Graphic::Vector(vector_table) => { - for current_vector_row in vector_table.iter() { - output_vector_table.push(TableRow { - element: current_vector_row.element.clone(), - transform: *current_graphic_row.transform * *current_vector_row.transform, - alpha_blending: AlphaBlending { - blend_mode: current_vector_row.alpha_blending.blend_mode, - opacity: current_graphic_row.alpha_blending.opacity * current_vector_row.alpha_blending.opacity, - fill: current_vector_row.alpha_blending.fill, - clip: current_vector_row.alpha_blending.clip, - }, + + flatten_recursive(output, sub_table, extract_variant); + } + // Try to extract the target variant; if it matches, push its rows with composed transform and opacity + other => { + if let Some(typed_table) = extract_variant(other) { + for row in typed_table.into_iter() { + output.push(TableRow { + element: row.element, + transform: current_graphic_row.transform * row.transform, + alpha_blending: compose_alpha_blending(current_graphic_row.alpha_blending, row.alpha_blending), source_node_id, }); } } - _ => {} } } } - - let mut output = Table::new(); - flatten_table(&mut output, content); - output } - /// Deeply flattens any raster content within a graphic table, discarding non-raster content, and returning a table of only raster elements. - fn into_flattened_raster_table(self) -> Table> - where - Self: std::marker::Sized, - { - let content = self.into_graphic_table(); - - fn flatten_table(output_raster_table: &mut Table>, current_graphic_table: Table) { - for current_graphic_row in current_graphic_table.iter() { - let current_graphic = current_graphic_row.element.clone(); - let source_node_id = *current_graphic_row.source_node_id; - - match current_graphic { - // If we're allowed to recurse, flatten any tables we encounter - Graphic::Graphic(mut current_graphic_table) => { - // Apply the parent graphic's transform to all child elements - for graphic in current_graphic_table.iter_mut() { - *graphic.transform = *current_graphic_row.transform * *graphic.transform; - } + let mut output = Table::new(); + flatten_recursive(&mut output, content, extract_variant); + output +} - flatten_table(output_raster_table, current_graphic_table); - } - // Push any leaf RasterCPU elements we encounter - Graphic::RasterCPU(raster_table) => { - for current_raster_row in raster_table.iter() { - output_raster_table.push(TableRow { - element: current_raster_row.element.clone(), - transform: *current_graphic_row.transform * *current_raster_row.transform, - alpha_blending: AlphaBlending { - blend_mode: current_raster_row.alpha_blending.blend_mode, - opacity: current_graphic_row.alpha_blending.opacity * current_raster_row.alpha_blending.opacity, - fill: current_raster_row.alpha_blending.fill, - clip: current_raster_row.alpha_blending.clip, - }, - source_node_id, - }); - } - } - _ => {} - } - } - } +/// Maps from a concrete element type to its corresponding `Graphic` enum variant, +/// enabling type-directed casting of typed tables from a `Graphic` value. +pub trait TryFromGraphic: Clone + Sized { + fn try_from_graphic(graphic: Graphic) -> Option>; +} - let mut output = Table::new(); - flatten_table(&mut output, content); - output +impl TryFromGraphic for Vector { + fn try_from_graphic(graphic: Graphic) -> Option> { + if let Graphic::Vector(t) = graphic { Some(t) } else { None } } +} - /// Deeply flattens any color content within a graphic table, discarding non-color content, and returning a table of only color elements. - fn into_flattened_color_table(self) -> Table - where - Self: std::marker::Sized, - { - let content = self.into_graphic_table(); - - fn flatten_table(output_color_table: &mut Table, current_graphic_table: Table) { - for current_graphic_row in current_graphic_table.iter() { - let current_graphic = current_graphic_row.element.clone(); - let source_node_id = *current_graphic_row.source_node_id; - - match current_graphic { - // If we're allowed to recurse, flatten any tables we encounter - Graphic::Graphic(mut current_graphic_table) => { - // Apply the parent graphic's transform to all child elements - for graphic in current_graphic_table.iter_mut() { - *graphic.transform = *current_graphic_row.transform * *graphic.transform; - } +impl TryFromGraphic for Raster { + fn try_from_graphic(graphic: Graphic) -> Option> { + if let Graphic::RasterCPU(t) = graphic { Some(t) } else { None } + } +} - flatten_table(output_color_table, current_graphic_table); - } - // Push any leaf Color elements we encounter - Graphic::Color(color_table) => { - for current_color_row in color_table.iter() { - output_color_table.push(TableRow { - element: *current_color_row.element, - transform: *current_graphic_row.transform * *current_color_row.transform, - alpha_blending: AlphaBlending { - blend_mode: current_color_row.alpha_blending.blend_mode, - opacity: current_graphic_row.alpha_blending.opacity * current_color_row.alpha_blending.opacity, - fill: current_color_row.alpha_blending.fill, - clip: current_color_row.alpha_blending.clip, - }, - source_node_id, - }); - } - } - _ => {} - } - } - } +impl TryFromGraphic for Color { + fn try_from_graphic(graphic: Graphic) -> Option> { + if let Graphic::Color(t) = graphic { Some(t) } else { None } + } +} - let mut output = Table::new(); - flatten_table(&mut output, content); - output +impl TryFromGraphic for GradientStops { + fn try_from_graphic(graphic: Graphic) -> Option> { + if let Graphic::Gradient(t) = graphic { Some(t) } else { None } } +} - /// Deeply flattens any gradient content within a graphic table, discarding non-gradient content, and returning a table of only gradient elements. - fn into_flattened_gradient_table(self) -> Table +// Local trait to convert types to Table (avoids orphan rule issues) +pub trait IntoGraphicTable { + fn into_graphic_table(self) -> Table; + + /// Deeply flattens any content of type `T` within a graphic table, discarding all other content, and returning a flat table of only `T` elements. + fn into_flattened_table(self) -> Table where Self: std::marker::Sized, { - let content = self.into_graphic_table(); - - fn flatten_table(output_gradient_table: &mut Table, current_graphic_table: Table) { - for current_graphic_row in current_graphic_table.iter() { - let current_graphic = current_graphic_row.element.clone(); - let source_node_id = *current_graphic_row.source_node_id; - - match current_graphic { - // If we're allowed to recurse, flatten any tables we encounter - Graphic::Graphic(mut current_graphic_table) => { - // Apply the parent graphic's transform to all child elements - for graphic in current_graphic_table.iter_mut() { - *graphic.transform = *current_graphic_row.transform * *graphic.transform; - } - - flatten_table(output_gradient_table, current_graphic_table); - } - // Push any leaf GradientStops elements we encounter - Graphic::Gradient(gradient_table) => { - for current_gradient_row in gradient_table.iter() { - output_gradient_table.push(TableRow { - element: current_gradient_row.element.clone(), - transform: *current_graphic_row.transform * *current_gradient_row.transform, - alpha_blending: AlphaBlending { - blend_mode: current_gradient_row.alpha_blending.blend_mode, - opacity: current_graphic_row.alpha_blending.opacity * current_gradient_row.alpha_blending.opacity, - fill: current_gradient_row.alpha_blending.fill, - clip: current_gradient_row.alpha_blending.clip, - }, - source_node_id, - }); - } - } - _ => {} - } - } - } - - let mut output = Table::new(); - flatten_table(&mut output, content); - output + flatten_graphic_table(self.into_graphic_table(), T::try_from_graphic) } } diff --git a/node-graph/libraries/graphic-types/src/lib.rs b/node-graph/libraries/graphic-types/src/lib.rs index 49b23f92f7..54e4302bca 100644 --- a/node-graph/libraries/graphic-types/src/lib.rs +++ b/node-graph/libraries/graphic-types/src/lib.rs @@ -8,7 +8,7 @@ pub use vector_types; // Re-export commonly used types at the crate root pub use artboard::Artboard; -pub use graphic::{Graphic, IntoGraphicTable, Vector}; +pub use graphic::{Graphic, IntoGraphicTable, TryFromGraphic, Vector}; pub mod migrations { use core_types::{ diff --git a/node-graph/nodes/graphic/src/graphic.rs b/node-graph/nodes/graphic/src/graphic.rs index ffd98cec1e..72f1e23208 100644 --- a/node-graph/nodes/graphic/src/graphic.rs +++ b/node-graph/nodes/graphic/src/graphic.rs @@ -295,31 +295,31 @@ pub async fn flatten_graphic(_: impl Ctx, content: Table, fully_flatten /// Converts a graphic table into a vector table by deeply flattening any vector content it contains, and discarding any non-vector content. #[node_macro::node(category("Vector"))] pub async fn flatten_vector(_: impl Ctx, #[implementations(Table, Table)] content: T) -> Table { - content.into_flattened_vector_table() + content.into_flattened_table() } /// Converts a graphic table into a raster table by deeply flattening any raster content it contains, and discarding any non-raster content. #[node_macro::node(category("Raster"))] pub async fn flatten_raster(_: impl Ctx, #[implementations(Table, Table>)] content: T) -> Table> { - content.into_flattened_raster_table() + content.into_flattened_table() } /// Converts a graphic table into a color table by deeply flattening any color content it contains, and discarding any non-color content. #[node_macro::node(category("General"))] pub async fn flatten_color(_: impl Ctx, #[implementations(Table, Table)] content: T) -> Table { - content.into_flattened_color_table() + content.into_flattened_table() } /// Converts a graphic table into a gradient table by deeply flattening any gradient content it contains, and discarding any non-gradient content. #[node_macro::node(category("General"))] pub async fn flatten_gradient(_: impl Ctx, #[implementations(Table, Table)] content: T) -> Table { - content.into_flattened_gradient_table() + content.into_flattened_table() } /// Constructs a gradient from a table of colors, where the colors are evenly distributed as gradient stops across the range from 0 to 1. #[node_macro::node(category("Color"))] fn colors_to_gradient(_: impl Ctx, #[implementations(Table, Table)] colors: T) -> GradientStops { - let colors = colors.into_flattened_color_table(); + let colors = colors.into_flattened_table::(); let total_colors = colors.len(); if total_colors == 0 { diff --git a/node-graph/nodes/vector/src/vector_nodes.rs b/node-graph/nodes/vector/src/vector_nodes.rs index 68d26202f1..87bdaf1d40 100644 --- a/node-graph/nodes/vector/src/vector_nodes.rs +++ b/node-graph/nodes/vector/src/vector_nodes.rs @@ -1212,66 +1212,28 @@ async fn map_points(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: Table Combine Paths pair of nodes. #[node_macro::node(category("Vector"), path(graphene_core::vector))] -pub async fn flatten_path( - _: impl Ctx, - #[implementations( - Table, - Table, - )] - content: Table, -) -> Table -where - Graphic: From>, -{ - // NOTE(AdamGerhant): - // A node-based solution to support passing through vector data could be a network node with a cache node - // connected to a Flatten Path connected to an if else node, another connection from the cache directly to - // the if else node, and another connection from the cache to a matches type node connected to the if else node. - - fn flatten_table(output: &mut TableRowMut, graphic_table: &Table) { - for (current_index, current_element) in graphic_table.iter().enumerate() { - match current_element.element { - Graphic::Vector(vector) => { - // Loop through every row of the `Table` and concatenate each element's subpath into the output `Vector` element. - for (vector_index, row) in vector.iter().enumerate() { - let other = row.element; - let transform = *current_element.transform * *row.transform; - let node_id = current_element.source_node_id.map(|node_id| node_id.0).unwrap_or_default(); - - let mut hasher = DefaultHasher::new(); - (current_index, vector_index, node_id).hash(&mut hasher); - let collision_hash_seed = hasher.finish(); - - output.element.concat(other, transform, collision_hash_seed); - - // TODO: Make this instead use the first encountered style - // Use the last encountered style as the output style - output.element.style = row.element.style.clone(); - } - } - Graphic::Graphic(graphic) => { - let mut graphic = graphic.clone(); - for row in graphic.iter_mut() { - *row.transform = *current_element.transform * *row.transform; - } - - flatten_table(output, &graphic); - } - _ => {} - } - } - } - +pub async fn flatten_path(_: impl Ctx, #[implementations(Table, Table)] content: T) -> Table { // Create a table with one empty `Vector` element, then get a mutable reference to it which we append flattened subpaths to let mut output_table = Table::new_from_element(Vector::default()); - let Some(mut output) = output_table.iter_mut().next() else { return output_table }; + let Some(output) = output_table.iter_mut().next() else { return output_table }; + + // Concatenate every vector element's subpaths into the single output compound path + for (index, row) in content.into_flattened_table().iter().enumerate() { + let node_id = row.source_node_id.map(|node_id| node_id.0).unwrap_or_default(); + + let mut hasher = DefaultHasher::new(); + (index, node_id).hash(&mut hasher); + let collision_hash_seed = hasher.finish(); - // Flatten the graphic input into the output `Vector` element - let base_graphic_table = Table::new_from_element(Graphic::from(content)); - flatten_table(&mut output, &base_graphic_table); + output.element.concat(row.element, *row.transform, collision_hash_seed); + + // TODO: Make this instead use the first encountered style + // Use the last encountered style as the output style + output.element.style = row.element.style.clone(); + } - // Return the single-row Table containing the flattened Vector subpaths output_table } @@ -1782,7 +1744,7 @@ async fn morph( let graphic_table_content = content.clone().into_graphic_table(); // If the input isn't a Table, we convert it into one by flattening any Table content. - let content = content.into_flattened_vector_table(); + let content = content.into_flattened_table::(); // Determine source and target indices and interpolation time fraction let progression = progression.max(0.);