diff --git a/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs b/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs index c97a12ff..1b8f7621 100644 --- a/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs +++ b/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs @@ -993,7 +993,7 @@ fn table_node_id_path_layout_with_breadcrumb(path: &Table, data: &mut La /// Mirrors [`dispatch_value_widget`] but routes to [`TableRowLayout::layout_with_breadcrumb`]. /// Returns `None` for unrecognized types. fn drilldown_attribute_layout(any: &dyn Any, data: &mut LayoutData) -> Option> { - // `Table` is interpreted as a path (e.g. the `editor:layer` attribute), so each item's NodeId value + // `Table` is interpreted as a path (e.g. the `editor:layer_path` attribute), so each item's NodeId value // resolves against the prefix made up of preceding items. Handled before the generic `Table` blanket impl. if let Some(path) = any.downcast_ref::>() { return Some(table_node_id_path_layout_with_breadcrumb(path, data)); diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index 9e8e5e7b..61150918 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -239,12 +239,12 @@ fn document_node_definitions() -> HashMap HashMap` describing the +/// path (from the root document network) to the layer node that owns the row. Editor tools use this to +/// route clicks/selection back to the originating layer at any nesting depth. +pub const ATTR_EDITOR_LAYER_PATH: &str = "editor:layer_path"; + +/// Attribute key under which a row stores a `Table` snapshot of the upstream content that fed +/// into a destructive merge (Boolean Operation, Flatten Path, Morph, Rasterize, etc.). The renderer +/// recurses into this snapshot during metadata collection so the editor can still surface click targets +/// for the original child layers after their content has been collapsed into a single output. +pub const ATTR_EDITOR_MERGED_LAYERS: &str = "editor:merged_layers"; + +/// Attribute key for the byte offset where a regex match begins in the input string, set by the +/// `regex_find_all` and `regex_capture` text nodes. +pub const ATTR_START: &str = "start"; + +/// Attribute key for the byte offset where a regex match ends in the input string, set by the +/// `regex_find_all` and `regex_capture` text nodes. +pub const ATTR_END: &str = "end"; + +/// Attribute key for a regex named-capture-group's name (empty for unnamed groups), set by the +/// `regex_capture` text node. +pub const ATTR_NAME: &str = "name"; + +/// Attribute key for a JSON value's type (`"string"`, `"number"`, `"object"`, etc.), set by the +/// `json_query_all` text node alongside each extracted value. +pub const ATTR_TYPE: &str = "type"; + // ===================== // TRAIT: AttributeValue // ===================== @@ -747,7 +786,7 @@ impl BoundingBox for Table { fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> RenderBoundingBox { let mut combined_bounds = None; - for (element, row_transform) in self.iter_element_values().zip(self.iter_attribute_values_or_default::("transform")) { + for (element, row_transform) in self.iter_element_values().zip(self.iter_attribute_values_or_default::(ATTR_TRANSFORM)) { match element.bounding_box(transform * row_transform, include_stroke) { RenderBoundingBox::None => continue, RenderBoundingBox::Infinite => return RenderBoundingBox::Infinite, @@ -793,10 +832,10 @@ impl graphene_hash::CacheHash for Table { for element in self.iter_element_values() { element.cache_hash(state); } - for transform in self.iter_attribute_values_or_default::("transform") { + for transform in self.iter_attribute_values_or_default::(ATTR_TRANSFORM) { graphene_hash::CacheHash::cache_hash(&transform, state); } - for alpha_blending in self.iter_attribute_values_or_default::("alpha_blending") { + for alpha_blending in self.iter_attribute_values_or_default::(ATTR_ALPHA_BLENDING) { alpha_blending.cache_hash(state); } } @@ -811,14 +850,14 @@ impl PartialEq for Table { impl ApplyTransform for Table { /// Right-multiplies the modification into each row's transform attribute. fn apply_transform(&mut self, modification: &DAffine2) { - for transform in self.iter_attribute_values_mut_or_default::("transform") { + for transform in self.iter_attribute_values_mut_or_default::(ATTR_TRANSFORM) { *transform *= *modification; } } /// Left-multiplies the modification into each row's transform attribute. fn left_apply_transform(&mut self, modification: &DAffine2) { - for transform in self.iter_attribute_values_mut_or_default::("transform") { + for transform in self.iter_attribute_values_mut_or_default::(ATTR_TRANSFORM) { *transform = *modification * *transform; } } diff --git a/node-graph/libraries/graphic-types/src/artboard.rs b/node-graph/libraries/graphic-types/src/artboard.rs index 244c363e..ab2ad81c 100644 --- a/node-graph/libraries/graphic-types/src/artboard.rs +++ b/node-graph/libraries/graphic-types/src/artboard.rs @@ -1,5 +1,4 @@ use crate::graphic::Graphic; -use core_types::Color; use core_types::blending::AlphaBlending; use core_types::bounds::{BoundingBox, RenderBoundingBox}; use core_types::math::quad::Quad; @@ -7,6 +6,7 @@ use core_types::render_complexity::RenderComplexity; use core_types::table::{Table, TableRow}; use core_types::transform::Transform; use core_types::uuid::NodeId; +use core_types::{ATTR_TRANSFORM, Color}; use dyn_any::DynAny; use glam::{DAffine2, DVec2, IVec2}; use graphene_hash::CacheHash; @@ -52,7 +52,7 @@ impl BoundingBox for Artboard { let mut combined_bounds = None; - for (element, row_transform) in self.content.iter_element_values().zip(self.content.iter_attribute_values_or_default::("transform")) { + for (element, row_transform) in self.content.iter_element_values().zip(self.content.iter_attribute_values_or_default::(ATTR_TRANSFORM)) { match element.bounding_box(transform * row_transform, include_stroke) { RenderBoundingBox::None => continue, RenderBoundingBox::Infinite => return RenderBoundingBox::Infinite, @@ -113,7 +113,7 @@ pub fn migrate_artboard<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Re alpha_blending: Vec, } - // Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs + // Attributes (transform, alpha_blending, editor:layer_path) are not serialized, so migration only needs // to recover the elements. Per-item attribute values are populated at runtime by the node graph. Ok(match ArtboardFormat::deserialize(deserializer)? { ArtboardFormat::ArtboardGroup(artboard_group) => artboard_group.artboards.into_iter().map(|(artboard, _)| TableRow::new_from_element(artboard)).collect(), diff --git a/node-graph/libraries/graphic-types/src/graphic.rs b/node-graph/libraries/graphic-types/src/graphic.rs index 518987a6..0cfcd201 100644 --- a/node-graph/libraries/graphic-types/src/graphic.rs +++ b/node-graph/libraries/graphic-types/src/graphic.rs @@ -1,4 +1,3 @@ -use core_types::Color; use core_types::blending::AlphaBlending; use core_types::bounds::{BoundingBox, RenderBoundingBox}; use core_types::graphene_hash::CacheHash; @@ -6,6 +5,7 @@ use core_types::ops::TableConvert; use core_types::render_complexity::RenderComplexity; use core_types::table::{Table, TableRow}; use core_types::uuid::NodeId; +use core_types::{ATTR_ALPHA_BLENDING, ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, Color}; use dyn_any::DynAny; use glam::DAffine2; use raster_types::{CPU, GPU, Raster}; @@ -141,19 +141,19 @@ fn flatten_graphic_table(content: Table, extract_variant: fn(Graphic 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 layer_path: Table = current_graphic_row.attribute_cloned_or_default("editor:layer"); - let current_transform: DAffine2 = current_graphic_row.attribute_cloned_or_default("transform"); - let current_alpha_blending: AlphaBlending = current_graphic_row.attribute_cloned_or_default("alpha_blending"); + let layer_path: Table = current_graphic_row.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH); + let current_transform: DAffine2 = current_graphic_row.attribute_cloned_or_default(ATTR_TRANSFORM); + let current_alpha_blending: AlphaBlending = current_graphic_row.attribute_cloned_or_default(ATTR_ALPHA_BLENDING); match current_graphic_row.into_element() { // Recurse into nested `Table` items, composing the parent's transform onto each child Graphic::Graphic(mut sub_table) => { for index in 0..sub_table.len() { - let child_transform: DAffine2 = sub_table.attribute_cloned_or_default("transform", index); - let child_alpha_blending: AlphaBlending = sub_table.attribute_cloned_or_default("alpha_blending", index); + let child_transform: DAffine2 = sub_table.attribute_cloned_or_default(ATTR_TRANSFORM, index); + let child_alpha_blending: AlphaBlending = sub_table.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index); - sub_table.set_attribute("transform", index, current_transform * child_transform); - sub_table.set_attribute("alpha_blending", index, compose_alpha_blending(current_alpha_blending, child_alpha_blending)); + sub_table.set_attribute(ATTR_TRANSFORM, index, current_transform * child_transform); + sub_table.set_attribute(ATTR_ALPHA_BLENDING, index, compose_alpha_blending(current_alpha_blending, child_alpha_blending)); } flatten_recursive(output, sub_table, extract_variant); @@ -162,13 +162,13 @@ fn flatten_graphic_table(content: Table, extract_variant: fn(Graphic other => { if let Some(typed_table) = extract_variant(other) { for row in typed_table.into_iter() { - let row_transform: DAffine2 = row.attribute_cloned_or_default("transform"); - let row_alpha_blending: AlphaBlending = row.attribute_cloned_or_default("alpha_blending"); + let row_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); + let row_alpha_blending: AlphaBlending = row.attribute_cloned_or_default(ATTR_ALPHA_BLENDING); let (element, mut attributes) = row.into_parts(); - attributes.insert("transform", current_transform * row_transform); - attributes.insert("alpha_blending", compose_alpha_blending(current_alpha_blending, row_alpha_blending)); - attributes.insert("editor:layer", layer_path.clone()); + attributes.insert(ATTR_TRANSFORM, current_transform * row_transform); + attributes.insert(ATTR_ALPHA_BLENDING, compose_alpha_blending(current_alpha_blending, row_alpha_blending)); + attributes.insert(ATTR_EDITOR_LAYER_PATH, layer_path.clone()); output.push(TableRow::from_parts(element, attributes)); } @@ -321,7 +321,7 @@ impl Graphic { pub fn had_clip_enabled(&self) -> bool { fn all_clipped(table: &Table) -> bool { - table.iter_attribute_values_or_default::("alpha_blending").all(|a| a.clip) + table.iter_attribute_values_or_default::(ATTR_ALPHA_BLENDING).all(|a| a.clip) } match self { Graphic::Vector(table) => all_clipped(table), @@ -337,7 +337,7 @@ impl Graphic { match self { Graphic::Vector(vector) => vector .iter_element_values() - .zip(vector.iter_attribute_values_or_default::("alpha_blending")) + .zip(vector.iter_attribute_values_or_default::(ATTR_ALPHA_BLENDING)) .all(|(element, alpha_blending)| { (alpha_blending.opacity > 1. - f32::EPSILON) && element.style.fill().is_opaque() && element.style.stroke().is_none_or(|stroke| !stroke.has_renderable_stroke()) }), @@ -504,7 +504,7 @@ pub fn migrate_graphic<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Res Table(serde_json::Value), } - // Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs + // Attributes (transform, alpha_blending, editor:layer_path) are not serialized, so migration only needs // to recover the elements. Per-item attribute values are populated at runtime by the node graph. Ok(match GraphicFormat::deserialize(deserializer)? { GraphicFormat::OldGraphicGroup(old) => old.elements.into_iter().map(|(graphic, _)| TableRow::new_from_element(graphic)).collect(), diff --git a/node-graph/libraries/graphic-types/src/lib.rs b/node-graph/libraries/graphic-types/src/lib.rs index c74a8525..9335d41d 100644 --- a/node-graph/libraries/graphic-types/src/lib.rs +++ b/node-graph/libraries/graphic-types/src/lib.rs @@ -72,7 +72,7 @@ pub mod migrations { Ok(match VectorFormat::deserialize(deserializer)? { VectorFormat::Vector(vector) => Table::new_from_element(vector), - // Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs + // Attributes (transform, alpha_blending, editor:layer_path) are not serialized, so migration only needs // to recover the elements. Per-item attribute values are populated at runtime by the node graph. VectorFormat::OldVectorData(old) => Table::new_from_element(Vector { style: old.style, diff --git a/node-graph/libraries/raster-types/src/image.rs b/node-graph/libraries/raster-types/src/image.rs index 58a92981..bb4a70dd 100644 --- a/node-graph/libraries/raster-types/src/image.rs +++ b/node-graph/libraries/raster-types/src/image.rs @@ -1,9 +1,8 @@ use crate::raster_types::{CPU, Raster}; use crate::{Bitmap, BitmapMut}; -use core_types::AlphaBlending; -use core_types::Color; use core_types::color::float_to_srgb_u8; use core_types::table::{Table, TableRow}; +use core_types::{ATTR_ALPHA_BLENDING, ATTR_TRANSFORM, AlphaBlending, Color}; // use crate::vector::Vector; // TODO: Check if Vector is actually used, if so handle differently use core_types::color::*; use dyn_any::{DynAny, StaticType}; @@ -319,7 +318,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Table::new_from_element(Raster::new_cpu(table.element(0).unwrap().clone())) } - // Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs + // Attributes (transform, alpha_blending, editor:layer_path) are not serialized, so migration only needs // to recover the elements. Per-item attribute values are populated at runtime by the node graph. fn old_table_to_new_table(old_table: OldTable) -> Table { old_table.element.into_iter().map(TableRow::new_from_element).collect() @@ -339,8 +338,8 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) -> FormatVersions::Image(image) => Table::new_from_element(Raster::new_cpu(image)), FormatVersions::OldImageFrame(OldImageFrame { image, transform, alpha_blending }) => { let mut image_frame_table = Table::new_from_element(Raster::new_cpu(image)); - image_frame_table.set_attribute("transform", 0, transform); - image_frame_table.set_attribute("alpha_blending", 0, alpha_blending); + image_frame_table.set_attribute(ATTR_TRANSFORM, 0, transform); + image_frame_table.set_attribute(ATTR_ALPHA_BLENDING, 0, alpha_blending); image_frame_table } FormatVersions::OlderImageFrameTable(old_table) => from_image_frame_table(older_table_to_new_table(old_table)), @@ -430,7 +429,7 @@ pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D RasterTableRow(TableRow>), } - // Attributes (transform, alpha_blending, editor:layer) are not serialized, so migration only needs + // Attributes (transform, alpha_blending, editor:layer_path) are not serialized, so migration only needs // to recover the element. Per-item attribute values are populated at runtime by the node graph. Ok(match FormatVersions::deserialize(deserializer)? { FormatVersions::Image(image) => TableRow::new_from_element(Raster::new_cpu(image)), diff --git a/node-graph/libraries/rendering/src/renderer.rs b/node-graph/libraries/rendering/src/renderer.rs index 995b6334..076f2e7e 100644 --- a/node-graph/libraries/rendering/src/renderer.rs +++ b/node-graph/libraries/rendering/src/renderer.rs @@ -10,6 +10,7 @@ use core_types::render_complexity::RenderComplexity; use core_types::table::{Table, TableRow}; use core_types::transform::{Footprint, Transform}; use core_types::uuid::{NodeId, generate_uuid}; +use core_types::{ATTR_ALPHA_BLENDING, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_TRANSFORM}; use dyn_any::DynAny; use glam::{DAffine2, DVec2}; use graphene_hash::CacheHashWrapper; @@ -412,9 +413,9 @@ impl Render for Graphic { metadata.upstream_footprints.insert(element_id, footprint); // TODO: Find a way to handle more than the first item if !table.is_empty() { - let layer_path: Table = table.attribute_cloned_or_default("editor:layer", 0); + let layer_path: Table = table.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, 0); let layer = layer_path.iter_element_values().next_back().copied(); - let transform: DAffine2 = table.attribute_cloned_or_default("transform", 0); + let transform: DAffine2 = table.attribute_cloned_or_default(ATTR_TRANSFORM, 0); metadata.first_element_source_id.insert(element_id, layer); metadata.local_transforms.insert(element_id, transform); @@ -425,7 +426,7 @@ impl Render for Graphic { // TODO: Find a way to handle more than the first item if !table.is_empty() { - metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0)); + metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default(ATTR_TRANSFORM, 0)); } } Graphic::RasterGPU(table) => { @@ -433,7 +434,7 @@ impl Render for Graphic { // TODO: Find a way to handle more than the first item if !table.is_empty() { - metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0)); + metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default(ATTR_TRANSFORM, 0)); } } Graphic::Color(table) => { @@ -441,7 +442,7 @@ impl Render for Graphic { // TODO: Find a way to handle more than the first item if !table.is_empty() { - metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0)); + metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default(ATTR_TRANSFORM, 0)); } } Graphic::Gradient(table) => { @@ -449,7 +450,7 @@ impl Render for Graphic { // TODO: Find a way to handle more than the first item if !table.is_empty() { - metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default("transform", 0)); + metadata.local_transforms.insert(element_id, table.attribute_cloned_or_default(ATTR_TRANSFORM, 0)); } } } @@ -551,7 +552,7 @@ impl Render for Artboard { |attributes| { let matrix = format_transform_matrix(self.transform()); if !matrix.is_empty() { - attributes.push("transform", matrix); + attributes.push(ATTR_TRANSFORM, matrix); } if self.clip { @@ -656,7 +657,7 @@ impl Render for Table { fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option) { for index in 0..self.len() { - let layer_path: Table = self.attribute_cloned_or_default("editor:layer", index); + let layer_path: Table = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index); let layer = layer_path.iter_element_values().next_back().copied(); self.element(index).unwrap().collect_metadata(metadata, footprint, layer); } @@ -678,8 +679,8 @@ impl Render for Table { let mut mask_state = None; for index in 0..self.len() { - let transform: DAffine2 = self.attribute_cloned_or_default("transform", index); - let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index); + let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index); + let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index); let element = self.element(index).unwrap(); render.parent_tag( @@ -687,7 +688,7 @@ impl Render for Table { |attributes| { let matrix = format_transform_matrix(transform); if !matrix.is_empty() { - attributes.push("transform", matrix); + attributes.push(ATTR_TRANSFORM, matrix); } let opacity = alpha_blending.opacity(render_params.for_mask); @@ -732,9 +733,9 @@ impl Render for Table { let mut mask_element_and_transform = None; for index in 0..self.len() { - let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index); + let row_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index); let transform = transform * row_transform; - let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index); + let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index); let element = self.element(index).unwrap(); let mut layer = false; @@ -806,8 +807,8 @@ impl Render for Table { fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option) { for index in 0..self.len() { - let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index); - let layer_path: Table = self.attribute_cloned_or_default("editor:layer", index); + let row_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index); + let layer_path: Table = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index); let layer = layer_path.iter_element_values().next_back().copied(); let element = self.element(index).unwrap(); @@ -817,7 +818,7 @@ impl Render for Table { if let Some(element_id) = layer { element.collect_metadata(metadata, footprint, Some(element_id)); } else { - // Recurse through anonymous wrapper items to reach nested content with editor:layer tags + // Recurse through anonymous wrapper items to reach nested content with editor:layer_path tags element.collect_metadata(metadata, footprint, None); } } @@ -826,7 +827,7 @@ impl Render for Table { let mut all_upstream_click_targets = Vec::new(); for index in 0..self.len() { - let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index); + let row_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index); let element = self.element(index).unwrap(); let mut new_click_targets = Vec::new(); element.add_upstream_click_targets(&mut new_click_targets); @@ -844,7 +845,7 @@ impl Render for Table { fn add_upstream_click_targets(&self, click_targets: &mut Vec) { for index in 0..self.len() { - let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index); + let row_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index); let element = self.element(index).unwrap(); let mut new_click_targets = Vec::new(); @@ -863,7 +864,7 @@ impl Render for Table { } fn new_ids_from_hash(&mut self, _reference: Option) { - let (elements, layers) = self.element_and_attribute_slices_mut::>("editor:layer"); + let (elements, layers) = self.element_and_attribute_slices_mut::>(ATTR_EDITOR_LAYER_PATH); for (element, layer) in elements.iter_mut().zip(layers.iter()) { element.new_ids_from_hash(layer.iter_element_values().next_back().copied()); } @@ -874,8 +875,8 @@ impl Render for Table { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { for index in 0..self.len() { let Some(vector) = self.element(index) else { continue }; - let multiplied_transform: DAffine2 = self.attribute_cloned_or_default("transform", index); - let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index); + let multiplied_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index); + let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index); // Only consider strokes with non-zero weight, since default strokes with zero weight would prevent assigning the correct stroke transform let has_real_stroke = vector.style.stroke().filter(|stroke| stroke.weight() > 0.); @@ -915,7 +916,7 @@ impl Render for Table { attributes.push("d", path.clone()); let matrix = format_transform_matrix(element_transform); if !matrix.is_empty() { - attributes.push("transform", matrix); + attributes.push(ATTR_TRANSFORM, matrix); } let mut style = vector.style.clone(); style.clear_stroke(); @@ -940,8 +941,8 @@ impl Render for Table { let vector_row = Table::new_from_row( TableRow::new_from_element(cloned_vector) - .with_attribute("transform", multiplied_transform) - .with_attribute("alpha_blending", alpha_blending), + .with_attribute(ATTR_TRANSFORM, multiplied_transform) + .with_attribute(ATTR_ALPHA_BLENDING, alpha_blending), ); (id, mask_type, vector_row) @@ -957,7 +958,7 @@ impl Render for Table { attributes.push("d", face_d.clone()); let matrix = format_transform_matrix(element_transform); if !matrix.is_empty() { - attributes.push("transform", matrix); + attributes.push(ATTR_TRANSFORM, matrix); } let mut style = vector.style.clone(); style.clear_stroke(); @@ -978,7 +979,7 @@ impl Render for Table { attributes.push("d", path.clone()); let matrix = format_transform_matrix(element_transform); if !matrix.is_empty() { - attributes.push("transform", matrix); + attributes.push(ATTR_TRANSFORM, matrix); } let defs = &mut attributes.0.svg_defs; @@ -1043,7 +1044,7 @@ impl Render for Table { attributes.push("d", path); let matrix = format_transform_matrix(element_transform); if !matrix.is_empty() { - attributes.push("transform", matrix); + attributes.push(ATTR_TRANSFORM, matrix); } let mut style = vector.style.clone(); style.clear_stroke(); @@ -1068,8 +1069,8 @@ impl Render for Table { use graphic_types::vector_types::vector; let Some(element) = self.element(index) else { continue }; - let row_transform: DAffine2 = self.attribute_cloned_or_default("transform", index); - let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index); + let row_transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index); + let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index); let multiplied_transform = parent_transform * row_transform; let has_real_stroke = element.style.stroke().filter(|stroke| stroke.weight() > 0.); let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.); @@ -1257,8 +1258,8 @@ impl Render for Table { let vector_table = Table::new_from_row( TableRow::new_from_element(cloned_element) - .with_attribute("transform", row_transform) - .with_attribute("alpha_blending", alpha_blending), + .with_attribute(ATTR_TRANSFORM, row_transform) + .with_attribute(ATTR_ALPHA_BLENDING, alpha_blending), ); let bounds = element.bounding_box_with_transform(multiplied_transform).unwrap_or(layer_bounds); @@ -1329,12 +1330,12 @@ impl Render for Table { fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, caller_element_id: Option) { for index in 0..self.len() { let Some(vector) = self.element(index) else { continue }; - let transform: DAffine2 = self.attribute_cloned_or_default("transform", index); - let layer_path: Table = self.attribute_cloned_or_default("editor:layer", index); + let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index); + let layer_path: Table = self.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index); let layer = layer_path.iter_element_values().next_back().copied(); if let Some(element_id) = caller_element_id.or(layer) { - // When recovering element_id from the item's editor:layer tag (because the caller + // When recovering element_id from the item's editor:layer_path tag (because the caller // passed None), also store the transform metadata that Graphic::collect_metadata // normally provides but skipped due to the None element_id. if caller_element_id.is_none() { @@ -1378,7 +1379,7 @@ impl Render for Table { // If this item carries a snapshot of upstream graphic content (e.g. it was produced by Boolean Operation, // Flatten Path, Morph, or any other destructive merge), recurse into that snapshot so the editor can // surface the original child layers' click targets. - let upstream_nested_layers = self.attribute_cloned_or_default::>("editor:merged_layers", index); + let upstream_nested_layers = self.attribute_cloned_or_default::>(ATTR_EDITOR_MERGED_LAYERS, index); if !upstream_nested_layers.is_empty() { let mut upstream_footprint = footprint; upstream_footprint.transform *= transform; @@ -1390,7 +1391,7 @@ impl Render for Table { fn add_upstream_click_targets(&self, click_targets: &mut Vec) { for index in 0..self.len() { let Some(vector) = self.element(index) else { continue }; - let transform: DAffine2 = self.attribute_cloned_or_default("transform", index); + let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index); let stroke_width = vector.style.stroke().as_ref().map_or(0., Stroke::effective_width); let filled = vector.style.fill() != &Fill::None; @@ -1435,8 +1436,8 @@ impl Render for Table> { for index in 0..self.len() { let Some(image) = self.element(index) else { continue }; - let transform: DAffine2 = self.attribute_cloned_or_default("transform", index); - let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index); + let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index); + let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index); if image.data.is_empty() { continue; @@ -1457,7 +1458,7 @@ impl Render for Table> { let matrix = DAffine2::from_scale_angle_translation(transform_values.0, transform_values.1, transform_values.2); let matrix = format_transform_matrix(matrix); if !matrix.is_empty() { - attributes.push("transform", matrix); + attributes.push(ATTR_TRANSFORM, matrix); } attributes.push("width", size.x.to_string()); @@ -1500,7 +1501,7 @@ impl Render for Table> { attributes.push("href", base64_string); let matrix = format_transform_matrix(transform); if !matrix.is_empty() { - attributes.push("transform", matrix); + attributes.push(ATTR_TRANSFORM, matrix); } let opacity = alpha_blending.opacity(render_params.for_mask); @@ -1522,7 +1523,7 @@ impl Render for Table> { continue; } - let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index); + let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index); let blend_mode = alpha_blending.blend_mode.to_peniko(); let opacity = alpha_blending.opacity(render_params.for_mask); @@ -1537,7 +1538,7 @@ impl Render for Table> { layer = true; } - let transform_attribute: DAffine2 = self.attribute_cloned_or_default("transform", index); + let transform_attribute: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index); if let RenderMode::Outline = render_params.render_mode { let outline_transform: DAffine2 = transform * transform_attribute; @@ -1577,7 +1578,7 @@ impl Render for Table> { metadata.upstream_footprints.insert(element_id, footprint); // TODO: Find a way to handle more than one item of the `Table>` if !self.is_empty() { - let transform: DAffine2 = self.attribute_cloned_or_default("transform", 0); + let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, 0); metadata.local_transforms.insert(element_id, transform); // If this raster carries a snapshot of upstream graphic content (e.g. it was produced by Rasterize, @@ -1586,7 +1587,7 @@ impl Render for Table> { // The snapshot was captured before Rasterize shifted its input transforms to align with the rasterization // area, so the children are already in the coordinate space matching `footprint` here — we must NOT // multiply in `transform` (which is the rasterization area, not a layer-stack transform). - let upstream_nested_layers = self.attribute_cloned_or_default::>("editor:merged_layers", 0); + let upstream_nested_layers = self.attribute_cloned_or_default::>(ATTR_EDITOR_MERGED_LAYERS, 0); if !upstream_nested_layers.is_empty() { upstream_nested_layers.collect_metadata(metadata, footprint, None); } @@ -1609,7 +1610,7 @@ impl Render for Table> { fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) { for index in 0..self.len() { let Some(raster) = self.element(index) else { continue }; - let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index); + let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index); let blend_mode = match render_params.render_mode { RenderMode::Outline => peniko::Mix::Normal, _ => alpha_blending.blend_mode.to_peniko(), @@ -1626,7 +1627,7 @@ impl Render for Table> { layer = true; } - let transform_attribute: DAffine2 = self.attribute_cloned_or_default("transform", index); + let transform_attribute: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index); if let RenderMode::Outline = render_params.render_mode { let outline_transform = transform * transform_attribute; @@ -1667,7 +1668,7 @@ impl Render for Table> { metadata.upstream_footprints.insert(element_id, footprint); // TODO: Find a way to handle more than one item of the `Table>` if !self.is_empty() { - let transform: DAffine2 = self.attribute_cloned_or_default("transform", 0); + let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, 0); metadata.local_transforms.insert(element_id, transform); // If this raster carries a snapshot of upstream graphic content (e.g. it was produced by Rasterize, @@ -1676,7 +1677,7 @@ impl Render for Table> { // The snapshot was captured before Rasterize shifted its input transforms to align with the rasterization // area, so the children are already in the coordinate space matching `footprint` here — we must NOT // multiply in `transform` (which is the rasterization area, not a layer-stack transform). - let upstream_nested_layers = self.attribute_cloned_or_default::>("editor:merged_layers", 0); + let upstream_nested_layers = self.attribute_cloned_or_default::>(ATTR_EDITOR_MERGED_LAYERS, 0); if !upstream_nested_layers.is_empty() { upstream_nested_layers.collect_metadata(metadata, footprint, None); } @@ -1697,7 +1698,7 @@ impl Render for Table> { // later replace with the current viewport transform before each render. impl Render for Table { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { - for (color, alpha_blending) in self.iter_element_values().zip(self.iter_attribute_values_or_default::("alpha_blending")) { + for (color, alpha_blending) in self.iter_element_values().zip(self.iter_attribute_values_or_default::(ATTR_ALPHA_BLENDING)) { render.leaf_tag("polyline", |attributes| { // Chrome doesn't like drawing centered rectangles bigger than ~20 million so we draw a polyline quad instead let max = u64::MAX; @@ -1723,7 +1724,7 @@ impl Render for Table { fn render_to_vello(&self, scene: &mut Scene, _parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) { use vello::peniko; - for (color, alpha_blending) in self.iter_element_values().zip(self.iter_attribute_values_or_default::("alpha_blending")) { + for (color, alpha_blending) in self.iter_element_values().zip(self.iter_attribute_values_or_default::(ATTR_ALPHA_BLENDING)) { let blend_mode = alpha_blending.blend_mode.to_peniko(); let opacity = alpha_blending.opacity(render_params.for_mask); @@ -1752,8 +1753,8 @@ impl Render for Table { fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) { for index in 0..self.len() { let Some(gradient) = self.element(index) else { continue }; - let transform: DAffine2 = self.attribute_cloned_or_default("transform", index); - let alpha_blending: AlphaBlending = self.attribute_cloned_or_default("alpha_blending", index); + let transform: DAffine2 = self.attribute_cloned_or_default(ATTR_TRANSFORM, index); + let alpha_blending: AlphaBlending = self.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, index); render.leaf_tag("rect", |attributes| { // Chrome doesn't like drawing centered rectangles bigger than ~20 million so we draw a polyline quad instead let max = u64::MAX; @@ -1820,7 +1821,7 @@ impl Render for Table { fn render_to_vello(&self, scene: &mut Scene, _parent_transform: DAffine2, _context: &mut RenderContext, render_params: &RenderParams) { use vello::peniko; - for (gradient, alpha_blending) in self.iter_element_values().zip(self.iter_attribute_values_or_default::("alpha_blending")) { + for (gradient, alpha_blending) in self.iter_element_values().zip(self.iter_attribute_values_or_default::(ATTR_ALPHA_BLENDING)) { let blend_mode = alpha_blending.blend_mode.to_peniko(); let opacity = alpha_blending.opacity(render_params.for_mask); diff --git a/node-graph/libraries/vector-types/src/vector/style.rs b/node-graph/libraries/vector-types/src/vector/style.rs index 103a7c0f..2dc8301a 100644 --- a/node-graph/libraries/vector-types/src/vector/style.rs +++ b/node-graph/libraries/vector-types/src/vector/style.rs @@ -1,6 +1,7 @@ //! Contains stylistic options for SVG elements. pub use crate::gradient::*; +use core_types::ATTR_ALPHA_BLENDING; use core_types::AlphaBlending; use core_types::Color; use core_types::color::Alpha; @@ -135,7 +136,7 @@ impl From> for Fill { impl From> for Fill { fn from(color: Table) -> Fill { - let alpha = color.attribute_cloned_or_default::("alpha_blending", 0).opacity; + let alpha = color.attribute_cloned_or_default::(ATTR_ALPHA_BLENDING, 0).opacity; let color = color.element(0).copied(); Fill::solid_or_none(color.map(|c| c.with_alpha(c.alpha() * alpha))) } diff --git a/node-graph/nodes/blending/src/lib.rs b/node-graph/nodes/blending/src/lib.rs index 4f7399e4..bb6bbb33 100644 --- a/node-graph/nodes/blending/src/lib.rs +++ b/node-graph/nodes/blending/src/lib.rs @@ -1,7 +1,7 @@ use core_types::AlphaBlending; use core_types::registry::types::Percentage; use core_types::table::Table; -use core_types::{BlendMode, Color, Ctx}; +use core_types::{ATTR_ALPHA_BLENDING, BlendMode, Color, Ctx}; use graphic_types::Graphic; use graphic_types::Vector; use graphic_types::raster_types::{CPU, Raster}; @@ -18,35 +18,35 @@ impl MultiplyAlpha for Color { } impl MultiplyAlpha for Table { fn multiply_alpha(&mut self, factor: f64) { - for a in self.iter_attribute_values_mut_or_default::("alpha_blending") { + for a in self.iter_attribute_values_mut_or_default::(ATTR_ALPHA_BLENDING) { a.opacity *= factor as f32; } } } impl MultiplyAlpha for Table { fn multiply_alpha(&mut self, factor: f64) { - for a in self.iter_attribute_values_mut_or_default::("alpha_blending") { + for a in self.iter_attribute_values_mut_or_default::(ATTR_ALPHA_BLENDING) { a.opacity *= factor as f32; } } } impl MultiplyAlpha for Table> { fn multiply_alpha(&mut self, factor: f64) { - for a in self.iter_attribute_values_mut_or_default::("alpha_blending") { + for a in self.iter_attribute_values_mut_or_default::(ATTR_ALPHA_BLENDING) { a.opacity *= factor as f32; } } } impl MultiplyAlpha for Table { fn multiply_alpha(&mut self, factor: f64) { - for a in self.iter_attribute_values_mut_or_default::("alpha_blending") { + for a in self.iter_attribute_values_mut_or_default::(ATTR_ALPHA_BLENDING) { a.opacity *= factor as f32; } } } impl MultiplyAlpha for Table { fn multiply_alpha(&mut self, factor: f64) { - for a in self.iter_attribute_values_mut_or_default::("alpha_blending") { + for a in self.iter_attribute_values_mut_or_default::(ATTR_ALPHA_BLENDING) { a.opacity *= factor as f32; } } @@ -62,35 +62,35 @@ impl MultiplyFill for Color { } impl MultiplyFill for Table { fn multiply_fill(&mut self, factor: f64) { - for a in self.iter_attribute_values_mut_or_default::("alpha_blending") { + for a in self.iter_attribute_values_mut_or_default::(ATTR_ALPHA_BLENDING) { a.fill *= factor as f32; } } } impl MultiplyFill for Table { fn multiply_fill(&mut self, factor: f64) { - for a in self.iter_attribute_values_mut_or_default::("alpha_blending") { + for a in self.iter_attribute_values_mut_or_default::(ATTR_ALPHA_BLENDING) { a.fill *= factor as f32; } } } impl MultiplyFill for Table> { fn multiply_fill(&mut self, factor: f64) { - for a in self.iter_attribute_values_mut_or_default::("alpha_blending") { + for a in self.iter_attribute_values_mut_or_default::(ATTR_ALPHA_BLENDING) { a.fill *= factor as f32; } } } impl MultiplyFill for Table { fn multiply_fill(&mut self, factor: f64) { - for a in self.iter_attribute_values_mut_or_default::("alpha_blending") { + for a in self.iter_attribute_values_mut_or_default::(ATTR_ALPHA_BLENDING) { a.fill *= factor as f32; } } } impl MultiplyFill for Table { fn multiply_fill(&mut self, factor: f64) { - for a in self.iter_attribute_values_mut_or_default::("alpha_blending") { + for a in self.iter_attribute_values_mut_or_default::(ATTR_ALPHA_BLENDING) { a.fill *= factor as f32; } } @@ -102,35 +102,35 @@ trait SetBlendMode { impl SetBlendMode for Table { fn set_blend_mode(&mut self, blend_mode: BlendMode) { - for a in self.iter_attribute_values_mut_or_default::("alpha_blending") { + for a in self.iter_attribute_values_mut_or_default::(ATTR_ALPHA_BLENDING) { a.blend_mode = blend_mode; } } } impl SetBlendMode for Table { fn set_blend_mode(&mut self, blend_mode: BlendMode) { - for a in self.iter_attribute_values_mut_or_default::("alpha_blending") { + for a in self.iter_attribute_values_mut_or_default::(ATTR_ALPHA_BLENDING) { a.blend_mode = blend_mode; } } } impl SetBlendMode for Table> { fn set_blend_mode(&mut self, blend_mode: BlendMode) { - for a in self.iter_attribute_values_mut_or_default::("alpha_blending") { + for a in self.iter_attribute_values_mut_or_default::(ATTR_ALPHA_BLENDING) { a.blend_mode = blend_mode; } } } impl SetBlendMode for Table { fn set_blend_mode(&mut self, blend_mode: BlendMode) { - for a in self.iter_attribute_values_mut_or_default::("alpha_blending") { + for a in self.iter_attribute_values_mut_or_default::(ATTR_ALPHA_BLENDING) { a.blend_mode = blend_mode; } } } impl SetBlendMode for Table { fn set_blend_mode(&mut self, blend_mode: BlendMode) { - for a in self.iter_attribute_values_mut_or_default::("alpha_blending") { + for a in self.iter_attribute_values_mut_or_default::(ATTR_ALPHA_BLENDING) { a.blend_mode = blend_mode; } } @@ -142,35 +142,35 @@ trait SetClip { impl SetClip for Table { fn set_clip(&mut self, clip: bool) { - for a in self.iter_attribute_values_mut_or_default::("alpha_blending") { + for a in self.iter_attribute_values_mut_or_default::(ATTR_ALPHA_BLENDING) { a.clip = clip; } } } impl SetClip for Table { fn set_clip(&mut self, clip: bool) { - for a in self.iter_attribute_values_mut_or_default::("alpha_blending") { + for a in self.iter_attribute_values_mut_or_default::(ATTR_ALPHA_BLENDING) { a.clip = clip; } } } impl SetClip for Table> { fn set_clip(&mut self, clip: bool) { - for a in self.iter_attribute_values_mut_or_default::("alpha_blending") { + for a in self.iter_attribute_values_mut_or_default::(ATTR_ALPHA_BLENDING) { a.clip = clip; } } } impl SetClip for Table { fn set_clip(&mut self, clip: bool) { - for a in self.iter_attribute_values_mut_or_default::("alpha_blending") { + for a in self.iter_attribute_values_mut_or_default::(ATTR_ALPHA_BLENDING) { a.clip = clip; } } } impl SetClip for Table { fn set_clip(&mut self, clip: bool) { - for a in self.iter_attribute_values_mut_or_default::("alpha_blending") { + for a in self.iter_attribute_values_mut_or_default::(ATTR_ALPHA_BLENDING) { a.clip = clip; } } diff --git a/node-graph/nodes/brush/src/brush.rs b/node-graph/nodes/brush/src/brush.rs index c123c37b..ca5aae4c 100644 --- a/node-graph/nodes/brush/src/brush.rs +++ b/node-graph/nodes/brush/src/brush.rs @@ -10,6 +10,7 @@ use core_types::table::{Table, TableRow}; use core_types::transform::Transform; use core_types::uuid::NodeId; use core_types::value::ClonedNode; +use core_types::{ATTR_ALPHA_BLENDING, ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM}; use core_types::{AlphaBlending, Ctx, Node}; use glam::{DAffine2, DVec2}; use raster_nodes::blending_nodes::blend_colors; @@ -90,7 +91,7 @@ where return target; } - let (elements, transforms) = target.element_and_attribute_slices_mut::("transform"); + let (elements, transforms) = target.element_and_attribute_slices_mut::(ATTR_TRANSFORM); for (element, transform_attribute) in elements.iter_mut().zip(transforms.iter()) { let target_width = element.width; let target_height = element.height; @@ -280,7 +281,7 @@ async fn brush( let has_erase_or_restore_strokes = strokes.iter_element_values().any(|s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore)); if has_erase_or_restore_strokes { let opaque_image = Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE); - let mut erase_restore_mask = TableRow::new_from_element(Raster::new_cpu(opaque_image)).with_attribute("transform", background_bounds); + let mut erase_restore_mask = TableRow::new_from_element(Raster::new_cpu(opaque_image)).with_attribute(ATTR_TRANSFORM, background_bounds); for stroke in strokes.into_iter().map(|row| row.into_element()) { let mut brush_texture = cache.get_cached_brush(&stroke.style); @@ -312,14 +313,14 @@ async fn brush( actual_image = blend_image_closure(erase_restore_mask, actual_image, |a, b| blend_params.eval((a, b))); } - let transform: DAffine2 = actual_image.attribute_cloned_or_default("transform"); - let alpha_blending: AlphaBlending = actual_image.attribute_cloned_or_default("alpha_blending"); - let layer: Table = actual_image.attribute_cloned_or_default("editor:layer"); + let transform: DAffine2 = actual_image.attribute_cloned_or_default(ATTR_TRANSFORM); + let alpha_blending: AlphaBlending = actual_image.attribute_cloned_or_default(ATTR_ALPHA_BLENDING); + let layer: Table = actual_image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH); *image.element_mut(0).unwrap() = actual_image.into_element(); - image.set_attribute("transform", 0, transform); - image.set_attribute("alpha_blending", 0, alpha_blending); - image.set_attribute("editor:layer", 0, layer); + image.set_attribute(ATTR_TRANSFORM, 0, transform); + image.set_attribute(ATTR_ALPHA_BLENDING, 0, alpha_blending); + image.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer); image } @@ -329,8 +330,8 @@ pub fn blend_image_closure(foreground: TableRow>, mut background: Ta let background_size = DVec2::new(background.element().width as f64, background.element().height as f64); // Transforms a point from the background image to the foreground image - let foreground_transform: DAffine2 = foreground.attribute_cloned_or_default("transform"); - let background_transform: DAffine2 = background.attribute_cloned_or_default("transform"); + let foreground_transform: DAffine2 = foreground.attribute_cloned_or_default(ATTR_TRANSFORM); + let background_transform: DAffine2 = background.attribute_cloned_or_default(ATTR_TRANSFORM); let background_to_foreground = DAffine2::from_scale(foreground_size) * foreground_transform.inverse() * background_transform * DAffine2::from_scale(1. / background_size); // Footprint of the foreground image (0, 0)..(1, 1) in the background image space @@ -361,7 +362,7 @@ pub fn blend_stamp_closure(foreground: BrushStampGenerator, mut backgroun let background_size = DVec2::new(background.element().width as f64, background.element().height as f64); // Transforms a point from the background image to the foreground image - let background_transform: DAffine2 = background.attribute_cloned_or_default("transform"); + let background_transform: DAffine2 = background.attribute_cloned_or_default(ATTR_TRANSFORM); let background_to_foreground = background_transform * DAffine2::from_scale(1. / background_size); // Footprint of the foreground image (0, 0)..(1, 1) in the background image space diff --git a/node-graph/nodes/brush/src/brush_cache.rs b/node-graph/nodes/brush/src/brush_cache.rs index a5f2a619..cfb54cef 100644 --- a/node-graph/nodes/brush/src/brush_cache.rs +++ b/node-graph/nodes/brush/src/brush_cache.rs @@ -1,5 +1,6 @@ use crate::brush_stroke::BrushStroke; use crate::brush_stroke::BrushStyle; +use core_types::ATTR_TRANSFORM; use core_types::graphene_hash::CacheHashWrapper; use core_types::table::TableRow; use dyn_any::DynAny; @@ -65,7 +66,7 @@ impl BrushCacheImpl { // Check if the first non-blended stroke is an extension of the last one. // Transform is set to ZERO (not the default IDENTITY) as a sentinel to mark this item as uninitialized. - let mut first_stroke_texture = TableRow::new_from_element(Raster::::default()).with_attribute("transform", glam::DAffine2::ZERO); + let mut first_stroke_texture = TableRow::new_from_element(Raster::::default()).with_attribute(ATTR_TRANSFORM, glam::DAffine2::ZERO); let mut first_stroke_point_skip = 0; let strokes = input[num_blended_strokes..].to_vec(); if !strokes.is_empty() && self.prev_input.len() > num_blended_strokes { diff --git a/node-graph/nodes/graphic/src/graphic.rs b/node-graph/nodes/graphic/src/graphic.rs index 42213692..0830971f 100644 --- a/node-graph/nodes/graphic/src/graphic.rs +++ b/node-graph/nodes/graphic/src/graphic.rs @@ -2,7 +2,7 @@ use core_types::bounds::{BoundingBox, RenderBoundingBox}; use core_types::registry::types::{Angle, SignedInteger}; use core_types::table::{Table, TableRow}; use core_types::uuid::NodeId; -use core_types::{AnyHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl}; +use core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, AnyHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl}; use glam::{DAffine2, DVec2}; use graphic_types::graphic::{Graphic, IntoGraphicTable}; use graphic_types::{Artboard, Vector}; @@ -196,8 +196,8 @@ where // Create and add mirrored items for mut row in content.into_iter() { - let current_transform: DAffine2 = row.attribute_cloned_or_default("transform"); - row.set_attribute("transform", reflected_transform * current_transform); + let current_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); + row.set_attribute(ATTR_TRANSFORM, reflected_transform * current_transform); result_table.push(row); } @@ -207,7 +207,7 @@ where /// Returns the path identifying the subgraph (network) that contains this proto node — i.e. the input `node_path` /// with its own trailing entry dropped. The terminating element of the returned path is the document node whose /// encapsulated network we live in, so the path doubles as a unique reference to that node at any nesting depth. -/// Used as the value source for stamping the `editor:layer` attribute on each item of a layer's output, which lets +/// Used as the value source for stamping the `editor:layer_path` attribute on each item of a layer's output, which lets /// editor tools (e.g. selection, click target routing) trace data back to its owning layer regardless of whether /// the layer is at the root document network or nested inside a custom subgraph. #[node_macro::node(name("Path of Subgraph"), category(""))] @@ -296,7 +296,7 @@ pub async fn legacy_layer_extend( let mut base = base; for mut row in new.into_iter() { - row.set_attribute("editor:layer", layer); + row.set_attribute(ATTR_EDITOR_LAYER_PATH, layer); base.push(row); } @@ -348,7 +348,7 @@ pub async fn flatten_graphic(_: impl Ctx, content: Table, fully_flatten for index in 0..current_graphic_table.len() { let Some(current_element) = current_graphic_table.element(index) else { continue }; let current_element = current_element.clone(); - let current_transform: DAffine2 = current_graphic_table.attribute_cloned_or_default("transform", index); + let current_transform: DAffine2 = current_graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index); let recurse = fully_flatten || recursion_depth == 0; @@ -356,7 +356,7 @@ pub async fn flatten_graphic(_: impl Ctx, content: Table, fully_flatten // If we're allowed to recurse, flatten any graphics we encounter Graphic::Graphic(mut current_element) if recurse => { // Apply the parent graphic's transform to all child elements - for graphic_transform in current_element.iter_attribute_values_mut_or_default::("transform") { + for graphic_transform in current_element.iter_attribute_values_mut_or_default::(ATTR_TRANSFORM) { *graphic_transform = current_transform * *graphic_transform; } diff --git a/node-graph/nodes/gstd/src/platform_application_io.rs b/node-graph/nodes/gstd/src/platform_application_io.rs index 5ad29c21..9002f553 100644 --- a/node-graph/nodes/gstd/src/platform_application_io.rs +++ b/node-graph/nodes/gstd/src/platform_application_io.rs @@ -3,12 +3,12 @@ use base64::Engine; #[cfg(target_family = "wasm")] use canvas_utils::{Canvas, CanvasHandle}; #[cfg(target_family = "wasm")] -use core_types::WasmNotSend; -#[cfg(target_family = "wasm")] use core_types::math::bbox::Bbox; use core_types::table::{Table, TableRow}; #[cfg(target_family = "wasm")] use core_types::transform::Footprint; +#[cfg(target_family = "wasm")] +use core_types::{ATTR_EDITOR_MERGED_LAYERS, ATTR_TRANSFORM, WasmNotSend}; use core_types::{Color, Ctx}; pub use graph_craft::application_io::*; pub use graph_craft::document::value::RenderOutputType; @@ -210,7 +210,7 @@ where ..Default::default() }; - for transform in data.iter_attribute_values_mut_or_default::("transform") { + for transform in data.iter_attribute_values_mut_or_default::(ATTR_TRANSFORM) { *transform = DAffine2::from_translation(-aabb.start) * *transform; } data.render_svg(&mut render, &render_params); @@ -237,7 +237,7 @@ where let image = Image::from_image_data(&rasterized.data().0, resolution.x as u32, resolution.y as u32); Table::new_from_row( TableRow::new_from_element(Raster::new_cpu(image)) - .with_attribute("transform", footprint.transform) - .with_attribute("editor:merged_layers", upstream_graphic_table), + .with_attribute(ATTR_TRANSFORM, footprint.transform) + .with_attribute(ATTR_EDITOR_MERGED_LAYERS, upstream_graphic_table), ) } diff --git a/node-graph/nodes/path-bool/src/lib.rs b/node-graph/nodes/path-bool/src/lib.rs index c6e7b14c..ac866c67 100644 --- a/node-graph/nodes/path-bool/src/lib.rs +++ b/node-graph/nodes/path-bool/src/lib.rs @@ -1,6 +1,6 @@ use core_types::table::{Table, TableRow}; use core_types::uuid::NodeId; -use core_types::{AlphaBlending, Color, Ctx}; +use core_types::{ATTR_ALPHA_BLENDING, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_TRANSFORM, AlphaBlending, Color, Ctx}; use glam::{DAffine2, DVec2}; use graphic_types::vector_types::subpath::{ManipulatorGroup, Subpath}; use graphic_types::vector_types::vector::PointId; @@ -40,8 +40,8 @@ async fn boolean_operation, boolean_operation: let mut row = if let Some(index) = copy_from_index { let mut attributes = vector.clone_row_attributes(index); // The boolean op bakes input transforms into the output geometry, so the result item carries no transform of its own - attributes.insert("transform", DAffine2::IDENTITY); + attributes.insert(ATTR_TRANSFORM, DAffine2::IDENTITY); let copy_from = vector.element(index).unwrap(); let element = Vector { style: copy_from.style.clone(), @@ -139,7 +139,7 @@ fn boolean_operation_on_vector_table(vector: &Table, boolean_operation: for index in 0..vector.len() { let element = vector.element(index).unwrap(); - paths.push(to_bez_path(element, vector.attribute_cloned_or_default("transform", index))); + paths.push(to_bez_path(element, vector.attribute_cloned_or_default(ATTR_TRANSFORM, index))); } let top = match Topology::::from_paths(paths.iter().enumerate().map(|(idx, path)| (path, (idx, paths.len()))), EPSILON) { @@ -167,18 +167,18 @@ fn flatten_vector(graphic_table: &Table) -> Table { match graphic.clone() { Graphic::Vector(vector) => { // Apply the parent graphic's transform to each element of the `Table` - let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index); + let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index); vector .into_iter() .map(|mut sub_vector| { - let current_transform: DAffine2 = sub_vector.attribute_cloned_or_default("transform"); - *sub_vector.attribute_mut_or_insert_default("transform") = parent_transform * current_transform; + let current_transform: DAffine2 = sub_vector.attribute_cloned_or_default(ATTR_TRANSFORM); + *sub_vector.attribute_mut_or_insert_default(ATTR_TRANSFORM) = parent_transform * current_transform; sub_vector }) .collect::>() } Graphic::RasterCPU(image) => { - let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index); + let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index); let make_row = |transform, layer, alpha_blending| { let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE); subpath.apply_transform(transform); @@ -187,8 +187,8 @@ fn flatten_vector(graphic_table: &Table) -> Table { element.style.set_fill(Fill::Solid(Color::BLACK)); TableRow::new_from_element(element) - .with_attribute("alpha_blending", alpha_blending) - .with_attribute("editor:layer", layer) + .with_attribute(ATTR_ALPHA_BLENDING, alpha_blending) + .with_attribute(ATTR_EDITOR_LAYER_PATH, layer) }; // Apply the parent graphic's transform to each raster element, preserving each item's layer @@ -196,15 +196,15 @@ fn flatten_vector(graphic_table: &Table) -> Table { // back to the originating raster layer (0..image.len()) .map(|i| { - let row_transform: DAffine2 = image.attribute_cloned_or_default("transform", i); - let layer: Table = image.attribute_cloned_or_default("editor:layer", i); - let alpha_blending: AlphaBlending = image.attribute_cloned_or_default("alpha_blending", i); + let row_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM, i); + let layer: Table = image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, i); + let alpha_blending: AlphaBlending = image.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, i); make_row(parent_transform * row_transform, layer, alpha_blending) }) .collect::>() } Graphic::RasterGPU(image) => { - let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index); + let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index); let make_row = |transform, layer, alpha_blending| { let mut subpath = Subpath::new_rectangle(DVec2::ZERO, DVec2::ONE); subpath.apply_transform(transform); @@ -213,8 +213,8 @@ fn flatten_vector(graphic_table: &Table) -> Table { element.style.set_fill(Fill::Solid(Color::BLACK)); TableRow::new_from_element(element) - .with_attribute("alpha_blending", alpha_blending) - .with_attribute("editor:layer", layer) + .with_attribute(ATTR_ALPHA_BLENDING, alpha_blending) + .with_attribute(ATTR_EDITOR_LAYER_PATH, layer) }; // Apply the parent graphic's transform to each raster element, preserving each item's layer @@ -222,17 +222,17 @@ fn flatten_vector(graphic_table: &Table) -> Table { // back to the originating raster layer (0..image.len()) .map(|i| { - let row_transform: DAffine2 = image.attribute_cloned_or_default("transform", i); - let layer: Table = image.attribute_cloned_or_default("editor:layer", i); - let alpha_blending: AlphaBlending = image.attribute_cloned_or_default("alpha_blending", i); + let row_transform: DAffine2 = image.attribute_cloned_or_default(ATTR_TRANSFORM, i); + let layer: Table = image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, i); + let alpha_blending: AlphaBlending = image.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, i); make_row(parent_transform * row_transform, layer, alpha_blending) }) .collect::>() } Graphic::Graphic(mut graphic) => { - let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default("transform", index); + let parent_transform: DAffine2 = graphic_table.attribute_cloned_or_default(ATTR_TRANSFORM, index); // Apply the parent graphic's transform to each element of the inner `Table` - for transform in graphic.iter_attribute_values_mut_or_default::("transform") { + for transform in graphic.iter_attribute_values_mut_or_default::(ATTR_TRANSFORM) { *transform = parent_transform * *transform; } diff --git a/node-graph/nodes/raster/src/std_nodes.rs b/node-graph/nodes/raster/src/std_nodes.rs index 3cf5102c..011f3427 100644 --- a/node-graph/nodes/raster/src/std_nodes.rs +++ b/node-graph/nodes/raster/src/std_nodes.rs @@ -6,6 +6,7 @@ use core_types::context::{Ctx, ExtractFootprint}; use core_types::math::bbox::Bbox; use core_types::table::{Table, TableRow}; use core_types::transform::Transform; +use core_types::{ATTR_ALPHA_BLENDING, ATTR_TRANSFORM}; use dyn_any::DynAny; use fastnoise_lite; use glam::{DAffine2, DVec2, Vec2}; @@ -34,7 +35,7 @@ pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Tabl image_frame .into_iter() .filter_map(|row| { - let image_frame_transform: DAffine2 = row.attribute_cloned_or_default("transform"); + let image_frame_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); let (image, mut attributes) = row.into_parts(); // Resize the image using the image crate @@ -86,7 +87,7 @@ pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Tabl // we need to adjust the offset if we truncate the offset calculation let new_transform = image_frame_transform * DAffine2::from_translation(offset) * DAffine2::from_scale(size); - attributes.insert("transform", new_transform); + attributes.insert(ATTR_TRANSFORM, new_transform); Some(TableRow::from_parts(Raster::new_cpu(image), attributes)) }) @@ -194,7 +195,7 @@ pub fn mask( .into_iter() .filter_map(|mut row| { let image_size = DVec2::new(row.element().width as f64, row.element().height as f64); - let stencil_transform: DAffine2 = stencil.attribute_cloned_or_default("transform"); + let stencil_transform: DAffine2 = stencil.attribute_cloned_or_default(ATTR_TRANSFORM); let mask_size = stencil_transform.scale_magnitudes(); if mask_size == DVec2::ZERO { @@ -202,7 +203,7 @@ pub fn mask( } // Transforms a point from the background image to the foreground image - let transform_attribute: DAffine2 = row.attribute_cloned_or_default("transform"); + let transform_attribute: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); let bg_to_fg = transform_attribute * DAffine2::from_scale(1. / image_size); let stencil_transform_inverse = stencil_transform.inverse(); @@ -230,7 +231,7 @@ pub fn extend_image_to_bounds(_: impl Ctx, image: Table>, bounds: DA image .into_iter() .map(|mut row| { - let row_transform: DAffine2 = row.attribute_cloned_or_default("transform"); + let row_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); let image_aabb = Bbox::unit().affine_transform(row_transform).to_axis_aligned_bbox(); let bounds_aabb = Bbox::unit().affine_transform(bounds.transform()).to_axis_aligned_bbox(); if image_aabb.contains(bounds_aabb.start) && image_aabb.contains(bounds_aabb.end) { @@ -267,7 +268,7 @@ pub fn extend_image_to_bounds(_: impl Ctx, image: Table>, bounds: DA let new_texture_to_layer_space = row_transform * DAffine2::from_scale(1. / orig_image_scale) * DAffine2::from_translation(new_start) * DAffine2::from_scale(new_scale); *row.element_mut() = Raster::new_cpu(new_image); - row.set_attribute("transform", new_texture_to_layer_space); + row.set_attribute(ATTR_TRANSFORM, new_texture_to_layer_space); row }) .collect() @@ -282,8 +283,8 @@ pub fn empty_image(_: impl Ctx, transform: DAffine2, color: Table) -> Tab let image = Image::new(width, height, color); let mut result_table = Table::new_from_element(Raster::new_cpu(image)); - result_table.set_attribute("transform", 0, transform); - result_table.set_attribute("alpha_blending", 0, AlphaBlending::default()); + result_table.set_attribute(ATTR_TRANSFORM, 0, transform); + result_table.set_attribute(ATTR_ALPHA_BLENDING, 0, AlphaBlending::default()); // Callers of empty_image can safely unwrap on returned `Table` result_table @@ -378,7 +379,7 @@ pub fn noise_pattern( } } - return Table::new_from_row(TableRow::new_from_element(Raster::new_cpu(image)).with_attribute("transform", transform)); + return Table::new_from_row(TableRow::new_from_element(Raster::new_cpu(image)).with_attribute(ATTR_TRANSFORM, transform)); } }; noise.set_noise_type(Some(noise_type)); @@ -436,7 +437,7 @@ pub fn noise_pattern( } } - Table::new_from_row(TableRow::new_from_element(Raster::new_cpu(image)).with_attribute("transform", transform)) + Table::new_from_row(TableRow::new_from_element(Raster::new_cpu(image)).with_attribute(ATTR_TRANSFORM, transform)) } #[node_macro::node(category("Raster: Pattern"))] @@ -481,7 +482,7 @@ pub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> Table> { data, ..Default::default() })) - .with_attribute("transform", DAffine2::from_translation(offset) * DAffine2::from_scale(size)), + .with_attribute(ATTR_TRANSFORM, DAffine2::from_translation(offset) * DAffine2::from_scale(size)), ) } diff --git a/node-graph/nodes/repeat/src/repeat_nodes.rs b/node-graph/nodes/repeat/src/repeat_nodes.rs index 48fd8f68..e4d94bbf 100644 --- a/node-graph/nodes/repeat/src/repeat_nodes.rs +++ b/node-graph/nodes/repeat/src/repeat_nodes.rs @@ -2,7 +2,7 @@ use crate::gcore::Context; use core::f64::consts::TAU; use core_types::registry::types::{Angle, PixelSize}; use core_types::table::Table; -use core_types::{CloneVarArgs, Color, Ctx, ExtractAll, InjectVarArgs, OwnedContextImpl}; +use core_types::{ATTR_TRANSFORM, CloneVarArgs, Color, Ctx, ExtractAll, InjectVarArgs, OwnedContextImpl}; use glam::{DAffine2, DVec2}; use graphic_types::{Graphic, Vector}; use raster_types::{CPU, Raster}; @@ -80,10 +80,10 @@ pub async fn repeat_array + Default + Send + Clone + 'static>( for row_index in 0..generated_content.len() { let Some(mut row) = generated_content.clone_row(row_index) else { continue }; - let local_transform: DAffine2 = row.attribute_cloned_or_default("transform"); + let local_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); let local_translation = DAffine2::from_translation(local_transform.translation); let local_matrix = DAffine2::from_mat2(local_transform.matrix2); - *row.attribute_mut_or_insert_default("transform") = local_translation * transform * local_matrix; + *row.attribute_mut_or_insert_default(ATTR_TRANSFORM) = local_translation * transform * local_matrix; result_table.push(row); } @@ -126,10 +126,10 @@ async fn repeat_radial + Default + Send + Clone + 'static>( for row_index in 0..generated_content.len() { let Some(mut row) = generated_content.clone_row(row_index) else { continue }; - let local_transform: DAffine2 = row.attribute_cloned_or_default("transform"); + let local_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); let local_translation = DAffine2::from_translation(local_transform.translation); let local_matrix = DAffine2::from_mat2(local_transform.matrix2); - *row.attribute_mut_or_insert_default("transform") = local_translation * transform * local_matrix; + *row.attribute_mut_or_insert_default(ATTR_TRANSFORM) = local_translation * transform * local_matrix; result_table.push(row); } @@ -156,7 +156,7 @@ async fn repeat_on_points + Default + Send + Clone + 'static>( for points_index in 0..points.len() { let Some(points_element) = points.element(points_index) else { continue }; - let transform: DAffine2 = points.attribute_cloned_or_default("transform", points_index); + let transform: DAffine2 = points.attribute_cloned_or_default(ATTR_TRANSFORM, points_index); let mut iteration = async |index, point| { let transformed_point = transform.transform_point2(point); @@ -165,7 +165,7 @@ async fn repeat_on_points + Default + Send + Clone + 'static>( let generated_content = content.eval(new_ctx.into_context()).await; for mut generated_row in generated_content.into_iter() { - generated_row.attribute_mut_or_insert_default::("transform").translation = transformed_point; + generated_row.attribute_mut_or_insert_default::(ATTR_TRANSFORM).translation = transformed_point; result_table.push(generated_row); } }; @@ -237,7 +237,7 @@ mod test { let bounds = generated .element(index) .unwrap() - .bounding_box_with_transform(generated.attribute_cloned_or_default("transform", index)) + .bounding_box_with_transform(generated.attribute_cloned_or_default(ATTR_TRANSFORM, index)) .unwrap(); assert!(position.abs_diff_eq((bounds[0] + bounds[1]) / 2., 1e-10)); assert_eq!((bounds[1] - bounds[0]).x, position.y); diff --git a/node-graph/nodes/text/src/json.rs b/node-graph/nodes/text/src/json.rs index 536d76b8..19de8880 100644 --- a/node-graph/nodes/text/src/json.rs +++ b/node-graph/nodes/text/src/json.rs @@ -1,5 +1,5 @@ -use core_types::Ctx; use core_types::table::{Table, TableRow}; +use core_types::{ATTR_TYPE, Ctx}; use serde_json::Value; use crate::unescape_string; @@ -248,7 +248,10 @@ fn query_json_all( let mut results = Vec::new(); resolve_all(&value, &segments, !unquote_strings, &mut results); - results.into_iter().map(|(text, ty)| TableRow::new_from_element(text).with_attribute("type", ty.to_string())).collect() + results + .into_iter() + .map(|(text, ty)| TableRow::new_from_element(text).with_attribute(ATTR_TYPE, ty.to_string())) + .collect() } /// A parsed segment of a JSON access path. diff --git a/node-graph/nodes/text/src/path_builder.rs b/node-graph/nodes/text/src/path_builder.rs index 54f0f361..f0a1d0b3 100644 --- a/node-graph/nodes/text/src/path_builder.rs +++ b/node-graph/nodes/text/src/path_builder.rs @@ -1,3 +1,4 @@ +use core_types::ATTR_TRANSFORM; use core_types::table::{Table, TableRow}; use glam::{DAffine2, DVec2}; use parley::GlyphRun; @@ -52,7 +53,7 @@ impl PathBuilder { if per_glyph_items { self.vector_table - .push(TableRow::new_from_element(Vector::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false)).with_attribute("transform", DAffine2::from_translation(glyph_offset))); + .push(TableRow::new_from_element(Vector::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false)).with_attribute(ATTR_TRANSFORM, DAffine2::from_translation(glyph_offset))); } else { for subpath in self.glyph_subpaths.drain(..) { // Unwrapping here is ok because `self.vector_table` is initialized with a single `Table` item diff --git a/node-graph/nodes/text/src/regex.rs b/node-graph/nodes/text/src/regex.rs index d38e3de5..b43611d9 100644 --- a/node-graph/nodes/text/src/regex.rs +++ b/node-graph/nodes/text/src/regex.rs @@ -1,6 +1,6 @@ -use core_types::Ctx; use core_types::registry::types::SignedInteger; use core_types::table::{Table, TableRow}; +use core_types::{ATTR_END, ATTR_NAME, ATTR_START, Ctx}; /// Checks whether the string contains a match for the given regular expression pattern. Optionally restricts the match to only the start and/or end of the string. #[node_macro::node(category("Text: Regex"))] @@ -143,7 +143,10 @@ fn regex_find( let start = captured.map_or(0_u64, |m| m.start() as u64); let end = captured.map_or(0_u64, |m| m.end() as u64); let name = capture_names.get(i).cloned().flatten().unwrap_or_default(); - TableRow::new_from_element(text).with_attribute("start", start).with_attribute("end", end).with_attribute("name", name) + TableRow::new_from_element(text) + .with_attribute(ATTR_START, start) + .with_attribute(ATTR_END, end) + .with_attribute(ATTR_NAME, name) }) .collect() } @@ -185,8 +188,8 @@ fn regex_find_all( .filter_map(|m| m.ok()) .map(|m| { TableRow::new_from_element(m.as_str().to_string()) - .with_attribute("start", m.start() as u64) - .with_attribute("end", m.end() as u64) + .with_attribute(ATTR_START, m.start() as u64) + .with_attribute(ATTR_END, m.end() as u64) }) .collect() } diff --git a/node-graph/nodes/transform/src/transform_nodes.rs b/node-graph/nodes/transform/src/transform_nodes.rs index f8771295..910ab692 100644 --- a/node-graph/nodes/transform/src/transform_nodes.rs +++ b/node-graph/nodes/transform/src/transform_nodes.rs @@ -2,7 +2,7 @@ use core::f64; use core_types::color::Color; use core_types::table::Table; use core_types::transform::{ApplyTransform, ScaleType, Transform}; -use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, InjectFootprint, ModifyFootprint, OwnedContextImpl}; +use core_types::{ATTR_TRANSFORM, CloneVarArgs, Context, Ctx, ExtractAll, InjectFootprint, ModifyFootprint, OwnedContextImpl}; use glam::{DAffine2, DMat2, DVec2}; use graphic_types::Graphic; use graphic_types::Vector; @@ -66,7 +66,7 @@ fn reset_transform( reset_rotation: bool, reset_scale: bool, ) -> Table { - for row_transform in content.iter_attribute_values_mut_or_default::("transform") { + for row_transform in content.iter_attribute_values_mut_or_default::(ATTR_TRANSFORM) { if reset_translation { row_transform.translation = DVec2::ZERO; } @@ -102,7 +102,7 @@ fn replace_transform( mut content: Table, transform: DAffine2, ) -> Table { - for row_transform in content.iter_attribute_values_mut_or_default::("transform") { + for row_transform in content.iter_attribute_values_mut_or_default::(ATTR_TRANSFORM) { *row_transform = transform.transform(); } content @@ -123,7 +123,7 @@ async fn extract_transform( )] content: Table, ) -> DAffine2 { - content.attribute_cloned_or_default::("transform", 0) + content.attribute_cloned_or_default::(ATTR_TRANSFORM, 0) } /// Produces the inverse of the input transform, which is the transform that undoes the effect of the original transform. diff --git a/node-graph/nodes/vector/src/vector_modification_nodes.rs b/node-graph/nodes/vector/src/vector_modification_nodes.rs index 59290b2b..0ff9e358 100644 --- a/node-graph/nodes/vector/src/vector_modification_nodes.rs +++ b/node-graph/nodes/vector/src/vector_modification_nodes.rs @@ -1,6 +1,6 @@ -use core_types::Ctx; use core_types::table::Table; use core_types::uuid::NodeId; +use core_types::{ATTR_EDITOR_LAYER_PATH, ATTR_TRANSFORM, Ctx}; use glam::DAffine2; use graphic_types::Vector; use vector_types::vector::VectorModification; @@ -21,8 +21,8 @@ async fn path_modify(_ctx: impl Ctx, mut vector: Table, modification: Bo let len = node_path.len(); node_path.into_iter().take(len.saturating_sub(1)).collect() }; - let existing: Table = vector.attribute_cloned_or_default("editor:layer", 0); - vector.set_attribute("editor:layer", 0, if existing.is_empty() { subgraph_path } else { existing }); + let existing: Table = vector.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, 0); + vector.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, if existing.is_empty() { subgraph_path } else { existing }); if vector.len() > 1 { warn!("The path modify ran on {} vector items. Only the first can be modified.", vector.len()); @@ -33,7 +33,7 @@ async fn path_modify(_ctx: impl Ctx, mut vector: Table, modification: Bo /// Applies the vector path's local transformation to its geometry and resets the transform to the identity. #[node_macro::node(category("Vector"))] async fn apply_transform(_ctx: impl Ctx, mut vector: Table) -> Table { - let (elements, transforms) = vector.element_and_attribute_slices_mut::("transform"); + let (elements, transforms) = vector.element_and_attribute_slices_mut::(ATTR_TRANSFORM); for (element, transform) in elements.iter_mut().zip(transforms.iter_mut()) { for (_, point) in element.point_domain.positions_mut() { *point = transform.transform_point2(*point); diff --git a/node-graph/nodes/vector/src/vector_nodes.rs b/node-graph/nodes/vector/src/vector_nodes.rs index fd0ca721..9bcee935 100644 --- a/node-graph/nodes/vector/src/vector_nodes.rs +++ b/node-graph/nodes/vector/src/vector_nodes.rs @@ -7,7 +7,7 @@ use core_types::registry::types::{Angle, Length, Multiplier, Percentage, PixelLe use core_types::table::{Table, TableRow}; use core_types::transform::{Footprint, Transform}; use core_types::uuid::NodeId; -use core_types::{CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl}; +use core_types::{ATTR_ALPHA_BLENDING, ATTR_EDITOR_LAYER_PATH, ATTR_EDITOR_MERGED_LAYERS, ATTR_TRANSFORM, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl}; use glam::{DAffine2, DMat2, DVec2}; use graphic_types::Vector; use graphic_types::raster_types::{CPU, GPU, Raster}; @@ -41,7 +41,7 @@ impl VectorTableIterMut for Table { fn for_each_vector_mut(&mut self, mut f: impl FnMut(&mut Vector, DAffine2)) { for graphic in self.iter_element_values_mut() { let Some(vector_table) = graphic.as_vector_mut() else { continue }; - let (elements, transforms) = vector_table.element_and_attribute_slices_mut::("transform"); + let (elements, transforms) = vector_table.element_and_attribute_slices_mut::(ATTR_TRANSFORM); for (vector, transform) in elements.iter_mut().zip(transforms.iter()) { f(vector, *transform); } @@ -55,7 +55,7 @@ impl VectorTableIterMut for Table { impl VectorTableIterMut for Table { fn for_each_vector_mut(&mut self, mut f: impl FnMut(&mut Vector, DAffine2)) { - let (elements, transforms) = self.element_and_attribute_slices_mut::("transform"); + let (elements, transforms) = self.element_and_attribute_slices_mut::(ATTR_TRANSFORM); for (vector, transform) in elements.iter_mut().zip(transforms.iter()) { f(vector, *transform); } @@ -288,7 +288,7 @@ async fn copy_to_points( let do_scale = random_scale_difference.abs() > 1e-6; let do_rotation = random_rotation.abs() > 1e-6; - let points_transform: DAffine2 = row.attribute_cloned_or_default("transform"); + let points_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); for &point in row.element().point_domain.positions() { let translation = points_transform.transform_point2(point); @@ -317,8 +317,8 @@ async fn copy_to_points( for row_index in 0..content.len() { let Some(mut row) = content.clone_row(row_index) else { continue }; - let row_transform: DAffine2 = row.attribute_cloned_or_default("transform"); - row.set_attribute("transform", transform * row_transform); + let row_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); + row.set_attribute(ATTR_TRANSFORM, transform * row_transform); result_table.push(row); } @@ -349,7 +349,7 @@ async fn round_corners( ) -> Table { (0..source.len()) .map(|index| { - let source_transform: DAffine2 = source.attribute_cloned_or_default("transform", index); + let source_transform: DAffine2 = source.attribute_cloned_or_default(ATTR_TRANSFORM, index); let source_transform_inverse = source_transform.inverse(); let attributes = source.clone_row_attributes(index); let source = source.element(index).unwrap(); @@ -455,7 +455,7 @@ pub fn merge_by_distance( MergeByDistanceAlgorithm::Spatial => content .into_iter() .map(|mut row| { - let transform: DAffine2 = row.attribute_cloned_or_default("transform"); + let transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); row.element_mut().merge_by_distance_spatial(transform, distance); row }) @@ -678,12 +678,12 @@ async fn extrude(_: impl Ctx, mut source: Table, direction: DVec2, joini #[node_macro::node(category("Vector: Modifier"), path(core_types::vector))] async fn box_warp(_: impl Ctx, content: Table, #[expose] rectangle: Table) -> Table { let Some(target) = rectangle.element(0).cloned() else { return content }; - let target_transform: DAffine2 = rectangle.attribute_cloned_or_default("transform", 0); + let target_transform: DAffine2 = rectangle.attribute_cloned_or_default(ATTR_TRANSFORM, 0); content .into_iter() .map(|mut row| { - let transform: DAffine2 = row.attribute_cloned_or_default("transform"); + let transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); let vector = std::mem::take(row.element_mut()); // Get the bounding box of the source vector geometry @@ -743,7 +743,7 @@ async fn box_warp(_: impl Ctx, content: Table, #[expose] rectangle: Tabl // Add this to the `Table` and reset the transform since we've applied it directly to the points *row.element_mut() = result; - row.set_attribute("transform", DAffine2::IDENTITY); + row.set_attribute(ATTR_TRANSFORM, DAffine2::IDENTITY); row }) .collect() @@ -852,8 +852,8 @@ where RowsOrColumns::Rows => DVec2::new(strip.along_position, strip.cross_position), RowsOrColumns::Columns => DVec2::new(strip.cross_position, strip.along_position), }; - let row_transform: DAffine2 = row.attribute_cloned_or_default("transform"); - row.set_attribute("transform", DAffine2::from_translation(target_position - top_left) * row_transform); + let row_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); + row.set_attribute(ATTR_TRANSFORM, DAffine2::from_translation(target_position - top_left) * row_transform); strip.along_position += along + separation; } else { @@ -864,8 +864,8 @@ where RowsOrColumns::Rows => DVec2::new(0., new_cross), RowsOrColumns::Columns => DVec2::new(new_cross, 0.), }; - let row_transform: DAffine2 = row.attribute_cloned_or_default("transform"); - row.set_attribute("transform", DAffine2::from_translation(target_position - top_left) * row_transform); + let row_transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); + row.set_attribute(ATTR_TRANSFORM, DAffine2::from_translation(target_position - top_left) * row_transform); strips.push(Strip { along_position: along + separation, @@ -896,7 +896,7 @@ async fn auto_tangents( ) -> Table { (0..source.len()) .map(|index| { - let transform: DAffine2 = source.attribute_cloned_or_default("transform", index); + let transform: DAffine2 = source.attribute_cloned_or_default(ATTR_TRANSFORM, index); let attributes = source.clone_row_attributes(index); let source = source.element(index).unwrap(); @@ -1063,7 +1063,7 @@ async fn bounding_box(_: impl Ctx, content: Table) -> Table { #[node_macro::node(category("Vector: Measure"), path(core_types::vector))] async fn dimensions(_: impl Ctx, content: Table) -> DVec2 { (0..content.len()) - .filter_map(|index| content.element(index).unwrap().bounding_box_with_transform(content.attribute_cloned_or_default("transform", index))) + .filter_map(|index| content.element(index).unwrap().bounding_box_with_transform(content.attribute_cloned_or_default(ATTR_TRANSFORM, index))) .reduce(|[acc_top_left, acc_bottom_right], [top_left, bottom_right]| [acc_top_left.min(top_left), acc_bottom_right.max(bottom_right)]) .map(|[top_left, bottom_right]| bottom_right - top_left) .unwrap_or_default() @@ -1115,7 +1115,7 @@ async fn offset_path(_: impl Ctx, content: Table, distance: f64, join: S content .into_iter() .map(|mut row| { - let transform_attribute: DAffine2 = row.attribute_cloned_or_default("transform"); + let transform_attribute: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); let transform = Affine::new(transform_attribute.to_cols_array()); let vector = std::mem::take(row.element_mut()); @@ -1296,14 +1296,14 @@ pub async fn flatten_path(_: impl Ctx, #[implem // Concatenate every vector element's subpaths into the single output compound path for index in 0..flattened.len() { let Some(element) = flattened.element(index) else { continue }; - let layer_path: Table = flattened.attribute_cloned_or_default("editor:layer", index); + let layer_path: Table = flattened.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, index); let node_id = layer_path.iter_element_values().next_back().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(); - output.concat(element, flattened.attribute_cloned_or_default("transform", index), collision_hash_seed); + output.concat(element, flattened.attribute_cloned_or_default(ATTR_TRANSFORM, index), collision_hash_seed); // TODO: Make this instead use the first encountered style // Use the last encountered style as the output style @@ -1313,13 +1313,13 @@ pub async fn flatten_path(_: impl Ctx, #[implem // Preserve a reference to the original upstream `Table` so the renderer can recurse into it // when collecting metadata, exposing the original child layers' click targets to editor tools. // This is the same mechanism Boolean Operation uses to keep its inputs editable after the merge. - output_table.set_attribute("editor:merged_layers", 0, graphic_table); + output_table.set_attribute(ATTR_EDITOR_MERGED_LAYERS, 0, graphic_table); // Adopt the last input item's layer so the editor can also bucket clicks under a contributing child layer if !flattened.is_empty() { let primary = flattened.len() - 1; - let layer_path: Table = flattened.attribute_cloned_or_default("editor:layer", primary); - output_table.set_attribute("editor:layer", 0, layer_path); + let layer_path: Table = flattened.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, primary); + output_table.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer_path); } output_table @@ -1365,12 +1365,12 @@ async fn sample_polyline( style: std::mem::take(&mut row.element_mut().style), }; // Transfer the stroke transform from the input vector content to the result. - result.style.set_stroke_transform(row.attribute_cloned_or_default("transform")); + result.style.set_stroke_transform(row.attribute_cloned_or_default(ATTR_TRANSFORM)); for local_bezpath in row.element().stroke_bezpath_iter() { // Apply the transform to compute sample locations in world space (for correct distance-based spacing) let mut world_bezpath = local_bezpath.clone(); - let transform_attribute: DAffine2 = row.attribute_cloned_or_default("transform"); + let transform_attribute: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); world_bezpath.apply_affine(Affine::new(transform_attribute.to_cols_array())); // Per-segment perimeter lengths (transform-baked) for distance-based spacing @@ -1431,7 +1431,7 @@ async fn simplify( content .into_iter() .map(|mut row| { - let transform_attribute: DAffine2 = row.attribute_cloned_or_default("transform"); + let transform_attribute: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); let transform = Affine::new(transform_attribute.to_cols_array()); let inverse_transform = transform.inverse(); @@ -1527,7 +1527,7 @@ async fn decimate( content .into_iter() .map(|mut row| { - let transform_attribute: DAffine2 = row.attribute_cloned_or_default("transform"); + let transform_attribute: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); let transform = Affine::new(transform_attribute.to_cols_array()); let inverse_transform = transform.inverse(); @@ -1710,7 +1710,7 @@ async fn position_on_path( let mut bezpaths: Vec<_> = (0..content.len()) .flat_map(|index| { - let transform: DAffine2 = content.attribute_cloned_or_default("transform", index); + let transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM, index); content.element(index).unwrap().stroke_bezpath_iter().map(move |bezpath| (bezpath, transform)).collect::>() }) .collect(); @@ -1750,7 +1750,7 @@ async fn tangent_on_path( let mut bezpaths: Vec<_> = (0..content.len()) .flat_map(|index| { - let transform: DAffine2 = content.attribute_cloned_or_default("transform", index); + let transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM, index); content.element(index).unwrap().stroke_bezpath_iter().map(move |bezpath| (bezpath, transform)).collect::>() }) .collect(); @@ -1948,7 +1948,7 @@ async fn jitter_points( .into_iter() .map(|mut row| { let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); - let transform_attribute: DAffine2 = row.attribute_cloned_or_default("transform"); + let transform_attribute: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); let inverse_linear = inverse_linear_or_repair(transform_attribute.matrix2); let deltas: Vec<_> = (0..row.element().point_domain.positions().len()) @@ -1969,7 +1969,7 @@ async fn jitter_points( }) .collect(); - let transform: DAffine2 = row.attribute_cloned_or_default("transform"); + let transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); apply_point_deltas(row.element_mut(), &deltas, transform); row @@ -1992,7 +1992,7 @@ async fn offset_points( content .into_iter() .map(|mut row| { - let transform_attribute: DAffine2 = row.attribute_cloned_or_default("transform"); + let transform_attribute: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); let inverse_linear = inverse_linear_or_repair(transform_attribute.matrix2); let deltas: Vec<_> = (0..row.element().point_domain.positions().len()) @@ -2005,7 +2005,7 @@ async fn offset_points( }) .collect(); - let transform: DAffine2 = row.attribute_cloned_or_default("transform"); + let transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); apply_point_deltas(row.element_mut(), &deltas, transform); row @@ -2146,7 +2146,7 @@ async fn morph( let default_polyline = || { let mut default_path = BezPath::new(); for index in 0..content.len() { - let transform_attribute: DAffine2 = content.attribute_cloned_or_default("transform", index); + let transform_attribute: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM, index); let origin = transform_attribute.translation; let point = kurbo::Point::new(origin.x, origin.y); if index == 0 { @@ -2164,7 +2164,7 @@ async fn morph( // User-provided path: collect all subpaths with transforms applied let paths: Vec = (0..path.len()) .flat_map(|index| { - let transform: DAffine2 = path.attribute_cloned_or_default("transform", index); + let transform: DAffine2 = path.attribute_cloned_or_default(ATTR_TRANSFORM, index); path.element(index) .unwrap() .stroke_bezpath_iter() @@ -2238,8 +2238,8 @@ async fn morph( if content.element(source_index).is_none() || content.element(target_index).is_none() { return 0.; } - let source_transform: DAffine2 = content.attribute_cloned_or_default("transform", source_index); - let target_transform: DAffine2 = content.attribute_cloned_or_default("transform", target_index); + let source_transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM, source_index); + let target_transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM, target_index); let (s_angle, s_scale, s_skew) = source_transform.decompose_rotation_scale_skew(); let (t_angle, t_scale, t_skew) = target_transform.decompose_rotation_scale_skew(); @@ -2311,8 +2311,8 @@ async fn morph( }; // Lerp styles - let source_alpha_blending: AlphaBlending = content.attribute_cloned_or_default("alpha_blending", source_index); - let target_alpha_blending: AlphaBlending = content.attribute_cloned_or_default("alpha_blending", target_index); + let source_alpha_blending: AlphaBlending = content.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, source_index); + let target_alpha_blending: AlphaBlending = content.attribute_cloned_or_default(ATTR_ALPHA_BLENDING, target_index); let vector_alpha_blending = source_alpha_blending.lerp(&target_alpha_blending, time as f32); // Evaluate the spatial position on the control path for the translation component. @@ -2330,8 +2330,8 @@ async fn morph( // This decomposition must match the one used in Stroke::lerp so the renderer's stroke_transform.inverse() // correctly cancels the element transform, keeping the stroke uniform when Stroke is after Transform. let lerped_transform = { - let source_transform: DAffine2 = content.attribute_cloned_or_default("transform", source_index); - let target_transform: DAffine2 = content.attribute_cloned_or_default("transform", target_index); + let source_transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM, source_index); + let target_transform: DAffine2 = content.attribute_cloned_or_default(ATTR_TRANSFORM, target_index); let (s_angle, s_scale, s_skew) = source_transform.decompose_rotation_scale_skew(); let (t_angle, t_scale, t_skew) = target_transform.decompose_rotation_scale_skew(); @@ -2360,7 +2360,7 @@ async fn morph( // in which case we skip pre-compensation to avoid propagating NaN through merged_layers transforms. if lerped_transform.matrix2.determinant().abs() > f64::EPSILON { let lerped_inverse = lerped_transform.inverse(); - for transform in graphic_table_content.iter_attribute_values_mut_or_default::("transform") { + for transform in graphic_table_content.iter_attribute_values_mut_or_default::(ATTR_TRANSFORM) { *transform = lerped_inverse * *transform; } } @@ -2372,8 +2372,8 @@ async fn morph( let endpoint_element = content.element(endpoint_index).unwrap(); let mut attributes = content.clone_row_attributes(endpoint_index); - attributes.insert("transform", lerped_transform); - attributes.insert("editor:merged_layers", graphic_table_content); + attributes.insert(ATTR_TRANSFORM, lerped_transform); + attributes.insert(ATTR_EDITOR_MERGED_LAYERS, graphic_table_content); return Table::new_from_row(TableRow::from_parts(endpoint_element.clone(), attributes)); } @@ -2529,14 +2529,14 @@ async fn morph( // The result is a synthesis of source and target, so adopt whichever endpoint the result is closer to as // the click-target identity (so the editor can route clicks back to one of the contributing layers) let primary_index = if time < 0.5 { source_index } else { target_index }; - let layer_path: Table = content.attribute_cloned_or_default("editor:layer", primary_index); + let layer_path: Table = content.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH, primary_index); Table::new_from_row( TableRow::new_from_element(vector) - .with_attribute("transform", lerped_transform) - .with_attribute("alpha_blending", vector_alpha_blending) - .with_attribute("editor:layer", layer_path) - .with_attribute("editor:merged_layers", graphic_table_content), + .with_attribute(ATTR_TRANSFORM, lerped_transform) + .with_attribute(ATTR_ALPHA_BLENDING, vector_alpha_blending) + .with_attribute(ATTR_EDITOR_LAYER_PATH, layer_path) + .with_attribute(ATTR_EDITOR_MERGED_LAYERS, graphic_table_content), ) } @@ -2815,7 +2815,7 @@ fn bevel(_: impl Ctx, source: Table, #[default(10.)] distance: Length) - source .into_iter() .map(|row| { - let transform: DAffine2 = row.attribute_cloned_or_default("transform"); + let transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); let (element, attributes) = row.into_parts(); TableRow::from_parts(bevel_algorithm(element, transform, distance), attributes) @@ -2837,7 +2837,7 @@ fn close_path(_: impl Ctx, source: Table) -> Table { #[node_macro::node(category("Vector: Measure"), path(core_types::vector))] fn point_inside(_: impl Ctx, source: Table, point: DVec2) -> bool { source.into_iter().any(|row| { - let transform: DAffine2 = row.attribute_cloned_or_default("transform"); + let transform: DAffine2 = row.attribute_cloned_or_default(ATTR_TRANSFORM); row.element().check_point_inside_shape(transform, point) }) } @@ -2918,7 +2918,7 @@ async fn index_points( async fn path_length(_: impl Ctx, source: Table) -> f64 { (0..source.len()) .map(|index| { - let transform: DAffine2 = source.attribute_cloned_or_default("transform", index); + let transform: DAffine2 = source.attribute_cloned_or_default(ATTR_TRANSFORM, index); source .element(index) @@ -2940,7 +2940,7 @@ async fn area(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: impl Node() }) @@ -2969,7 +2969,7 @@ async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: impl Node< CentroidType::Length => subpath.length_centroid_and_length(None, true), }; if let Some((subpath_centroid, area_or_length)) = partial { - let transform: DAffine2 = vector.attribute_cloned_or_default("transform", index); + let transform: DAffine2 = vector.attribute_cloned_or_default(ATTR_TRANSFORM, index); let subpath_centroid = transform.transform_point2(subpath_centroid); sum += area_or_length; @@ -2987,7 +2987,7 @@ async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: impl Node< let summed_positions = (0..vector.len()) .flat_map(|index| { - let transform: DAffine2 = vector.attribute_cloned_or_default("transform", index); + let transform: DAffine2 = vector.attribute_cloned_or_default(ATTR_TRANSFORM, index); vector .element(index) .unwrap() @@ -3032,7 +3032,7 @@ mod test { fn create_vector_row(bezpath: BezPath, transform: DAffine2) -> TableRow { let mut row = Vector::default(); row.append_bezpath(bezpath); - TableRow::new_from_element(row).with_attribute("transform", transform) + TableRow::new_from_element(row).with_attribute(ATTR_TRANSFORM, transform) } #[tokio::test] @@ -3054,7 +3054,7 @@ mod test { // Test a rectangular path with non-zero rotation let square = Vector::from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY)); let mut square = Table::new_from_element(square); - square.with_attribute_mut_or_default("transform", 0, |t: &mut DAffine2| *t *= DAffine2::from_angle(std::f64::consts::FRAC_PI_4)); + square.with_attribute_mut_or_default(ATTR_TRANSFORM, 0, |t: &mut DAffine2| *t *= DAffine2::from_angle(std::f64::consts::FRAC_PI_4)); let bounding_box = BoundingBoxNode { content: FutureWrapperNode(square) }.eval(Footprint::default()).await; let bounding_box = bounding_box.element(0).unwrap(); assert_eq!(bounding_box.region_manipulator_groups().count(), 1); @@ -3157,7 +3157,7 @@ mod test { async fn morph() { let mut rectangles = vector_node_from_bezpath(Rect::new(0., 0., 100., 100.).to_path(DEFAULT_ACCURACY)); let mut second_rectangle = rectangles.clone_row(0).unwrap(); - *second_rectangle.attribute_mut_or_insert_default::("transform") *= DAffine2::from_translation((-100., -100.).into()); + *second_rectangle.attribute_mut_or_insert_default::(ATTR_TRANSFORM) *= DAffine2::from_translation((-100., -100.).into()); rectangles.push(second_rectangle); let morphed = super::morph(Footprint::default(), rectangles, 0.5, false, InterpolationDistribution::default(), Table::default()).await; @@ -3168,7 +3168,7 @@ mod test { vec![DVec2::new(0., 0.), DVec2::new(100., 0.), DVec2::new(100., 100.), DVec2::new(0., 100.)] ); // The interpolated transform carries the midpoint translation (approximate due to arc-length parameterization) - assert!((morphed.attribute_cloned_or_default::("transform", 0).translation - DVec2::new(-50., -50.)).length() < 1e-3); + assert!((morphed.attribute_cloned_or_default::(ATTR_TRANSFORM, 0).translation - DVec2::new(-50., -50.)).length() < 1e-3); } #[track_caller] @@ -3244,7 +3244,7 @@ mod test { let vector = Vector::from_bezpath(source); let mut vector_table = Table::new_from_element(vector.clone()); - vector_table.set_attribute("transform", 0, DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.))); + vector_table.set_attribute(ATTR_TRANSFORM, 0, DAffine2::from_scale_angle_translation(DVec2::splat(10.), 1., DVec2::new(99., 77.))); let beveled = super::bevel((), Table::new_from_element(vector), 2_f64.sqrt() * 10.); let beveled = beveled.element(0).unwrap();