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.);