diff --git a/Cargo.lock b/Cargo.lock index ed8ade561..f72f6faef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2133,10 +2133,16 @@ dependencies = [ "graphene-application-io", "graphene-brush", "graphene-core", + "graphene-element", "graphene-element-nodes", + "graphene-math-nodes", "graphene-path-bool", + "graphene-raster", "graphene-raster-nodes", "graphene-svg-renderer", + "graphene-text", + "graphene-vector", + "graphene-vector-nodes", "iai-callgrind", "js-sys", "log", @@ -2161,6 +2167,7 @@ dependencies = [ "dyn-any", "glam", "graphene-core", + "graphene-text", "log", "serde", "web-sys", @@ -2174,9 +2181,15 @@ dependencies = [ "dyn-any", "glam", "graphene-core", + "graphene-element", + "graphene-raster", "graphene-raster-nodes", + "graphene-svg-renderer", + "graphene-vector", "node-macro", "serde", + "serde_json", + "specta", "tokio", ] @@ -2189,7 +2202,6 @@ dependencies = [ "fern", "futures", "graph-craft", - "graphene-core", "graphene-std", "interpreted-executor", "log", @@ -2204,14 +2216,12 @@ name = "graphene-core" version = "0.1.0" dependencies = [ "base64 0.22.1", - "bezier-rs", "bytemuck", "ctor", "dyn-any", "glam", "half", "image", - "kurbo", "log", "node-macro", "num-derive", @@ -2229,6 +2239,22 @@ dependencies = [ "wgpu", ] +[[package]] +name = "graphene-element" +version = "0.1.0" +dependencies = [ + "bezier-rs", + "dyn-any", + "glam", + "graphene-core", + "graphene-raster", + "graphene-vector", + "node-macro", + "serde", + "serde_json", + "specta", +] + [[package]] name = "graphene-element-nodes" version = "0.1.0" @@ -2237,7 +2263,10 @@ dependencies = [ "dyn-any", "glam", "graphene-core", + "graphene-element", "graphene-math-nodes", + "graphene-raster", + "graphene-vector", "log", "node-macro", "rand 0.9.0", @@ -2266,6 +2295,8 @@ dependencies = [ "dyn-any", "glam", "graphene-core", + "graphene-element", + "graphene-vector", "log", "node-macro", "path-bool", @@ -2273,6 +2304,23 @@ dependencies = [ "specta", ] +[[package]] +name = "graphene-raster" +version = "0.1.0" +dependencies = [ + "base64 0.22.1", + "bytemuck", + "dyn-any", + "glam", + "graphene-core", + "image", + "node-macro", + "serde", + "serde_json", + "specta", + "wgpu", +] + [[package]] name = "graphene-raster-nodes" version = "0.1.0" @@ -2284,6 +2332,7 @@ dependencies = [ "futures", "glam", "graphene-core", + "graphene-raster", "image", "ndarray", "node-macro", @@ -2308,14 +2357,18 @@ dependencies = [ "graphene-application-io", "graphene-brush", "graphene-core", + "graphene-element", "graphene-element-nodes", "graphene-math-nodes", "graphene-path-bool", + "graphene-raster", "graphene-raster-nodes", "graphene-svg-renderer", + "graphene-text", + "graphene-vector", + "graphene-vector-nodes", "image", "log", - "ndarray", "node-macro", "rand 0.9.0", "rand_chacha 0.9.0", @@ -2337,13 +2390,73 @@ dependencies = [ "dyn-any", "glam", "graphene-core", + "graphene-element", + "graphene-raster", + "graphene-vector", "log", + "node-macro", "num-traits", "serde", + "serde_json", + "specta", "usvg", "vello", ] +[[package]] +name = "graphene-text" +version = "0.1.0" +dependencies = [ + "bezier-rs", + "dyn-any", + "glam", + "graphene-core", + "graphene-vector", + "kurbo", + "node-macro", + "rustybuzz 0.20.1", + "serde", + "specta", +] + +[[package]] +name = "graphene-vector" +version = "0.1.0" +dependencies = [ + "bezier-rs", + "dyn-any", + "glam", + "graphene-core", + "kurbo", + "log", + "node-macro", + "petgraph 0.7.1", + "rustc-hash 2.1.1", + "serde", + "specta", + "tinyvec", +] + +[[package]] +name = "graphene-vector-nodes" +version = "0.1.0" +dependencies = [ + "bezier-rs", + "dyn-any", + "glam", + "graphene-core", + "graphene-vector", + "kurbo", + "log", + "node-macro", + "petgraph 0.7.1", + "rand 0.9.0", + "rustc-hash 2.1.1", + "serde", + "specta", + "tokio", +] + [[package]] name = "graphite-desktop" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 0fc9eef1d..2b8a6d01e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,14 +7,22 @@ members = [ "node-graph/gapplication-io", "node-graph/gbrush", "node-graph/gcore", - "node-graph/gstd", + "node-graph/gelement", + "node-graph/gelement-nodes", + "node-graph/gmath-nodes", + "node-graph/gpath-bool", "node-graph/gelement-nodes", "node-graph/gmath-nodes", "node-graph/gpath-bool", "node-graph/graph-craft", "node-graph/graphene-cli", + "node-graph/graster", "node-graph/graster-nodes", + "node-graph/gstd", "node-graph/gsvg-renderer", + "node-graph/gtext", + "node-graph/gvector", + "node-graph/gvector-nodes", "node-graph/interpreted-executor", "node-graph/node-macro", "node-graph/preprocessor", @@ -29,14 +37,22 @@ default-members = [ "frontend/wasm", "node-graph/gbrush", "node-graph/gcore", - "node-graph/gstd", + "node-graph/gelement", + "node-graph/gelement-nodes", + "node-graph/gmath-nodes", + "node-graph/gpath-bool", "node-graph/gelement-nodes", "node-graph/gmath-nodes", "node-graph/gpath-bool", "node-graph/graph-craft", "node-graph/graphene-cli", + "node-graph/graster", "node-graph/graster-nodes", + "node-graph/gstd", "node-graph/gsvg-renderer", + "node-graph/gtext", + "node-graph/gvector", + "node-graph/gvector-nodes", "node-graph/interpreted-executor", "node-graph/node-macro", ] @@ -52,13 +68,18 @@ path-bool = { path = "libraries/path-bool" } graphene-application-io = { path = "node-graph/gapplication-io" } graphene-brush = { path = "node-graph/gbrush" } graphene-core = { path = "node-graph/gcore" } +graphene-element = { path = "node-graph/gelement" } graphene-element-nodes = { path = "node-graph/gelement-nodes" } graphene-math-nodes = { path = "node-graph/gmath-nodes" } graphene-path-bool = { path = "node-graph/gpath-bool" } graph-craft = { path = "node-graph/graph-craft" } +graphene-raster = { path = "node-graph/graster" } graphene-raster-nodes = { path = "node-graph/graster-nodes" } graphene-std = { path = "node-graph/gstd" } graphene-svg-renderer = { path = "node-graph/gsvg-renderer" } +graphene-text = { path = "node-graph/gtext" } +graphene-vector = { path = "node-graph/gvector" } +graphene-vector-nodes = { path = "node-graph/gvector-nodes" } interpreted-executor = { path = "node-graph/interpreted-executor" } node-macro = { path = "node-graph/node-macro" } wgpu-executor = { path = "node-graph/wgpu-executor" } diff --git a/node-graph/gapplication-io/Cargo.toml b/node-graph/gapplication-io/Cargo.toml index 98051ce63..64a26be44 100644 --- a/node-graph/gapplication-io/Cargo.toml +++ b/node-graph/gapplication-io/Cargo.toml @@ -14,6 +14,7 @@ wgpu = ["dep:wgpu"] # Local dependencies dyn-any = { workspace = true } graphene-core = { workspace = true } +graphene-text = { workspace = true } # Workspace dependencies glam = { workspace = true } diff --git a/node-graph/gapplication-io/src/lib.rs b/node-graph/gapplication-io/src/lib.rs index d0fd84c6d..fe4dbcbf5 100644 --- a/node-graph/gapplication-io/src/lib.rs +++ b/node-graph/gapplication-io/src/lib.rs @@ -1,8 +1,8 @@ use dyn_any::{DynAny, StaticType, StaticTypeSized}; use glam::{DAffine2, UVec2}; -use graphene_core::text::FontCache; use graphene_core::transform::Footprint; -use graphene_core::vector::style::ViewMode; +use graphene_core::view_mode::ViewMode; +use graphene_text::FontCache; use std::fmt::Debug; use std::future::Future; use std::hash::{Hash, Hasher}; diff --git a/node-graph/gbrush/Cargo.toml b/node-graph/gbrush/Cargo.toml index 7b2f8b340..bec9599b9 100644 --- a/node-graph/gbrush/Cargo.toml +++ b/node-graph/gbrush/Cargo.toml @@ -14,11 +14,16 @@ serde = ["dep:serde"] # Local dependencies dyn-any = { workspace = true } graphene-core = { workspace = true } +graphene-vector = { workspace = true } +graphene-raster = { workspace = true } +graphene-element = { workspace = true } graphene-raster-nodes = { workspace = true } +graphene-svg-renderer = { workspace = true } node-macro = { workspace = true } # Workspace dependencies glam = { workspace = true } +specta = { workspace = true } # Optional workspace dependencies serde = { workspace = true, optional = true, features = ["derive"] } @@ -26,3 +31,4 @@ serde = { workspace = true, optional = true, features = ["derive"] } [dev-dependencies] # Workspace dependencies tokio = { workspace = true } +serde_json = { workspace = true } diff --git a/node-graph/gbrush/src/brush.rs b/node-graph/gbrush/src/brush.rs index 0e32aa0b9..9dc2de28b 100644 --- a/node-graph/gbrush/src/brush.rs +++ b/node-graph/gbrush/src/brush.rs @@ -1,21 +1,22 @@ use crate::brush_cache::BrushCache; use crate::brush_stroke::{BrushStroke, BrushStyle}; use glam::{DAffine2, DVec2}; +use graphene_core::Node; use graphene_core::blending::BlendMode; use graphene_core::bounds::BoundingBox; use graphene_core::color::{Alpha, Color, Pixel, Sample}; use graphene_core::generic::FnNode; use graphene_core::instances::Instance; use graphene_core::math::bbox::{AxisAlignedBbox, Bbox}; -use graphene_core::raster::BitmapMut; -use graphene_core::raster::image::Image; -use graphene_core::raster_types::{CPU, Raster, RasterDataTable}; use graphene_core::registry::FutureWrapperNode; use graphene_core::transform::Transform; use graphene_core::value::ClonedNode; -use graphene_core::{Ctx, Node}; +use graphene_raster::bitmap::BitmapMut; +use graphene_raster::image::Image; +use graphene_raster::{CPU, Raster, RasterDataTable}; use graphene_raster_nodes::adjustments::blend_colors; use graphene_raster_nodes::std_nodes::{empty_image, extend_image_to_bounds}; +use graphene_svg_renderer::renderer::GraphicElementRendered; #[derive(Clone, Copy, Debug, PartialEq)] pub struct BrushStampGenerator { diff --git a/node-graph/gcore/Cargo.toml b/node-graph/gcore/Cargo.toml index 9d7a9dde3..37f684116 100644 --- a/node-graph/gcore/Cargo.toml +++ b/node-graph/gcore/Cargo.toml @@ -27,13 +27,11 @@ rustc-hash = { workspace = true } dyn-any = { workspace = true } ctor = { workspace = true } rand_chacha = { workspace = true } -bezier-rs = { workspace = true } specta = { workspace = true } rustybuzz = { workspace = true } image = { workspace = true } half = { workspace = true } tinyvec = { workspace = true } -kurbo = { workspace = true } log = { workspace = true } base64 = { workspace = true } diff --git a/node-graph/gcore/src/blending.rs b/node-graph/gcore/src/blending.rs index 921fb0d0a..411d3d0ed 100644 --- a/node-graph/gcore/src/blending.rs +++ b/node-graph/gcore/src/blending.rs @@ -1,4 +1,5 @@ use dyn_any::DynAny; +use log::warn; use std::hash::Hash; #[derive(Copy, Clone, Debug, PartialEq, DynAny, specta::Type, serde::Serialize, serde::Deserialize)] diff --git a/node-graph/gcore/src/bounds.rs b/node-graph/gcore/src/bounds.rs index ccc373d4c..8f00be3a2 100644 --- a/node-graph/gcore/src/bounds.rs +++ b/node-graph/gcore/src/bounds.rs @@ -1,4 +1,4 @@ -use crate::Color; +use crate::color::Color; use glam::{DAffine2, DVec2}; pub trait BoundingBox { diff --git a/node-graph/gcore/src/color/color_traits.rs b/node-graph/gcore/src/color/color_traits.rs index b8ffa41f3..54a3d33ab 100644 --- a/node-graph/gcore/src/color/color_traits.rs +++ b/node-graph/gcore/src/color/color_traits.rs @@ -5,8 +5,6 @@ use std::fmt::Debug; #[cfg(target_arch = "spirv")] use spirv_std::num_traits::float::Float; -pub use crate::blending::*; - pub trait Linear { fn from_f32(x: f32) -> Self; fn to_f32(self) -> f32; diff --git a/node-graph/gcore/src/consts.rs b/node-graph/gcore/src/consts.rs index 505dc81cc..a5706f47c 100644 --- a/node-graph/gcore/src/consts.rs +++ b/node-graph/gcore/src/consts.rs @@ -1,4 +1,4 @@ -use crate::raster::Color; +use crate::color::Color; // RENDERING pub const LAYER_OUTLINE_STROKE_COLOR: Color = Color::BLACK; diff --git a/node-graph/gcore/src/debug.rs b/node-graph/gcore/src/debug.rs index b88ed1c8e..42f8c9687 100644 --- a/node-graph/gcore/src/debug.rs +++ b/node-graph/gcore/src/debug.rs @@ -1,5 +1,5 @@ -use crate::raster_types::{CPU, RasterDataTable}; -use crate::{Color, Ctx}; +use crate::color::Color; +use crate::context::Ctx; /// Meant for debugging purposes, not general use. Returns the size of the input type in bytes. #[node_macro::node(category("Debug"))] @@ -19,8 +19,4 @@ fn unwrap(_: impl Ctx, #[implementations(Option, Option, O input.unwrap_or_default() } -/// Meant for debugging purposes, not general use. Clones the input value. -#[node_macro::node(category("Debug"))] -fn clone<'i, T: Clone + 'i>(_: impl Ctx, #[implementations(&RasterDataTable)] value: &'i T) -> T { - value.clone() -} +// FIXME am I allowed to just remove clone? diff --git a/node-graph/gcore/src/extract_xy.rs b/node-graph/gcore/src/extract_xy.rs index 41e084e01..61c8d9b71 100644 --- a/node-graph/gcore/src/extract_xy.rs +++ b/node-graph/gcore/src/extract_xy.rs @@ -1,4 +1,4 @@ -use crate::Ctx; +use crate::context::Ctx; use dyn_any::DynAny; use glam::{DVec2, IVec2, UVec2}; diff --git a/node-graph/gcore/src/gradient.rs b/node-graph/gcore/src/gradient.rs index 8034cc987..3860a97f0 100644 --- a/node-graph/gcore/src/gradient.rs +++ b/node-graph/gcore/src/gradient.rs @@ -1,4 +1,4 @@ -use crate::Color; +use crate::color::Color; use dyn_any::DynAny; use glam::{DAffine2, DVec2}; diff --git a/node-graph/gcore/src/instances.rs b/node-graph/gcore/src/instances.rs index 5b5be2a31..096d637ef 100644 --- a/node-graph/gcore/src/instances.rs +++ b/node-graph/gcore/src/instances.rs @@ -1,4 +1,4 @@ -use crate::AlphaBlending; +use crate::blending::AlphaBlending; use crate::uuid::NodeId; use dyn_any::StaticType; use glam::DAffine2; diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index 78d659bf4..20d0ced2f 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -1,6 +1,3 @@ -#[macro_use] -extern crate log; - pub mod blending; pub mod bounds; pub mod color; @@ -10,35 +7,26 @@ pub mod debug; pub mod extract_xy; pub mod generic; pub mod gradient; -mod graphic_element; pub mod instances; pub mod math; pub mod memo; pub mod misc; pub mod ops; -pub mod raster; -pub mod raster_types; pub mod registry; pub mod structural; -pub mod text; pub mod transform; pub mod uuid; pub mod value; -pub mod vector; pub use crate as graphene_core; -pub use blending::*; -pub use context::*; pub use ctor; pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync}; -pub use graphic_element::*; pub use memo::MemoHash; pub use num_traits; -pub use raster::Color; use std::any::TypeId; +pub use std::borrow::Cow; use std::future::Future; use std::pin::Pin; -pub use types::Cow; // pub trait Node: for<'n> NodeIO<'n> { /// The node trait allows for defining any node. Nodes can only take one call argument input, however they can store references to other nodes inside the struct. diff --git a/node-graph/gcore/src/math/mod.rs b/node-graph/gcore/src/math/mod.rs index 06a1c21bc..a1fb18fb0 100644 --- a/node-graph/gcore/src/math/mod.rs +++ b/node-graph/gcore/src/math/mod.rs @@ -1,4 +1,3 @@ pub mod bbox; -pub mod math_ext; pub mod quad; pub mod rect; diff --git a/node-graph/gcore/src/transform.rs b/node-graph/gcore/src/transform.rs index dafd3791b..33773cc0a 100644 --- a/node-graph/gcore/src/transform.rs +++ b/node-graph/gcore/src/transform.rs @@ -1,7 +1,3 @@ -use crate::Artboard; -use crate::math::bbox::AxisAlignedBbox; -pub use crate::vector::ReferencePoint; -use core::f64; use glam::{DAffine2, DMat2, DVec2}; pub trait Transform { @@ -31,16 +27,6 @@ impl Transform for &T { } } -// Implementations for Artboard -impl Transform for Artboard { - fn transform(&self) -> DAffine2 { - DAffine2::from_translation(self.location.as_dvec2()) - } - fn local_pivot(&self, pivot: DVec2) -> DVec2 { - self.location.as_dvec2() + self.dimensions.as_dvec2() * pivot - } -} - // Implementations for DAffine2 impl Transform for DAffine2 { fn transform(&self) -> DAffine2 { @@ -110,13 +96,6 @@ impl Footprint { quality: RenderQuality::Full, }; - pub fn viewport_bounds_in_local_space(&self) -> AxisAlignedBbox { - let inverse = self.transform.inverse(); - let start = inverse.transform_point2((0., 0.).into()); - let end = inverse.transform_point2(self.resolution.as_dvec2()); - AxisAlignedBbox { start, end } - } - pub fn scale(&self) -> DVec2 { self.transform.decompose_scale() } diff --git a/node-graph/gcore/src/vector/mod.rs b/node-graph/gcore/src/vector/mod.rs deleted file mode 100644 index 75bcc3b7d..000000000 --- a/node-graph/gcore/src/vector/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub mod algorithms; -pub mod click_target; -pub mod generator_nodes; -pub mod misc; -mod reference_point; -pub mod style; -mod vector_data; -mod vector_nodes; - -pub use bezier_rs; -pub use reference_point::*; -pub use style::PathStyle; -pub use vector_data::*; -pub use vector_nodes::*; diff --git a/node-graph/gelement-nodes/Cargo.toml b/node-graph/gelement-nodes/Cargo.toml index e00195ec9..6639d387b 100644 --- a/node-graph/gelement-nodes/Cargo.toml +++ b/node-graph/gelement-nodes/Cargo.toml @@ -14,6 +14,9 @@ serde = ["dep:serde"] # Local dependencies dyn-any = { workspace = true } graphene-core = { workspace = true } +graphene-raster = { workspace = true } +graphene-vector = { workspace = true } +graphene-element = { workspace = true } node-macro = { workspace = true } bezier-rs = { workspace = true } diff --git a/node-graph/gelement-nodes/src/blending_nodes.rs b/node-graph/gelement-nodes/src/blending_nodes.rs index d74d86cbf..4dbed2ed1 100644 --- a/node-graph/gelement-nodes/src/blending_nodes.rs +++ b/node-graph/gelement-nodes/src/blending_nodes.rs @@ -1,10 +1,10 @@ -use graphene_core::GraphicGroupTable; use graphene_core::blending::BlendMode; use graphene_core::color::Color; use graphene_core::context::Ctx; -use graphene_core::raster_types::{CPU, RasterDataTable}; use graphene_core::registry::types::Percentage; -use graphene_core::vector::VectorDataTable; +use graphene_element::GraphicGroupTable; +use graphene_raster::{CPU, RasterDataTable}; +use graphene_vector::VectorDataTable; pub(super) trait MultiplyAlpha { fn multiply_alpha(&mut self, factor: f64); diff --git a/node-graph/gelement-nodes/src/instance.rs b/node-graph/gelement-nodes/src/instance.rs index c23a1d358..3d474e9a3 100644 --- a/node-graph/gelement-nodes/src/instance.rs +++ b/node-graph/gelement-nodes/src/instance.rs @@ -1,10 +1,11 @@ use glam::DVec2; -use graphene_core::GraphicElement; -use graphene_core::GraphicGroupTable; +use graphene_core::color::Color; use graphene_core::context::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, OwnedContextImpl}; use graphene_core::instances::{InstanceRef, Instances}; -use graphene_core::raster_types::{CPU, RasterDataTable}; -use graphene_core::vector::VectorDataTable; +use graphene_element::GraphicElement; +use graphene_element::GraphicGroupTable; +use graphene_raster::{CPU, GPU, RasterDataTable}; +use graphene_vector::VectorDataTable; use log::warn; #[node_macro::node(name("Instance on Points"), category("Instancing"), path(graphene_core::vector))] @@ -105,7 +106,7 @@ mod test { use glam::DVec2; use graphene_core::Node; use graphene_core::extract_xy::{ExtractXyNode, XY}; - use graphene_core::vector::VectorData; + use graphene_vector::VectorData; use std::pin::Pin; #[derive(Clone)] diff --git a/node-graph/gelement-nodes/src/lib.rs b/node-graph/gelement-nodes/src/lib.rs index e45e23ca1..c094f9851 100644 --- a/node-graph/gelement-nodes/src/lib.rs +++ b/node-graph/gelement-nodes/src/lib.rs @@ -4,3 +4,4 @@ pub mod conversion; pub mod instance; pub mod logic; pub mod transform_nodes; +pub mod vector_element_nodes; diff --git a/node-graph/gelement-nodes/src/logic.rs b/node-graph/gelement-nodes/src/logic.rs index e9b564ff1..abf3258b7 100644 --- a/node-graph/gelement-nodes/src/logic.rs +++ b/node-graph/gelement-nodes/src/logic.rs @@ -1,7 +1,7 @@ use glam::{DAffine2, DVec2}; use graphene_core::color::Color; use graphene_core::context::{Context, Ctx}; -use graphene_core::vector::VectorDataTable; +use graphene_vector::VectorDataTable; #[node_macro::node(category("Debug"), name("Log to Console"))] fn log_to_console(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2, Color, Option)] value: T) -> T { diff --git a/node-graph/gelement-nodes/src/transform_nodes.rs b/node-graph/gelement-nodes/src/transform_nodes.rs index f4e7ef6d8..a6ae730f7 100644 --- a/node-graph/gelement-nodes/src/transform_nodes.rs +++ b/node-graph/gelement-nodes/src/transform_nodes.rs @@ -1,11 +1,11 @@ use core::f64; use glam::{DAffine2, DVec2}; -use graphene_core::GraphicGroupTable; use graphene_core::context::{CloneVarArgs, Context, Ctx, ExtractAll, OwnedContextImpl}; use graphene_core::instances::Instances; -use graphene_core::raster_types::{CPU, GPU, RasterDataTable}; use graphene_core::transform::{ApplyTransform, Footprint, Transform}; -use graphene_core::vector::VectorDataTable; +use graphene_element::GraphicGroupTable; +use graphene_raster::{CPU, GPU, RasterDataTable}; +use graphene_vector::VectorDataTable; #[node_macro::node(category(""))] async fn transform( diff --git a/node-graph/gelement-nodes/src/vector_element_nodes.rs b/node-graph/gelement-nodes/src/vector_element_nodes.rs new file mode 100644 index 000000000..daa4307f0 --- /dev/null +++ b/node-graph/gelement-nodes/src/vector_element_nodes.rs @@ -0,0 +1,538 @@ +use glam::{DAffine2, DVec2}; +use graphene_core::color::Color; +use graphene_core::context::Ctx; +use graphene_core::gradient::{Gradient, GradientStops}; +use graphene_core::instances::{InstanceMut, Instances}; +use graphene_core::registry::types::{Angle, IntegerCount, Multiplier, PixelSize, SeedValue}; +use graphene_element::{GraphicElement, GraphicGroupTable}; +use graphene_raster::{CPU, GPU, RasterDataTable}; +use graphene_vector::reference_point::ReferencePoint; +use graphene_vector::style::{Fill, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin}; +use graphene_vector::{VectorData, VectorDataTable}; +use rand::{Rng, SeedableRng}; +use std::f64::consts::TAU; +use std::hash::{DefaultHasher, Hash, Hasher}; + +/// Implemented for types that can be converted to an iterator of vector data. +/// Used for the fill and stroke node so they can be used on VectorData or GraphicGroup +trait VectorDataTableIterMut { + fn vector_iter_mut(&mut self) -> impl Iterator>; +} + +impl VectorDataTableIterMut for GraphicGroupTable { + fn vector_iter_mut(&mut self) -> impl Iterator> { + // Grab only the direct children + self.instance_mut_iter() + .filter_map(|element| element.instance.as_vector_data_mut()) + .flat_map(move |vector_data| vector_data.instance_mut_iter()) + } +} + +impl VectorDataTableIterMut for VectorDataTable { + fn vector_iter_mut(&mut self) -> impl Iterator> { + self.instance_mut_iter() + } +} + +#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))] +async fn assign_colors( + _: impl Ctx, + #[implementations(GraphicGroupTable, VectorDataTable)] + #[widget(ParsedWidgetOverride::Hidden)] + /// The vector elements, or group of vector elements, to apply the fill and/or stroke style to. + mut vector_group: T, + #[default(true)] + /// Whether to style the fill. + fill: bool, + /// Whether to style the stroke. + stroke: bool, + #[widget(ParsedWidgetOverride::Custom = "assign_colors_gradient")] + /// The range of colors to select from. + gradient: GradientStops, + /// Whether to reverse the gradient. + reverse: bool, + /// Whether to randomize the color selection for each element from throughout the gradient. + randomize: bool, + #[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")] + /// The seed used for randomization. + seed: SeedValue, + #[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")] + /// The number of elements to span across the gradient before repeating. A 0 value will span the entire gradient once. + repeat_every: u32, +) -> T +where + T: VectorDataTableIterMut + 'n + Send, +{ + let length = vector_group.vector_iter_mut().count(); + let gradient = if reverse { gradient.reversed() } else { gradient }; + + let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); + + for (i, vector_data) in vector_group.vector_iter_mut().enumerate() { + let factor = match randomize { + true => rng.random::(), + false => match repeat_every { + 0 => i as f64 / (length - 1).max(1) as f64, + 1 => 0., + _ => i as f64 % repeat_every as f64 / (repeat_every - 1) as f64, + }, + }; + + let color = gradient.evaluate(factor); + + if fill { + vector_data.instance.style.set_fill(Fill::Solid(color)); + } + if stroke { + if let Some(stroke) = vector_data.instance.style.stroke().and_then(|stroke| stroke.with_color(&Some(color))) { + vector_data.instance.style.set_stroke(stroke); + } + } + } + + vector_group +} + +#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))] +async fn fill + 'n + Send, V>( + _: impl Ctx, + #[implementations( + VectorDataTable, + VectorDataTable, + VectorDataTable, + VectorDataTable, + GraphicGroupTable, + GraphicGroupTable, + GraphicGroupTable, + GraphicGroupTable + )] + /// The vector elements, or group of vector elements, to apply the fill to. + mut vector_data: V, + #[implementations( + Fill, + Option, + Color, + Gradient, + Fill, + Option, + Color, + Gradient, + )] + #[default(Color::BLACK)] + /// The fill to paint the path with. + fill: F, + _backup_color: Option, + _backup_gradient: Gradient, +) -> V +where + V: VectorDataTableIterMut + 'n + Send, +{ + let fill: Fill = fill.into(); + for vector in vector_data.vector_iter_mut() { + let mut fill = fill.clone(); + if let Fill::Gradient(gradient) = &mut fill { + gradient.transform *= *vector.transform; + } + vector.instance.style.set_fill(fill); + } + + vector_data +} + +/// Applies a stroke style to the vector data contained in the input. +#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("stroke_properties"))] +async fn stroke> + 'n + Send, V>( + _: impl Ctx, + #[implementations(VectorDataTable, VectorDataTable, GraphicGroupTable, GraphicGroupTable)] + /// The vector elements, or group of vector elements, to apply the stroke to. + mut vector_data: Instances, + #[implementations( + Option, + Color, + Option, + Color, + )] + #[default(Color::BLACK)] + /// The stroke color. + color: C, + #[default(2.)] + /// The stroke weight. + weight: f64, + /// The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape. + align: StrokeAlign, + /// The shape of the stroke at open endpoints. + cap: StrokeCap, + /// The curvature of the bent stroke at sharp corners. + join: StrokeJoin, + #[default(4.)] + /// The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio. + miter_limit: f64, + /// The order to paint the stroke on top of the fill, or the fill on top of the stroke. + /// + paint_order: PaintOrder, + /// The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed. + dash_lengths: Vec, + /// The phase offset distance from the starting point of the dash pattern. + dash_offset: f64, +) -> Instances +where + Instances: VectorDataTableIterMut + 'n + Send, +{ + let stroke = Stroke { + color: color.into(), + weight, + dash_lengths, + dash_offset, + cap, + join, + join_miter_limit: miter_limit, + align, + transform: DAffine2::IDENTITY, + non_scaling: false, + paint_order, + }; + + for vector in vector_data.vector_iter_mut() { + let mut stroke = stroke.clone(); + stroke.transform *= *vector.transform; + vector.instance.style.set_stroke(stroke); + } + + vector_data +} + +#[node_macro::node(category("Instancing"), path(graphene_core::vector))] +async fn repeat( + _: impl Ctx, + // TODO: Implement other GraphicElementRendered types. + #[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable)] instance: Instances, + #[default(100., 100.)] + // TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed. + direction: PixelSize, + angle: Angle, + #[default(4)] instances: IntegerCount, +) -> Instances { + let angle = angle.to_radians(); + let count = instances.max(1); + let total = (count - 1) as f64; + + let mut result_table = Instances::::default(); + + for index in 0..count { + let angle = index as f64 * angle / total; + let translation = index as f64 * direction / total; + let transform = DAffine2::from_angle(angle) * DAffine2::from_translation(translation); + + for instance in instance.instance_ref_iter() { + let mut instance = instance.to_instance_cloned(); + + let local_translation = DAffine2::from_translation(instance.transform.translation); + let local_matrix = DAffine2::from_mat2(instance.transform.matrix2); + instance.transform = local_translation * transform * local_matrix; + + result_table.push(instance); + } + } + + result_table +} + +#[node_macro::node(category("Instancing"), path(graphene_core::vector))] +async fn circular_repeat( + _: impl Ctx, + // TODO: Implement other GraphicElementRendered types. + #[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable)] instance: Instances, + angle_offset: Angle, + #[default(5)] radius: f64, + #[default(5)] instances: IntegerCount, +) -> Instances { + let count = instances.max(1); + + let mut result_table = Instances::::default(); + + for index in 0..count { + let angle = DAffine2::from_angle((TAU / count as f64) * index as f64 + angle_offset.to_radians()); + let translation = DAffine2::from_translation(radius * DVec2::Y); + let transform = angle * translation; + + for instance in instance.instance_ref_iter() { + let mut instance = instance.to_instance_cloned(); + + let local_translation = DAffine2::from_translation(instance.transform.translation); + let local_matrix = DAffine2::from_mat2(instance.transform.matrix2); + instance.transform = local_translation * transform * local_matrix; + + result_table.push(instance); + } + } + + result_table +} + +#[node_macro::node(name("Copy to Points"), category("Instancing"), path(graphene_core::vector))] +async fn copy_to_points( + _: impl Ctx, + points: VectorDataTable, + #[expose] + /// Artwork to be copied and placed at each point. + #[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable)] + instance: Instances, + /// Minimum range of randomized sizes given to each instance. + #[default(1)] + #[range((0., 2.))] + #[unit("x")] + random_scale_min: Multiplier, + /// Maximum range of randomized sizes given to each instance. + #[default(1)] + #[range((0., 2.))] + #[unit("x")] + random_scale_max: Multiplier, + /// Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes). + #[range((-50., 50.))] + random_scale_bias: f64, + /// Seed to determine unique variations on all the randomized instance sizes. + random_scale_seed: SeedValue, + /// Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise. + #[range((0., 360.))] + random_rotation: Angle, + /// Seed to determine unique variations on all the randomized instance angles. + random_rotation_seed: SeedValue, +) -> Instances { + let mut result_table = Instances::::default(); + + let random_scale_difference = random_scale_max - random_scale_min; + + for point_instance in points.instance_iter() { + let mut scale_rng = rand::rngs::StdRng::seed_from_u64(random_scale_seed.into()); + let mut rotation_rng = rand::rngs::StdRng::seed_from_u64(random_rotation_seed.into()); + + let do_scale = random_scale_difference.abs() > 1e-6; + let do_rotation = random_rotation.abs() > 1e-6; + + let points_transform = point_instance.transform; + for &point in point_instance.instance.point_domain.positions() { + let translation = points_transform.transform_point2(point); + + let rotation = if do_rotation { + let degrees = (rotation_rng.random::() - 0.5) * random_rotation; + degrees / 360. * TAU + } else { + 0. + }; + + let scale = if do_scale { + if random_scale_bias.abs() < 1e-6 { + // Linear + random_scale_min + scale_rng.random::() * random_scale_difference + } else { + // Weighted (see ) + let horizontal_scale_factor = 1. - 2_f64.powf(random_scale_bias); + let scale_factor = (1. - scale_rng.random::() * horizontal_scale_factor).log2() / random_scale_bias; + random_scale_min + scale_factor * random_scale_difference + } + } else { + random_scale_min + }; + + let transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation); + + for mut instance in instance.instance_ref_iter().map(|instance| instance.to_instance_cloned()) { + let local_matrix = DAffine2::from_mat2(instance.transform.matrix2); + instance.transform = transform * local_matrix; + + result_table.push(instance); + } + } + } + + result_table +} + +#[node_macro::node(category("Instancing"), path(graphene_core::vector))] +async fn mirror( + _: impl Ctx, + #[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable)] instance: Instances, + #[default(ReferencePoint::Center)] relative_to_bounds: ReferencePoint, + offset: f64, + #[range((-90., 90.))] angle: Angle, + #[default(true)] keep_original: bool, +) -> Instances { + let mut result_table = Instances::default(); + + // Normalize the direction vector + let normal = DVec2::from_angle(angle.to_radians()); + + // The mirror reference is based on the bounding box (at least for now, until we have proper local layer origins) + let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY, false) else { + return result_table; + }; + + let reference_point_location = relative_to_bounds.point_in_bounding_box((bounding_box[0], bounding_box[1]).into()); + let mirror_reference_point = reference_point_location.map(|point| point + normal * offset); + + // Create the reflection matrix + let reflection = DAffine2::from_mat2_translation( + glam::DMat2::from_cols( + DVec2::new(1. - 2. * normal.x * normal.x, -2. * normal.y * normal.x), + DVec2::new(-2. * normal.x * normal.y, 1. - 2. * normal.y * normal.y), + ), + DVec2::ZERO, + ); + + // Apply reflection around the reference point + let reflected_transform = if let Some(mirror_reference_point) = mirror_reference_point { + DAffine2::from_translation(mirror_reference_point) * reflection * DAffine2::from_translation(-mirror_reference_point) + } else { + reflection * DAffine2::from_translation(DVec2::from_angle(angle.to_radians()) * DVec2::splat(-offset)) + }; + + // Add original instance depending on the keep_original flag + if keep_original { + for instance in instance.clone().instance_iter() { + result_table.push(instance); + } + } + + // Create and add mirrored instance + for mut instance in instance.instance_iter() { + instance.transform = reflected_transform * instance.transform; + instance.source_node_id = None; + result_table.push(instance); + } + + result_table +} + +#[node_macro::node(category("Vector"), path(graphene_core::vector))] +async fn flatten_path(_: impl Ctx, #[implementations(GraphicGroupTable, VectorDataTable)] graphic_group_input: Instances) -> VectorDataTable { + // A node based solution to support passing through vector data could be a network node with a cache node connected to + // a Flatten Path connected to an if else node, another connection from the cache directly + // To the if else node, and another connection from the cache to a matches type node connected to the if else node. + fn flatten_group(graphic_group_table: &GraphicGroupTable, output: &mut InstanceMut) { + for (group_index, current_element) in graphic_group_table.instance_ref_iter().enumerate() { + match current_element.instance { + GraphicElement::VectorData(vector_data_table) => { + // Loop through every row of the VectorDataTable and concatenate each instance's subpath into the output VectorData instance. + for (vector_index, vector_data_instance) in vector_data_table.instance_ref_iter().enumerate() { + let other = vector_data_instance.instance; + let transform = *current_element.transform * *vector_data_instance.transform; + let node_id = current_element.source_node_id.map(|node_id| node_id.0).unwrap_or_default(); + + let mut hasher = DefaultHasher::new(); + (group_index, vector_index, node_id).hash(&mut hasher); + let collision_hash_seed = hasher.finish(); + + output.instance.concat(other, transform, collision_hash_seed); + + // Use the last encountered style as the output style + output.instance.style = vector_data_instance.instance.style.clone(); + } + } + GraphicElement::GraphicGroup(graphic_group) => { + let mut graphic_group = graphic_group.clone(); + for instance in graphic_group.instance_mut_iter() { + *instance.transform = *current_element.transform * *instance.transform; + } + + flatten_group(&graphic_group, output); + } + _ => {} + } + } + } + + // Create a table with one instance of an empty VectorData, then get a mutable reference to it which we append flattened subpaths to + let mut output_table = VectorDataTable::new(VectorData::default()); + let Some(mut output) = output_table.instance_mut_iter().next() else { + return output_table; + }; + + // Flatten the graphic group input into the output VectorData instance + let base_graphic_group = GraphicGroupTable::new(graphic_group_input.to_graphic_element()); + flatten_group(&base_graphic_group, &mut output); + + // Return the single-row VectorDataTable containing the flattened VectorData subpaths + output_table +} + +#[node_macro::node(category("General"), path(graphene_core::vector))] +async fn count_elements(_: impl Ctx, #[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable, RasterDataTable)] source: Instances) -> u64 { + source.instance_iter().count() as u64 +} + +#[cfg(test)] +mod tests { + use super::*; + use bezier_rs::Subpath; + use graphene_core::transform::Footprint; + use graphene_vector::PointId; + + fn vector_node(data: Subpath) -> VectorDataTable { + VectorDataTable::new(VectorData::from_subpath(data)) + } + + #[tokio::test] + async fn repeat() { + let direction = DVec2::X * 1.5; + let instances = 3; + let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await; + let vector_data = super::flatten_path(Footprint::default(), repeated).await; + let vector_data = vector_data.instance_ref_iter().next().unwrap().instance; + assert_eq!(vector_data.region_bezier_paths().count(), 3); + for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() { + assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5); + } + } + + #[tokio::test] + async fn repeat_transform_position() { + let direction = DVec2::new(12., 10.); + let instances = 8; + let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await; + let vector_data = super::flatten_path(Footprint::default(), repeated).await; + let vector_data = vector_data.instance_ref_iter().next().unwrap().instance; + assert_eq!(vector_data.region_bezier_paths().count(), 8); + for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() { + assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5); + } + } + + #[tokio::test] + async fn circular_repeat() { + let repeated = super::circular_repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await; + let vector_data = super::flatten_path(Footprint::default(), repeated).await; + let vector_data = vector_data.instance_ref_iter().next().unwrap().instance; + assert_eq!(vector_data.region_bezier_paths().count(), 8); + + for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() { + let expected_angle = (index as f64 + 1.) * 45.; + + let center = (subpath.manipulator_groups()[0].anchor + subpath.manipulator_groups()[2].anchor) / 2.; + let actual_angle = DVec2::Y.angle_to(center).to_degrees(); + + assert!((actual_angle - expected_angle).abs() % 360. < 1e-5, "Expected {expected_angle} found {actual_angle}"); + } + } + + #[tokio::test] + async fn copy_to_points() { + let points = Subpath::new_rect(DVec2::NEG_ONE * 10., DVec2::ONE * 10.); + let instance = Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE); + + let expected_points = VectorData::from_subpath(points.clone()).point_domain.positions().to_vec(); + + let copy_to_points = super::copy_to_points(Footprint::default(), vector_node(points), vector_node(instance), 1., 1., 0., 0, 0., 0).await; + let flatten_path = super::flatten_path(Footprint::default(), copy_to_points).await; + let flattened_copy_to_points = flatten_path.instance_ref_iter().next().unwrap().instance; + + assert_eq!(flattened_copy_to_points.region_bezier_paths().count(), expected_points.len()); + + for (index, (_, subpath)) in flattened_copy_to_points.region_bezier_paths().enumerate() { + let offset = expected_points[index]; + assert_eq!( + &subpath.anchors(), + &[offset + DVec2::NEG_ONE, offset + DVec2::new(1., -1.), offset + DVec2::ONE, offset + DVec2::new(-1., 1.),] + ); + } + } +} diff --git a/node-graph/gelement/Cargo.toml b/node-graph/gelement/Cargo.toml new file mode 100644 index 000000000..8b5e29104 --- /dev/null +++ b/node-graph/gelement/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "graphene-element" +version = "0.1.0" +edition = "2024" +description = "graphene element data format" +authors = ["Graphite Authors "] +license = "MIT OR Apache-2.0" + +[dependencies] +# Local dependencies +dyn-any = { workspace = true } +graphene-core = { workspace = true } +graphene-raster = { workspace = true } +graphene-vector = { workspace = true } +node-macro = { workspace = true } +bezier-rs = { workspace = true } + +# Workspace dependencies +glam = { workspace = true } +specta = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gelement/src/graphic_element.rs similarity index 94% rename from node-graph/gcore/src/graphic_element.rs rename to node-graph/gelement/src/graphic_element.rs index c8df19248..6a6e483d3 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gelement/src/graphic_element.rs @@ -1,14 +1,13 @@ -use crate::blending::AlphaBlending; -use crate::bounds::BoundingBox; -use crate::color::Color; -use crate::instances::{Instance, Instances}; -use crate::math::quad::Quad; -use crate::raster::image::Image; -use crate::raster_types::{CPU, GPU, Raster, RasterDataTable}; -use crate::uuid::NodeId; -use crate::vector::{VectorData, VectorDataTable}; use dyn_any::DynAny; use glam::{DAffine2, DVec2, IVec2}; +use graphene_core::blending::AlphaBlending; +use graphene_core::color::Color; +use graphene_core::instances::{Instance, Instances}; +use graphene_core::transform::Transform; +use graphene_core::uuid::NodeId; +use graphene_raster::image::Image; +use graphene_raster::{CPU, GPU, Raster, RasterDataTable}; +use graphene_vector::{VectorData, VectorDataTable}; use std::hash::Hash; // TODO: Eventually remove this migration document upgrade code @@ -246,6 +245,16 @@ pub struct Artboard { pub clip: bool, } +// Implementations for Artboard +impl Transform for Artboard { + fn transform(&self) -> DAffine2 { + DAffine2::from_translation(self.location.as_dvec2()) + } + fn local_pivot(&self, pivot: DVec2) -> DVec2 { + self.location.as_dvec2() + self.dimensions.as_dvec2() * pivot + } +} + impl Default for Artboard { fn default() -> Self { Self::new(IVec2::ZERO, IVec2::new(1920, 1080)) diff --git a/node-graph/gelement/src/lib.rs b/node-graph/gelement/src/lib.rs new file mode 100644 index 000000000..6e955d05d --- /dev/null +++ b/node-graph/gelement/src/lib.rs @@ -0,0 +1,2 @@ +mod graphic_element; +pub use graphic_element::*; diff --git a/node-graph/gmath-nodes/src/lib.rs b/node-graph/gmath-nodes/src/lib.rs index 37a7ed364..8e16439ec 100644 --- a/node-graph/gmath-nodes/src/lib.rs +++ b/node-graph/gmath-nodes/src/lib.rs @@ -1,7 +1,9 @@ use glam::DVec2; +use graphene_core::color::Color; +use graphene_core::context::Ctx; use graphene_core::gradient::GradientStops; +use graphene_core::num_traits; use graphene_core::registry::types::{Fraction, Percentage}; -use graphene_core::{Color, Ctx, num_traits}; use log::warn; use math_parser::ast; use math_parser::context::{EvalContext, NothingMap, ValueProvider}; diff --git a/node-graph/gpath-bool/Cargo.toml b/node-graph/gpath-bool/Cargo.toml index db472571a..9f91a038d 100644 --- a/node-graph/gpath-bool/Cargo.toml +++ b/node-graph/gpath-bool/Cargo.toml @@ -11,6 +11,8 @@ license = "MIT OR Apache-2.0" dyn-any = { workspace = true } bezier-rs = { workspace = true } graphene-core = { workspace = true } +graphene-vector = { workspace = true } +graphene-element = { workspace = true } node-macro = { workspace = true } glam = { workspace = true } specta = { workspace = true } diff --git a/node-graph/gpath-bool/src/lib.rs b/node-graph/gpath-bool/src/lib.rs index e94963298..9931ed6dd 100644 --- a/node-graph/gpath-bool/src/lib.rs +++ b/node-graph/gpath-bool/src/lib.rs @@ -1,15 +1,13 @@ use bezier_rs::{ManipulatorGroup, Subpath}; use dyn_any::DynAny; use glam::{DAffine2, DVec2}; +use graphene_core::context::{CloneVarArgs, Context, Ctx, ExtractAll, OwnedContextImpl}; use graphene_core::instances::{Instance, InstanceRef}; -use graphene_core::vector::algorithms::merge_by_distance::MergeByDistanceExt; -use graphene_core::vector::style::Fill; -use graphene_core::vector::{PointId, VectorData, VectorDataTable}; -use graphene_core::{Color, Ctx, GraphicElement, GraphicGroupTable}; -pub use path_bool as path_bool_lib; +use graphene_element::{GraphicElement, GraphicGroupTable}; +use graphene_vector::style::Fill; +use graphene_vector::{PointId, VectorData, VectorDataTable}; use path_bool::{FillRule, PathBooleanOperation}; use std::ops::Mul; - // TODO: Fix boolean ops to work by removing .transform() and .one_instnace_*() calls, // TODO: since before we used a Vec of single-row tables and now we use a single table // TODO: with multiple rows while still assuming a single row for the boolean operations. diff --git a/node-graph/graph-craft/Cargo.toml b/node-graph/graph-craft/Cargo.toml index b38677339..9626f5433 100644 --- a/node-graph/graph-craft/Cargo.toml +++ b/node-graph/graph-craft/Cargo.toml @@ -20,8 +20,14 @@ graphene-element-nodes = { workspace = true } graphene-path-bool = { workspace = true } graphene-brush = { workspace = true } graphene-application-io = { workspace = true } +graphene-element = { workspace = true } +graphene-math-nodes = { workspace = true } +graphene-raster = { workspace = true } graphene-svg-renderer = { workspace = true } graphene-raster-nodes = { workspace = true } +graphene-text = { workspace = true } +graphene-vector = { workspace = true } +graphene-vector-nodes = { workspace = true } # Workspace dependencies log = { workspace = true } diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index 8f1e0d5fb..5396f98a1 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -7,12 +7,13 @@ pub use glam::{DAffine2, DVec2, IVec2, UVec2}; use graphene_application_io::SurfaceFrame; use graphene_brush::brush_cache::BrushCache; use graphene_brush::brush_stroke::BrushStroke; -use graphene_core::raster_types::CPU; -use graphene_core::transform::ReferencePoint; +use graphene_core::color::Color; use graphene_core::uuid::NodeId; -use graphene_core::vector::style::Fill; -use graphene_core::{Color, MemoHash, Node, Type}; +use graphene_core::{MemoHash, Node, Type}; +use graphene_raster::CPU; use graphene_svg_renderer::RenderMetadata; +use graphene_vector::reference_point::ReferencePoint; +use graphene_vector::style::Fill; use std::fmt::Display; use std::hash::Hash; use std::marker::PhantomData; @@ -181,41 +182,41 @@ tagged_value! { F64Array4([f64; 4]), NodePath(Vec), #[serde(alias = "ManipulatorGroupIds")] // TODO: Eventually remove this alias document upgrade code - PointIds(Vec), + PointIds(Vec), // ==================== // GRAPHICAL DATA TYPES // ==================== - GraphicElement(graphene_core::GraphicElement), + GraphicElement(graphene_element::GraphicElement), #[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::vector::migrate_vector_data"))] // TODO: Eventually remove this migration document upgrade code - VectorData(graphene_core::vector::VectorDataTable), + VectorData(graphene_vector::VectorDataTable), #[cfg_attr(target_arch = "wasm32", serde(alias = "ImageFrame", deserialize_with = "graphene_core::raster::image::migrate_image_frame"))] // TODO: Eventually remove this migration document upgrade code - RasterData(graphene_core::raster_types::RasterDataTable), + RasterData(graphene_raster::RasterDataTable), #[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::migrate_graphic_group"))] // TODO: Eventually remove this migration document upgrade code - GraphicGroup(graphene_core::GraphicGroupTable), + GraphicGroup(graphene_element::GraphicGroupTable), #[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::migrate_artboard_group"))] // TODO: Eventually remove this migration document upgrade code - ArtboardGroup(graphene_core::ArtboardGroupTable), + ArtboardGroup(graphene_element::ArtboardGroupTable), // ============ // STRUCT TYPES // ============ - Artboard(graphene_core::Artboard), - Image(graphene_core::raster::Image), - Color(graphene_core::raster::color::Color), - OptionalColor(Option), + Artboard(graphene_element::Artboard), + Image(graphene_raster::image::Image), + Color(Color), + OptionalColor(Option), Palette(Vec), - Subpaths(Vec>), - Fill(graphene_core::vector::style::Fill), - Stroke(graphene_core::vector::style::Stroke), - Gradient(graphene_core::vector::style::Gradient), + Subpaths(Vec>), + Fill(graphene_vector::style::Fill), + Stroke(graphene_vector::style::Stroke), + Gradient(graphene_core::gradient::Gradient), #[serde(alias = "GradientPositions")] // TODO: Eventually remove this alias document upgrade code - GradientStops(graphene_core::vector::style::GradientStops), - Font(graphene_core::text::Font), + GradientStops(graphene_core::gradient::GradientStops), + Font(graphene_text::Font), BrushStrokes(Vec), BrushCache(BrushCache), DocumentNode(DocumentNode), Curve(graphene_raster_nodes::curve::Curve), Footprint(graphene_core::transform::Footprint), - VectorModification(Box), - FontCache(Arc), + VectorModification(Box), + FontCache(Arc), // ========== // ENUM TYPES // ========== @@ -232,21 +233,21 @@ tagged_value! { DomainWarpType(graphene_raster_nodes::adjustments::DomainWarpType), RelativeAbsolute(graphene_raster_nodes::adjustments::RelativeAbsolute), SelectiveColorChoice(graphene_raster_nodes::adjustments::SelectiveColorChoice), - GridType(graphene_core::vector::misc::GridType), - ArcType(graphene_core::vector::misc::ArcType), - MergeByDistanceAlgorithm(graphene_core::vector::misc::MergeByDistanceAlgorithm), - PointSpacingType(graphene_core::vector::misc::PointSpacingType), + GridType(graphene_vector_nodes::misc::GridType), + ArcType(graphene_vector_nodes::misc::ArcType), + MergeByDistanceAlgorithm(graphene_vector_nodes::misc::MergeByDistanceAlgorithm), + PointSpacingType(graphene_vector_nodes::misc::PointSpacingType), #[serde(alias = "LineCap")] - StrokeCap(graphene_core::vector::style::StrokeCap), + StrokeCap(graphene_vector::style::StrokeCap), #[serde(alias = "LineJoin")] - StrokeJoin(graphene_core::vector::style::StrokeJoin), - StrokeAlign(graphene_core::vector::style::StrokeAlign), - PaintOrder(graphene_core::vector::style::PaintOrder), - FillType(graphene_core::vector::style::FillType), - FillChoice(graphene_core::vector::style::FillChoice), - GradientType(graphene_core::vector::style::GradientType), - ReferencePoint(graphene_core::transform::ReferencePoint), - CentroidType(graphene_core::vector::misc::CentroidType), + StrokeJoin(graphene_vector::style::StrokeJoin), + StrokeAlign(graphene_vector::style::StrokeAlign), + PaintOrder(graphene_vector::style::PaintOrder), + FillType(graphene_vector::style::FillType), + FillChoice(graphene_vector::style::FillChoice), + GradientType(graphene_core::gradient::GradientType), + ReferencePoint(graphene_vector::reference_point::ReferencePoint), + CentroidType(graphene_vector_nodes::misc::CentroidType), BooleanOperation(graphene_path_bool::BooleanOperation), } diff --git a/node-graph/graphene-cli/Cargo.toml b/node-graph/graphene-cli/Cargo.toml index 84bf96e7c..e3092ff96 100644 --- a/node-graph/graphene-cli/Cargo.toml +++ b/node-graph/graphene-cli/Cargo.toml @@ -16,7 +16,6 @@ gpu = ["interpreted-executor/gpu", "graphene-std/gpu", "wgpu-executor"] [dependencies] # Local dependencies -graphene-core = { workspace = true } graphene-std = { workspace = true } interpreted-executor = { workspace = true } graph-craft = { workspace = true, features = ["loading"] } diff --git a/node-graph/graster-nodes/Cargo.toml b/node-graph/graster-nodes/Cargo.toml index 3bc97e7e9..1a29cc231 100644 --- a/node-graph/graster-nodes/Cargo.toml +++ b/node-graph/graster-nodes/Cargo.toml @@ -14,6 +14,7 @@ serde = ["dep:serde"] # Local dependencies dyn-any = { workspace = true } graphene-core = { workspace = true } +graphene-raster = { workspace = true } node-macro = { workspace = true } # Workspace dependencies diff --git a/node-graph/graster-nodes/src/adjustments.rs b/node-graph/graster-nodes/src/adjustments.rs index 297fd1b39..d37b948d2 100644 --- a/node-graph/graster-nodes/src/adjustments.rs +++ b/node-graph/graster-nodes/src/adjustments.rs @@ -1,6 +1,5 @@ #![allow(clippy::too_many_arguments)] -use crate::curve::CubicSplines; use dyn_any::DynAny; use graphene_core::Node; use graphene_core::blending::BlendMode; @@ -8,12 +7,12 @@ use graphene_core::color::Color; use graphene_core::color::Pixel; use graphene_core::context::Ctx; use graphene_core::gradient::GradientStops; -use graphene_core::raster::image::Image; -use graphene_core::raster_types::{CPU, Raster, RasterDataTable}; use graphene_core::registry::types::{Angle, Percentage, SignedPercentage}; +use graphene_raster::curve::CubicSplines; +use graphene_raster::image::Image; +use graphene_raster::{CPU, Raster, RasterDataTable}; use std::cmp::Ordering; use std::fmt::Debug; - // TODO: Implement the following: // Color Balance // Aims for interoperable compatibility with: @@ -1181,8 +1180,8 @@ fn color_overlay>( mod test { use graphene_core::blending::BlendMode; use graphene_core::color::Color; - use graphene_core::raster::image::Image; - use graphene_core::raster_types::{Raster, RasterDataTable}; + use graphene_raster::image::Image; + use graphene_raster::{Raster, RasterDataTable}; #[tokio::test] async fn color_overlay_multiply() { diff --git a/node-graph/graster-nodes/src/dehaze.rs b/node-graph/graster-nodes/src/dehaze.rs index 268ccee09..5b5f7fc7e 100644 --- a/node-graph/graster-nodes/src/dehaze.rs +++ b/node-graph/graster-nodes/src/dehaze.rs @@ -1,7 +1,7 @@ use graphene_core::context::Ctx; -use graphene_core::raster::image::Image; -use graphene_core::raster_types::{CPU, Raster, RasterDataTable}; use graphene_core::registry::types::Percentage; +use graphene_raster::image::Image; +use graphene_raster::{CPU, Raster, RasterDataTable}; use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage}; use ndarray::{Array2, ArrayBase, Dim, OwnedRepr}; use std::cmp::{max, min}; diff --git a/node-graph/graster-nodes/src/filter.rs b/node-graph/graster-nodes/src/filter.rs index 3fd1f0686..8322f2195 100644 --- a/node-graph/graster-nodes/src/filter.rs +++ b/node-graph/graster-nodes/src/filter.rs @@ -1,9 +1,9 @@ use graphene_core::color::Color; use graphene_core::context::Ctx; -use graphene_core::raster::image::Image; -use graphene_core::raster::{Bitmap, BitmapMut}; -use graphene_core::raster_types::{CPU, Raster, RasterDataTable}; use graphene_core::registry::types::PixelLength; +use graphene_raster::bitmap::{Bitmap, BitmapMut}; +use graphene_raster::image::Image; +use graphene_raster::{CPU, Raster, RasterDataTable}; /// Blurs the image with a Gaussian or blur kernel filter. #[node_macro::node(category("Raster: Filter"))] diff --git a/node-graph/graster-nodes/src/image_color_palette.rs b/node-graph/graster-nodes/src/image_color_palette.rs index 6163c88bb..4860b027a 100644 --- a/node-graph/graster-nodes/src/image_color_palette.rs +++ b/node-graph/graster-nodes/src/image_color_palette.rs @@ -1,6 +1,6 @@ use graphene_core::color::Color; use graphene_core::context::Ctx; -use graphene_core::raster_types::{CPU, RasterDataTable}; +use graphene_raster::{CPU, RasterDataTable}; #[node_macro::node(category("Color"))] async fn image_color_palette( @@ -65,8 +65,8 @@ async fn image_color_palette( #[cfg(test)] mod test { use super::*; - use graphene_core::raster::image::Image; - use graphene_core::raster_types::{Raster, RasterDataTable}; + use graphene_raster::image::Image; + use graphene_raster::{Raster, RasterDataTable}; #[test] fn test_image_color_palette() { diff --git a/node-graph/graster-nodes/src/std_nodes.rs b/node-graph/graster-nodes/src/std_nodes.rs index 9b331424d..10bd9cdbb 100644 --- a/node-graph/graster-nodes/src/std_nodes.rs +++ b/node-graph/graster-nodes/src/std_nodes.rs @@ -8,10 +8,11 @@ use graphene_core::color::{Alpha, AlphaMut, Channel, LinearChannel, Luminance, R use graphene_core::context::{Ctx, ExtractFootprint}; use graphene_core::instances::Instance; use graphene_core::math::bbox::Bbox; -use graphene_core::raster::image::Image; -use graphene_core::raster::{Bitmap, BitmapMut}; -use graphene_core::raster_types::{CPU, Raster, RasterDataTable}; use graphene_core::transform::Transform; +use graphene_raster::bitmap::{Bitmap, BitmapMut}; +use graphene_raster::image::Image; +use graphene_raster::transform::FootprintExt; +use graphene_raster::{CPU, Raster, RasterDataTable}; use rand::prelude::*; use rand_chacha::ChaCha8Rng; use std::fmt::Debug; diff --git a/node-graph/graster/Cargo.toml b/node-graph/graster/Cargo.toml new file mode 100644 index 000000000..a14c34f98 --- /dev/null +++ b/node-graph/graster/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "graphene-raster" +version = "0.1.0" +edition = "2024" +description = "graphene raster data format" +authors = ["Graphite Authors "] +license = "MIT OR Apache-2.0" + +[features] +default = ["serde"] +serde = ["dep:serde"] +wgpu = ["dep:wgpu"] + +[dependencies] +# Local dependencies +dyn-any = { workspace = true } +graphene-core = { workspace = true } +node-macro = { workspace = true } + +# Workspace dependencies +glam = { workspace = true } +specta = { workspace = true } +base64 = { workspace = true } +bytemuck = { workspace = true } +image = { workspace = true } + +# Optional workspace dependencies +serde = { workspace = true, optional = true, features = ["derive"] } +wgpu = { workspace = true, optional = true } + +[dev-dependencies] +serde_json = { workspace = true } diff --git a/node-graph/gcore/src/raster.rs b/node-graph/graster/src/bitmap.rs similarity index 79% rename from node-graph/gcore/src/raster.rs rename to node-graph/graster/src/bitmap.rs index 60106bdc7..944130053 100644 --- a/node-graph/gcore/src/raster.rs +++ b/node-graph/graster/src/bitmap.rs @@ -1,20 +1,4 @@ -use crate::GraphicGroupTable; -pub use crate::color::*; -use crate::raster_types::{CPU, RasterDataTable}; -use crate::vector::VectorDataTable; -use std::fmt::Debug; - -#[cfg(target_arch = "spirv")] -use spirv_std::num_traits::float::Float; - -/// as to not yet rename all references -pub mod color { - pub use super::*; -} - -pub mod image; - -pub use self::image::Image; +use graphene_core::color::Pixel; pub trait Bitmap { type Pixel: Pixel; diff --git a/node-graph/gcore/src/raster/image.rs b/node-graph/graster/src/image.rs similarity index 97% rename from node-graph/gcore/src/raster/image.rs rename to node-graph/graster/src/image.rs index e93fe60ba..d06011202 100644 --- a/node-graph/gcore/src/raster/image.rs +++ b/node-graph/graster/src/image.rs @@ -1,18 +1,19 @@ -use super::Color; -use crate::AlphaBlending; -use crate::color::float_to_srgb_u8; -use crate::instances::{Instance, Instances}; -use crate::raster_types::Raster; +use crate::bitmap::{Bitmap, BitmapMut}; +use crate::{CPU, Raster, RasterDataTable}; use core::hash::{Hash, Hasher}; use dyn_any::{DynAny, StaticType}; use glam::{DAffine2, DVec2}; +use graphene_core::blending::AlphaBlending; +use graphene_core::color::{Alpha, AssociatedAlpha, Color, Linear, Pixel, RGB, SRGBA8, Sample, float_to_srgb_u8}; +use graphene_core::instances::{Instance, Instances}; +use std::fmt::Debug; use std::vec::Vec; mod base64_serde { //! Basic wrapper for [`serde`] to perform [`base64`] encoding - use super::super::Pixel; use base64::Engine; + use graphene_core::color::Pixel; use serde::{Deserialize, Deserializer, Serialize, Serializer}; pub fn as_base64(key: &[P], serializer: S) -> Result { @@ -138,7 +139,6 @@ impl Image { } } -use super::*; impl Image

where P::ColorChannel: Linear, @@ -485,7 +485,6 @@ mod test { #[test] fn test_image_serialization_roundtrip() { use super::*; - use crate::Color; let image = Image { width: 2, height: 2, diff --git a/node-graph/graster/src/lib.rs b/node-graph/graster/src/lib.rs new file mode 100644 index 000000000..979cb88cd --- /dev/null +++ b/node-graph/graster/src/lib.rs @@ -0,0 +1,5 @@ +pub mod bitmap; +pub mod image; +mod raster_types; + +pub use raster_types::*; diff --git a/node-graph/gcore/src/raster_types.rs b/node-graph/graster/src/raster_types.rs similarity index 95% rename from node-graph/gcore/src/raster_types.rs rename to node-graph/graster/src/raster_types.rs index 4fb1fc4d5..71243fc81 100644 --- a/node-graph/gcore/src/raster_types.rs +++ b/node-graph/graster/src/raster_types.rs @@ -1,11 +1,11 @@ -use crate::Color; -use crate::bounds::BoundingBox; -use crate::instances::Instances; -use crate::math::quad::Quad; -use crate::raster::Image; +use crate::image::Image; use core::ops::Deref; use dyn_any::DynAny; use glam::{DAffine2, DVec2}; +use graphene_core::bounds::BoundingBox; +use graphene_core::color::Color; +use graphene_core::instances::Instances; +use graphene_core::math::quad::Quad; #[cfg(feature = "wgpu")] use std::sync::Arc; diff --git a/node-graph/gstd/Cargo.toml b/node-graph/gstd/Cargo.toml index 844c9c0d3..5a9031b43 100644 --- a/node-graph/gstd/Cargo.toml +++ b/node-graph/gstd/Cargo.toml @@ -32,9 +32,14 @@ graphene-path-bool = { workspace = true } graphene-math-nodes = { workspace = true } graphene-svg-renderer = { workspace = true } graphene-application-io = { workspace = true } +graphene-element = { workspace = true } graphene-element-nodes = { workspace = true } +graphene-raster = { workspace = true } graphene-raster-nodes = { workspace = true } +graphene-vector = { workspace = true } +graphene-vector-nodes = { workspace = true } graphene-brush = { workspace = true } +graphene-text = { workspace = true } # Workspace dependencies fastnoise-lite = { workspace = true } @@ -66,8 +71,5 @@ web-sys = { workspace = true, optional = true, features = [ "ImageBitmapRenderingContext", ] } -# Required dependencies -ndarray = "0.16.1" - [dev-dependencies] tokio = { workspace = true } diff --git a/node-graph/gstd/src/lib.rs b/node-graph/gstd/src/lib.rs index 68e08d296..b0add8022 100644 --- a/node-graph/gstd/src/lib.rs +++ b/node-graph/gstd/src/lib.rs @@ -6,13 +6,14 @@ pub mod wasm_application_io; pub use graphene_application_io as application_io; pub use graphene_brush as brush; -pub use graphene_core::vector; pub use graphene_core::*; +pub use graphene_element as element; pub use graphene_element_nodes as element_nodes; pub use graphene_element_nodes::animation; pub use graphene_math_nodes as math_nodes; pub use graphene_path_bool as path_bool; pub use graphene_raster_nodes as raster_nodes; +pub use graphene_vector as vector; /// stop gap solutions until all paths have been replaced with their absolute ones pub mod renderer { diff --git a/node-graph/gstd/src/text.rs b/node-graph/gstd/src/text.rs index 0f69ae1bf..bae566577 100644 --- a/node-graph/gstd/src/text.rs +++ b/node-graph/gstd/src/text.rs @@ -1,7 +1,7 @@ -use crate::vector::{VectorData, VectorDataTable}; use graph_craft::wasm_application_io::WasmEditorApi; -use graphene_core::Ctx; -pub use graphene_core::text::*; +use graphene_core::context::Ctx; +pub use graphene_text::*; +use graphene_vector::{VectorData, VectorDataTable}; #[node_macro::node(category(""))] fn text<'i: 'n>( diff --git a/node-graph/gsvg-renderer/Cargo.toml b/node-graph/gsvg-renderer/Cargo.toml index 86b3b14aa..2d5aafca6 100644 --- a/node-graph/gsvg-renderer/Cargo.toml +++ b/node-graph/gsvg-renderer/Cargo.toml @@ -13,11 +13,17 @@ vello = ["dep:vello", "bezier-rs/kurbo"] # Local dependencies dyn-any = { workspace = true } graphene-core = { workspace = true } +graphene-raster = { workspace = true } +graphene-element = { workspace = true } +graphene-vector = { workspace = true } +node-macro = { workspace = true } bezier-rs = { workspace = true } # Workspace dependencies glam = { workspace = true } +specta = { workspace = true } serde = { workspace = true } +serde_json = { workspace = true } base64 = { workspace = true } log = { workspace = true } num-traits = { workspace = true } diff --git a/node-graph/gsvg-renderer/src/convert_usvg_path.rs b/node-graph/gsvg-renderer/src/convert_usvg_path.rs index 7ba7688ae..a9d1546da 100644 --- a/node-graph/gsvg-renderer/src/convert_usvg_path.rs +++ b/node-graph/gsvg-renderer/src/convert_usvg_path.rs @@ -1,6 +1,6 @@ use bezier_rs::{ManipulatorGroup, Subpath}; use glam::DVec2; -use graphene_core::vector::PointId; +use graphene_vector::PointId; pub fn convert_usvg_path(path: &usvg::Path) -> Vec> { let mut subpaths = Vec::new(); diff --git a/node-graph/gsvg-renderer/src/render_ext.rs b/node-graph/gsvg-renderer/src/render_ext.rs index d0d7c16cc..1e0777a6f 100644 --- a/node-graph/gsvg-renderer/src/render_ext.rs +++ b/node-graph/gsvg-renderer/src/render_ext.rs @@ -3,8 +3,8 @@ use glam::{DAffine2, DVec2}; use graphene_core::consts::{LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WEIGHT}; use graphene_core::gradient::{Gradient, GradientType}; use graphene_core::uuid::generate_uuid; -use graphene_core::vector::style::{Fill, PaintOrder, PathStyle, Stroke, StrokeAlign, StrokeCap, StrokeJoin, ViewMode}; -use std::fmt::Write; +use graphene_core::view_mode::ViewMode; +use graphene_vector::style::{Fill, PaintOrder, PathStyle, Stroke, StrokeAlign, StrokeCap, StrokeJoin}; pub trait RenderExt { type Output; diff --git a/node-graph/gsvg-renderer/src/renderer.rs b/node-graph/gsvg-renderer/src/renderer.rs index 95e9890a8..04e185aa6 100644 --- a/node-graph/gsvg-renderer/src/renderer.rs +++ b/node-graph/gsvg-renderer/src/renderer.rs @@ -1,21 +1,22 @@ use crate::render_ext::RenderExt; -use crate::to_peniko::BlendModeExt; +use base64::Engine; use bezier_rs::Subpath; use dyn_any::DynAny; use glam::{DAffine2, DVec2}; use graphene_core::blending::BlendMode; -use graphene_core::bounds::BoundingBox; use graphene_core::color::Color; use graphene_core::instances::Instance; use graphene_core::math::quad::Quad; -use graphene_core::raster::Image; -use graphene_core::raster_types::{CPU, GPU, RasterDataTable}; +use graphene_core::math::rect::Rect; use graphene_core::transform::{Footprint, Transform}; use graphene_core::uuid::{NodeId, generate_uuid}; -use graphene_core::vector::VectorDataTable; -use graphene_core::vector::click_target::{ClickTarget, FreePoint}; -use graphene_core::vector::style::{Fill, Stroke, StrokeAlign, ViewMode}; -use graphene_core::{AlphaBlending, Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable}; +use graphene_core::view_mode::ViewMode; +use graphene_element::{Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable}; +use graphene_raster::image::Image; +use graphene_raster::{CPU, GPU, RasterDataTable}; +use graphene_vector::VectorDataTable; +use graphene_vector::click_target::{ClickTarget, FreePoint}; +use graphene_vector::style::{Fill, Stroke, StrokeAlign}; use num_traits::Zero; use std::collections::{HashMap, HashSet}; use std::fmt::Write; diff --git a/node-graph/gtext/Cargo.toml b/node-graph/gtext/Cargo.toml new file mode 100644 index 000000000..d40487257 --- /dev/null +++ b/node-graph/gtext/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "graphene-text" +version = "0.1.0" +edition = "2024" +description = "graphene text nodes" +authors = ["Graphite Authors "] +license = "MIT OR Apache-2.0" + +[features] +default = ["serde"] +serde = ["dep:serde"] + +[dependencies] +# Local dependencies +dyn-any = { workspace = true } +bezier-rs = { workspace = true } +graphene-core = { workspace = true } +graphene-vector = { workspace = true } +node-macro = { workspace = true } + +# Workspace dependencies +kurbo = { workspace = true } +glam = { workspace = true } +specta = { workspace = true } +rustybuzz = { workspace = true } + +# Optional workspace dependencies +serde = { workspace = true, optional = true, features = ["derive"] } diff --git a/node-graph/gcore/src/text/font_cache.rs b/node-graph/gtext/src/font_cache.rs similarity index 91% rename from node-graph/gcore/src/text/font_cache.rs rename to node-graph/gtext/src/font_cache.rs index a872166fb..dbb39c72d 100644 --- a/node-graph/gcore/src/text/font_cache.rs +++ b/node-graph/gtext/src/font_cache.rs @@ -1,4 +1,5 @@ use dyn_any::DynAny; +use graphene_core::consts::{DEFAULT_FONT_FAMILY, DEFAULT_FONT_STYLE}; use std::collections::HashMap; /// A font type (storing font family and font style and an optional preview URL) @@ -16,7 +17,7 @@ impl Font { } impl Default for Font { fn default() -> Self { - Self::new(crate::consts::DEFAULT_FONT_FAMILY.into(), crate::consts::DEFAULT_FONT_STYLE.into()) + Self::new(DEFAULT_FONT_FAMILY.into(), DEFAULT_FONT_STYLE.into()) } } /// A cache of all loaded font data and preview urls along with the default font (send from `init_app` in `editor_api.rs`) @@ -33,9 +34,7 @@ impl FontCache { if self.font_file_data.contains_key(font) { Some(font) } else { - self.font_file_data - .keys() - .find(|font| font.font_family == crate::consts::DEFAULT_FONT_FAMILY && font.font_style == crate::consts::DEFAULT_FONT_STYLE) + self.font_file_data.keys().find(|font| font.font_family == DEFAULT_FONT_FAMILY && font.font_style == DEFAULT_FONT_STYLE) } } diff --git a/node-graph/gcore/src/text.rs b/node-graph/gtext/src/lib.rs similarity index 100% rename from node-graph/gcore/src/text.rs rename to node-graph/gtext/src/lib.rs diff --git a/node-graph/gcore/src/text/to_path.rs b/node-graph/gtext/src/to_path.rs similarity index 99% rename from node-graph/gcore/src/text/to_path.rs rename to node-graph/gtext/src/to_path.rs index 9988fcae3..f9648791c 100644 --- a/node-graph/gcore/src/text/to_path.rs +++ b/node-graph/gtext/src/to_path.rs @@ -1,6 +1,6 @@ -use crate::vector::PointId; use bezier_rs::{ManipulatorGroup, Subpath}; use glam::DVec2; +use graphene_vector::PointId; use rustybuzz::ttf_parser::{GlyphId, OutlineBuilder}; use rustybuzz::{GlyphBuffer, UnicodeBuffer}; diff --git a/node-graph/gvector-nodes/Cargo.toml b/node-graph/gvector-nodes/Cargo.toml new file mode 100644 index 000000000..2aad9b9a5 --- /dev/null +++ b/node-graph/gvector-nodes/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "graphene-vector-nodes" +version = "0.1.0" +edition = "2024" +description = "graphene vector nodes" +authors = ["Graphite Authors "] +license = "MIT OR Apache-2.0" + +[features] +default = ["serde"] +serde = [ + "dep:serde", + "bezier-rs/serde", +] + +[dependencies] +# Local dependencies +dyn-any = { workspace = true } +bezier-rs = { workspace = true } +graphene-core = { workspace = true } +graphene-vector = { workspace = true } +node-macro = { workspace = true } + +# Workspace dependencies +kurbo = { workspace = true } +glam = { workspace = true } +specta = { workspace = true } +log = { workspace = true } +rustc-hash = { workspace = true } +petgraph = { workspace = true } +rand = { workspace = true } + +# Optional workspace dependencies +serde = { workspace = true, optional = true, features = ["derive"] } + +[dev-dependencies] +# Workspace dependencies +tokio = { workspace = true } diff --git a/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs b/node-graph/gvector-nodes/src/algorithms/bezpath_algorithms.rs similarity index 99% rename from node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs rename to node-graph/gvector-nodes/src/algorithms/bezpath_algorithms.rs index fa8af0f6d..3daaddc3f 100644 --- a/node-graph/gcore/src/vector/algorithms/bezpath_algorithms.rs +++ b/node-graph/gvector-nodes/src/algorithms/bezpath_algorithms.rs @@ -1,5 +1,5 @@ use super::poisson_disk::poisson_disk_sample; -use crate::vector::misc::{PointSpacingType, dvec2_to_point}; +use crate::misc::{PointSpacingType, dvec2_to_point}; use glam::DVec2; use kurbo::{BezPath, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveDeriv, PathEl, PathSeg, Point, Rect, Shape}; diff --git a/node-graph/gcore/src/vector/algorithms/merge_by_distance.rs b/node-graph/gvector-nodes/src/algorithms/merge_by_distance.rs similarity index 98% rename from node-graph/gcore/src/vector/algorithms/merge_by_distance.rs rename to node-graph/gvector-nodes/src/algorithms/merge_by_distance.rs index 7cd5adc4c..116b68715 100644 --- a/node-graph/gcore/src/vector/algorithms/merge_by_distance.rs +++ b/node-graph/gvector-nodes/src/algorithms/merge_by_distance.rs @@ -1,5 +1,5 @@ -use crate::vector::{PointDomain, PointId, SegmentDomain, VectorData, VectorDataIndex}; use glam::{DAffine2, DVec2}; +use graphene_vector::{PointDomain, PointId, SegmentDomain, VectorData, VectorDataIndex}; use petgraph::prelude::UnGraphMap; use rustc_hash::FxHashSet; diff --git a/node-graph/gcore/src/vector/algorithms/mod.rs b/node-graph/gvector-nodes/src/algorithms/mod.rs similarity index 100% rename from node-graph/gcore/src/vector/algorithms/mod.rs rename to node-graph/gvector-nodes/src/algorithms/mod.rs diff --git a/node-graph/gcore/src/vector/algorithms/offset_subpath.rs b/node-graph/gvector-nodes/src/algorithms/offset_subpath.rs similarity index 99% rename from node-graph/gcore/src/vector/algorithms/offset_subpath.rs rename to node-graph/gvector-nodes/src/algorithms/offset_subpath.rs index 2041ebdef..8f475fc57 100644 --- a/node-graph/gcore/src/vector/algorithms/offset_subpath.rs +++ b/node-graph/gvector-nodes/src/algorithms/offset_subpath.rs @@ -1,5 +1,5 @@ -use crate::vector::PointId; use bezier_rs::{Bezier, BezierHandles, Join, Subpath, TValue}; +use graphene_vector::PointId; /// Value to control smoothness and mathematical accuracy to offset a cubic Bezier. const CUBIC_REGULARIZATION_ACCURACY: f64 = 0.5; diff --git a/node-graph/gcore/src/vector/algorithms/poisson_disk.rs b/node-graph/gvector-nodes/src/algorithms/poisson_disk.rs similarity index 100% rename from node-graph/gcore/src/vector/algorithms/poisson_disk.rs rename to node-graph/gvector-nodes/src/algorithms/poisson_disk.rs diff --git a/node-graph/gcore/src/vector/algorithms/spline.rs b/node-graph/gvector-nodes/src/algorithms/spline.rs similarity index 99% rename from node-graph/gcore/src/vector/algorithms/spline.rs rename to node-graph/gvector-nodes/src/algorithms/spline.rs index 27011013b..3b5905344 100644 --- a/node-graph/gcore/src/vector/algorithms/spline.rs +++ b/node-graph/gvector-nodes/src/algorithms/spline.rs @@ -141,7 +141,7 @@ mod tests { use super::*; #[test] fn closed_spline() { - use crate::vector::misc::{dvec2_to_point, point_to_dvec2}; + use graphene_vector::{dvec2_to_point, point_to_dvec2}; use kurbo::{BezPath, ParamCurve, ParamCurveDeriv}; // These points are just chosen arbitrary diff --git a/node-graph/gcore/src/vector/generator_nodes.rs b/node-graph/gvector-nodes/src/generator_nodes.rs similarity index 98% rename from node-graph/gcore/src/vector/generator_nodes.rs rename to node-graph/gvector-nodes/src/generator_nodes.rs index adab1ce53..3055c7504 100644 --- a/node-graph/gcore/src/vector/generator_nodes.rs +++ b/node-graph/gvector-nodes/src/generator_nodes.rs @@ -1,10 +1,9 @@ use super::misc::{ArcType, AsU64, GridType}; -use super::{PointId, SegmentId, StrokeId}; -use crate::Ctx; -use crate::registry::types::{Angle, PixelSize}; -use crate::vector::{HandleId, VectorData, VectorDataTable}; use bezier_rs::Subpath; use glam::DVec2; +use graphene_core::context::Ctx; +use graphene_core::registry::types::{Angle, PixelSize}; +use graphene_vector::{HandleId, PointId, SegmentId, StrokeId, VectorData, VectorDataTable}; trait CornerRadius { fn generate(self, size: DVec2, clamped: bool) -> VectorDataTable; diff --git a/node-graph/gvector-nodes/src/lib.rs b/node-graph/gvector-nodes/src/lib.rs new file mode 100644 index 000000000..ec5951a8e --- /dev/null +++ b/node-graph/gvector-nodes/src/lib.rs @@ -0,0 +1,5 @@ +pub mod algorithms; +pub mod generator_nodes; +pub mod misc; +pub mod modification; +pub mod vector_nodes; diff --git a/node-graph/gcore/src/vector/misc.rs b/node-graph/gvector-nodes/src/misc.rs similarity index 100% rename from node-graph/gcore/src/vector/misc.rs rename to node-graph/gvector-nodes/src/misc.rs diff --git a/node-graph/gcore/src/vector/vector_data/modification.rs b/node-graph/gvector-nodes/src/modification.rs similarity index 98% rename from node-graph/gcore/src/vector/vector_data/modification.rs rename to node-graph/gvector-nodes/src/modification.rs index 0f06c643a..a29d0aaa4 100644 --- a/node-graph/gcore/src/vector/vector_data/modification.rs +++ b/node-graph/gvector-nodes/src/modification.rs @@ -1,12 +1,20 @@ -use super::*; -use crate::Ctx; -use crate::instances::Instance; -use crate::uuid::generate_uuid; +use crate::misc::point_to_dvec2; use bezier_rs::BezierHandles; use dyn_any::DynAny; +use glam::DVec2; +use graphene_core::context::Ctx; +use graphene_core::instances::Instance; +use graphene_core::uuid::generate_uuid; +use graphene_vector::{FillId, HandleId, HandleType, PointDomain, PointId, RegionDomain, RegionId, SegmentDomain, SegmentId, StrokeId, VectorData, VectorDataTable}; use kurbo::{BezPath, PathEl, Point}; +use log::warn; +use serde::de::{SeqAccess, Visitor}; +use serde::ser::SerializeSeq; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::collections::{HashMap, HashSet}; +use std::fmt; use std::hash::BuildHasher; +use std::hash::Hash; /// Represents a procedural change to the [`PointDomain`] in [`VectorData`]. #[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)] @@ -434,11 +442,6 @@ async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modificat // Do we want to enforce that all serialized/deserialized hashmaps are a vec of tuples? // TODO: Eventually remove this document upgrade code -use serde::de::{SeqAccess, Visitor}; -use serde::ser::SerializeSeq; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::fmt; -use std::hash::Hash; pub fn serialize_hashmap(hashmap: &HashMap, serializer: S) -> Result where K: Serialize + Eq + Hash, diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gvector-nodes/src/vector_nodes.rs similarity index 76% rename from node-graph/gcore/src/vector/vector_nodes.rs rename to node-graph/gvector-nodes/src/vector_nodes.rs index 009d7ab35..551b41d4e 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gvector-nodes/src/vector_nodes.rs @@ -1,421 +1,26 @@ use super::algorithms::bezpath_algorithms::{self, position_on_bezpath, sample_polyline_on_bezpath, split_bezpath, tangent_on_bezpath}; use super::algorithms::offset_subpath::offset_subpath; use super::algorithms::spline::{solve_spline_first_handle_closed, solve_spline_first_handle_open}; -use super::misc::{CentroidType, point_to_dvec2}; -use super::style::{Fill, Gradient, GradientStops, Stroke}; -use super::{PointId, SegmentDomain, SegmentId, StrokeId, VectorData, VectorDataExt, VectorDataTable}; -use crate::bounds::BoundingBox; -use crate::instances::{Instance, InstanceMut, Instances}; -use crate::raster_types::{CPU, GPU, RasterDataTable}; -use crate::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier, Percentage, PixelLength, PixelSize, SeedValue}; -use crate::transform::{Footprint, ReferencePoint, Transform}; -use crate::vector::algorithms::merge_by_distance::MergeByDistanceExt; -use crate::vector::misc::{MergeByDistanceAlgorithm, PointSpacingType}; -use crate::vector::style::{PaintOrder, StrokeAlign, StrokeCap, StrokeJoin}; -use crate::vector::{FillId, PointDomain, RegionId}; -use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, GraphicElement, GraphicGroupTable, OwnedContextImpl}; +use super::misc::{CentroidType, MergeByDistanceAlgorithm, PointSpacingType, dvec2_to_point, point_to_dvec2}; +use crate::modification::VectorDataExt; use bezier_rs::{Join, ManipulatorGroup, Subpath}; use glam::{DAffine2, DVec2}; -use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, ParamCurve, PathEl, PathSeg, Shape}; +use graphene_core::color::Color; +use graphene_core::context::{CloneVarArgs, Context, Ctx, ExtractAll, OwnedContextImpl}; +use graphene_core::gradient::{Gradient, GradientStops}; +use graphene_core::instances::{Instance, InstanceMut, Instances}; +use graphene_core::registry::types::{Angle, Fraction, IntegerCount, Length, Multiplier, Percentage, PixelLength, PixelSize, SeedValue}; +use graphene_core::transform::{Footprint, Transform}; +use graphene_vector::style::{Fill, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin}; +use graphene_vector::{FillId, PointDomain, PointId, RegionId, SegmentDomain, SegmentId, StrokeId, VectorData, VectorDataTable}; +use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, ParamCurve, PathEl, PathSeg, Point, Shape}; +use log::warn; use rand::{Rng, SeedableRng}; use std::collections::hash_map::DefaultHasher; use std::f64::consts::PI; use std::f64::consts::TAU; use std::hash::{Hash, Hasher}; -/// Implemented for types that can be converted to an iterator of vector data. -/// Used for the fill and stroke node so they can be used on VectorData or GraphicGroup -trait VectorDataTableIterMut { - fn vector_iter_mut(&mut self) -> impl Iterator>; -} - -impl VectorDataTableIterMut for GraphicGroupTable { - fn vector_iter_mut(&mut self) -> impl Iterator> { - // Grab only the direct children - self.instance_mut_iter() - .filter_map(|element| element.instance.as_vector_data_mut()) - .flat_map(move |vector_data| vector_data.instance_mut_iter()) - } -} - -impl VectorDataTableIterMut for VectorDataTable { - fn vector_iter_mut(&mut self) -> impl Iterator> { - self.instance_mut_iter() - } -} - -#[node_macro::node(category("Vector: Style"), path(graphene_core::vector))] -async fn assign_colors( - _: impl Ctx, - #[implementations(GraphicGroupTable, VectorDataTable)] - #[widget(ParsedWidgetOverride::Hidden)] - /// The vector elements, or group of vector elements, to apply the fill and/or stroke style to. - mut vector_group: T, - #[default(true)] - /// Whether to style the fill. - fill: bool, - /// Whether to style the stroke. - stroke: bool, - #[widget(ParsedWidgetOverride::Custom = "assign_colors_gradient")] - /// The range of colors to select from. - gradient: GradientStops, - /// Whether to reverse the gradient. - reverse: bool, - /// Whether to randomize the color selection for each element from throughout the gradient. - randomize: bool, - #[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")] - /// The seed used for randomization. - seed: SeedValue, - #[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")] - /// The number of elements to span across the gradient before repeating. A 0 value will span the entire gradient once. - repeat_every: u32, -) -> T -where - T: VectorDataTableIterMut + 'n + Send, -{ - let length = vector_group.vector_iter_mut().count(); - let gradient = if reverse { gradient.reversed() } else { gradient }; - - let mut rng = rand::rngs::StdRng::seed_from_u64(seed.into()); - - for (i, vector_data) in vector_group.vector_iter_mut().enumerate() { - let factor = match randomize { - true => rng.random::(), - false => match repeat_every { - 0 => i as f64 / (length - 1).max(1) as f64, - 1 => 0., - _ => i as f64 % repeat_every as f64 / (repeat_every - 1) as f64, - }, - }; - - let color = gradient.evaluate(factor); - - if fill { - vector_data.instance.style.set_fill(Fill::Solid(color)); - } - if stroke { - if let Some(stroke) = vector_data.instance.style.stroke().and_then(|stroke| stroke.with_color(&Some(color))) { - vector_data.instance.style.set_stroke(stroke); - } - } - } - - vector_group -} - -#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))] -async fn fill + 'n + Send, V>( - _: impl Ctx, - #[implementations( - VectorDataTable, - VectorDataTable, - VectorDataTable, - VectorDataTable, - GraphicGroupTable, - GraphicGroupTable, - GraphicGroupTable, - GraphicGroupTable - )] - /// The vector elements, or group of vector elements, to apply the fill to. - mut vector_data: V, - #[implementations( - Fill, - Option, - Color, - Gradient, - Fill, - Option, - Color, - Gradient, - )] - #[default(Color::BLACK)] - /// The fill to paint the path with. - fill: F, - _backup_color: Option, - _backup_gradient: Gradient, -) -> V -where - V: VectorDataTableIterMut + 'n + Send, -{ - let fill: Fill = fill.into(); - for vector in vector_data.vector_iter_mut() { - let mut fill = fill.clone(); - if let Fill::Gradient(gradient) = &mut fill { - gradient.transform *= *vector.transform; - } - vector.instance.style.set_fill(fill); - } - - vector_data -} - -/// Applies a stroke style to the vector data contained in the input. -#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("stroke_properties"))] -async fn stroke> + 'n + Send, V>( - _: impl Ctx, - #[implementations(VectorDataTable, VectorDataTable, GraphicGroupTable, GraphicGroupTable)] - /// The vector elements, or group of vector elements, to apply the stroke to. - mut vector_data: Instances, - #[implementations( - Option, - Color, - Option, - Color, - )] - #[default(Color::BLACK)] - /// The stroke color. - color: C, - #[default(2.)] - /// The stroke weight. - weight: f64, - /// The alignment of stroke to the path's centerline or (for closed shapes) the inside or outside of the shape. - align: StrokeAlign, - /// The shape of the stroke at open endpoints. - cap: StrokeCap, - /// The curvature of the bent stroke at sharp corners. - join: StrokeJoin, - #[default(4.)] - /// The threshold for when a miter-joined stroke is converted to a bevel-joined stroke when a sharp angle becomes pointier than this ratio. - miter_limit: f64, - /// The order to paint the stroke on top of the fill, or the fill on top of the stroke. - /// - paint_order: PaintOrder, - /// The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed. - dash_lengths: Vec, - /// The phase offset distance from the starting point of the dash pattern. - dash_offset: f64, -) -> Instances -where - Instances: VectorDataTableIterMut + 'n + Send, -{ - let stroke = Stroke { - color: color.into(), - weight, - dash_lengths, - dash_offset, - cap, - join, - join_miter_limit: miter_limit, - align, - transform: DAffine2::IDENTITY, - non_scaling: false, - paint_order, - }; - - for vector in vector_data.vector_iter_mut() { - let mut stroke = stroke.clone(); - stroke.transform *= *vector.transform; - vector.instance.style.set_stroke(stroke); - } - - vector_data -} - -#[node_macro::node(category("Instancing"), path(graphene_core::vector))] -async fn repeat( - _: impl Ctx, - // TODO: Implement other GraphicElementRendered types. - #[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable)] instance: Instances, - #[default(100., 100.)] - // TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed. - direction: PixelSize, - angle: Angle, - #[default(4)] instances: IntegerCount, -) -> Instances { - let angle = angle.to_radians(); - let count = instances.max(1); - let total = (count - 1) as f64; - - let mut result_table = Instances::::default(); - - for index in 0..count { - let angle = index as f64 * angle / total; - let translation = index as f64 * direction / total; - let transform = DAffine2::from_angle(angle) * DAffine2::from_translation(translation); - - for instance in instance.instance_ref_iter() { - let mut instance = instance.to_instance_cloned(); - - let local_translation = DAffine2::from_translation(instance.transform.translation); - let local_matrix = DAffine2::from_mat2(instance.transform.matrix2); - instance.transform = local_translation * transform * local_matrix; - - result_table.push(instance); - } - } - - result_table -} - -#[node_macro::node(category("Instancing"), path(graphene_core::vector))] -async fn circular_repeat( - _: impl Ctx, - // TODO: Implement other GraphicElementRendered types. - #[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable)] instance: Instances, - angle_offset: Angle, - #[default(5)] radius: f64, - #[default(5)] instances: IntegerCount, -) -> Instances { - let count = instances.max(1); - - let mut result_table = Instances::::default(); - - for index in 0..count { - let angle = DAffine2::from_angle((TAU / count as f64) * index as f64 + angle_offset.to_radians()); - let translation = DAffine2::from_translation(radius * DVec2::Y); - let transform = angle * translation; - - for instance in instance.instance_ref_iter() { - let mut instance = instance.to_instance_cloned(); - - let local_translation = DAffine2::from_translation(instance.transform.translation); - let local_matrix = DAffine2::from_mat2(instance.transform.matrix2); - instance.transform = local_translation * transform * local_matrix; - - result_table.push(instance); - } - } - - result_table -} - -#[node_macro::node(name("Copy to Points"), category("Instancing"), path(graphene_core::vector))] -async fn copy_to_points( - _: impl Ctx, - points: VectorDataTable, - #[expose] - /// Artwork to be copied and placed at each point. - #[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable)] - instance: Instances, - /// Minimum range of randomized sizes given to each instance. - #[default(1)] - #[range((0., 2.))] - #[unit("x")] - random_scale_min: Multiplier, - /// Maximum range of randomized sizes given to each instance. - #[default(1)] - #[range((0., 2.))] - #[unit("x")] - random_scale_max: Multiplier, - /// Bias for the probability distribution of randomized sizes (0 is uniform, negatives favor more of small sizes, positives favor more of large sizes). - #[range((-50., 50.))] - random_scale_bias: f64, - /// Seed to determine unique variations on all the randomized instance sizes. - random_scale_seed: SeedValue, - /// Range of randomized angles given to each instance, in degrees ranging from furthest clockwise to counterclockwise. - #[range((0., 360.))] - random_rotation: Angle, - /// Seed to determine unique variations on all the randomized instance angles. - random_rotation_seed: SeedValue, -) -> Instances { - let mut result_table = Instances::::default(); - - let random_scale_difference = random_scale_max - random_scale_min; - - for point_instance in points.instance_iter() { - let mut scale_rng = rand::rngs::StdRng::seed_from_u64(random_scale_seed.into()); - let mut rotation_rng = rand::rngs::StdRng::seed_from_u64(random_rotation_seed.into()); - - let do_scale = random_scale_difference.abs() > 1e-6; - let do_rotation = random_rotation.abs() > 1e-6; - - let points_transform = point_instance.transform; - for &point in point_instance.instance.point_domain.positions() { - let translation = points_transform.transform_point2(point); - - let rotation = if do_rotation { - let degrees = (rotation_rng.random::() - 0.5) * random_rotation; - degrees / 360. * TAU - } else { - 0. - }; - - let scale = if do_scale { - if random_scale_bias.abs() < 1e-6 { - // Linear - random_scale_min + scale_rng.random::() * random_scale_difference - } else { - // Weighted (see ) - let horizontal_scale_factor = 1. - 2_f64.powf(random_scale_bias); - let scale_factor = (1. - scale_rng.random::() * horizontal_scale_factor).log2() / random_scale_bias; - random_scale_min + scale_factor * random_scale_difference - } - } else { - random_scale_min - }; - - let transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation); - - for mut instance in instance.instance_ref_iter().map(|instance| instance.to_instance_cloned()) { - let local_matrix = DAffine2::from_mat2(instance.transform.matrix2); - instance.transform = transform * local_matrix; - - result_table.push(instance); - } - } - } - - result_table -} - -#[node_macro::node(category("Instancing"), path(graphene_core::vector))] -async fn mirror( - _: impl Ctx, - #[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable)] instance: Instances, - #[default(ReferencePoint::Center)] relative_to_bounds: ReferencePoint, - offset: f64, - #[range((-90., 90.))] angle: Angle, - #[default(true)] keep_original: bool, -) -> Instances -where - Instances: BoundingBox, -{ - let mut result_table = Instances::default(); - - // Normalize the direction vector - let normal = DVec2::from_angle(angle.to_radians()); - - // The mirror reference is based on the bounding box (at least for now, until we have proper local layer origins) - let Some(bounding_box) = instance.bounding_box(DAffine2::IDENTITY, false) else { - return result_table; - }; - - let reference_point_location = relative_to_bounds.point_in_bounding_box((bounding_box[0], bounding_box[1]).into()); - let mirror_reference_point = reference_point_location.map(|point| point + normal * offset); - - // Create the reflection matrix - let reflection = DAffine2::from_mat2_translation( - glam::DMat2::from_cols( - DVec2::new(1. - 2. * normal.x * normal.x, -2. * normal.y * normal.x), - DVec2::new(-2. * normal.x * normal.y, 1. - 2. * normal.y * normal.y), - ), - DVec2::ZERO, - ); - - // Apply reflection around the reference point - let reflected_transform = if let Some(mirror_reference_point) = mirror_reference_point { - DAffine2::from_translation(mirror_reference_point) * reflection * DAffine2::from_translation(-mirror_reference_point) - } else { - reflection * DAffine2::from_translation(DVec2::from_angle(angle.to_radians()) * DVec2::splat(-offset)) - }; - - // Add original instance depending on the keep_original flag - if keep_original { - for instance in instance.clone().instance_iter() { - result_table.push(instance); - } - } - - // Create and add mirrored instance - for mut instance in instance.instance_iter() { - instance.transform = reflected_transform * instance.transform; - instance.source_node_id = None; - result_table.push(instance); - } - - result_table -} - #[node_macro::node(category("Vector: Modifier"), path(graphene_core::vector))] async fn round_corners( _: impl Ctx, @@ -1078,61 +683,6 @@ async fn solidify_stroke(_: impl Ctx, vector_data: VectorDataTable) -> VectorDat result_table } -#[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn flatten_path(_: impl Ctx, #[implementations(GraphicGroupTable, VectorDataTable)] graphic_group_input: Instances) -> VectorDataTable -where - GraphicElement: From>, -{ - // A node based solution to support passing through vector data could be a network node with a cache node connected to - // a Flatten Path connected to an if else node, another connection from the cache directly - // To the if else node, and another connection from the cache to a matches type node connected to the if else node. - fn flatten_group(graphic_group_table: &GraphicGroupTable, output: &mut InstanceMut) { - for (group_index, current_element) in graphic_group_table.instance_ref_iter().enumerate() { - match current_element.instance { - GraphicElement::VectorData(vector_data_table) => { - // Loop through every row of the VectorDataTable and concatenate each instance's subpath into the output VectorData instance. - for (vector_index, vector_data_instance) in vector_data_table.instance_ref_iter().enumerate() { - let other = vector_data_instance.instance; - let transform = *current_element.transform * *vector_data_instance.transform; - let node_id = current_element.source_node_id.map(|node_id| node_id.0).unwrap_or_default(); - - let mut hasher = DefaultHasher::new(); - (group_index, vector_index, node_id).hash(&mut hasher); - let collision_hash_seed = hasher.finish(); - - output.instance.concat(other, transform, collision_hash_seed); - - // Use the last encountered style as the output style - output.instance.style = vector_data_instance.instance.style.clone(); - } - } - GraphicElement::GraphicGroup(graphic_group) => { - let mut graphic_group = graphic_group.clone(); - for instance in graphic_group.instance_mut_iter() { - *instance.transform = *current_element.transform * *instance.transform; - } - - flatten_group(&graphic_group, output); - } - _ => {} - } - } - } - - // Create a table with one instance of an empty VectorData, then get a mutable reference to it which we append flattened subpaths to - let mut output_table = VectorDataTable::new(VectorData::default()); - let Some(mut output) = output_table.instance_mut_iter().next() else { - return output_table; - }; - - // Flatten the graphic group input into the output VectorData instance - let base_graphic_group = GraphicGroupTable::new(GraphicElement::from(graphic_group_input)); - flatten_group(&base_graphic_group, &mut output); - - // Return the single-row VectorDataTable containing the flattened VectorData subpaths - output_table -} - /// Convert vector geometry into a polyline composed of evenly spaced points. #[node_macro::node(category(""), path(graphene_core::vector))] async fn sample_polyline( @@ -1860,11 +1410,6 @@ fn point_inside(_: impl Ctx, source: VectorDataTable, point: DVec2) -> bool { source.instance_iter().any(|instance| instance.instance.check_point_inside_shape(instance.transform, point)) } -#[node_macro::node(category("General"), path(graphene_core::vector))] -async fn count_elements(_: impl Ctx, #[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable, RasterDataTable)] source: Instances) -> u64 { - source.instance_iter().count() as u64 -} - #[node_macro::node(category("Vector: Measure"), path(graphene_core::vector))] async fn path_length(_: impl Ctx, source: VectorDataTable) -> f64 { source @@ -1953,8 +1498,9 @@ async fn centroid(ctx: impl Ctx + CloneVarArgs + ExtractAll, vector_data: impl N #[cfg(test)] mod test { use super::*; - use crate::Node; use bezier_rs::Bezier; + use graphene_core::Node; + use graphene_core::transform::Footprint; use kurbo::Rect; use std::pin::Pin; @@ -1990,47 +1536,6 @@ mod test { } vector_data_table } - - #[tokio::test] - async fn repeat() { - let direction = DVec2::X * 1.5; - let instances = 3; - let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await; - let vector_data = super::flatten_path(Footprint::default(), repeated).await; - let vector_data = vector_data.instance_ref_iter().next().unwrap().instance; - assert_eq!(vector_data.region_bezier_paths().count(), 3); - for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() { - assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5); - } - } - #[tokio::test] - async fn repeat_transform_position() { - let direction = DVec2::new(12., 10.); - let instances = 8; - let repeated = super::repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::ZERO, DVec2::ONE)), direction, 0., instances).await; - let vector_data = super::flatten_path(Footprint::default(), repeated).await; - let vector_data = vector_data.instance_ref_iter().next().unwrap().instance; - assert_eq!(vector_data.region_bezier_paths().count(), 8); - for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() { - assert!((subpath.manipulator_groups()[0].anchor - direction * index as f64 / (instances - 1) as f64).length() < 1e-5); - } - } - #[tokio::test] - async fn circular_repeat() { - let repeated = super::circular_repeat(Footprint::default(), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE)), 45., 4., 8).await; - let vector_data = super::flatten_path(Footprint::default(), repeated).await; - let vector_data = vector_data.instance_ref_iter().next().unwrap().instance; - assert_eq!(vector_data.region_bezier_paths().count(), 8); - - for (index, (_, subpath)) in vector_data.region_bezier_paths().enumerate() { - let expected_angle = (index as f64 + 1.) * 45.; - - let center = (subpath.manipulator_groups()[0].anchor + subpath.manipulator_groups()[2].anchor) / 2.; - let actual_angle = DVec2::Y.angle_to(center).to_degrees(); - - assert!((actual_angle - expected_angle).abs() % 360. < 1e-5, "Expected {expected_angle} found {actual_angle}"); - } - } #[tokio::test] async fn bounding_box() { let bounding_box = super::bounding_box((), vector_node(Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE))).await; @@ -2057,27 +1562,6 @@ mod test { } } #[tokio::test] - async fn copy_to_points() { - let points = Subpath::new_rect(DVec2::NEG_ONE * 10., DVec2::ONE * 10.); - let instance = Subpath::new_rect(DVec2::NEG_ONE, DVec2::ONE); - - let expected_points = VectorData::from_subpath(points.clone()).point_domain.positions().to_vec(); - - let copy_to_points = super::copy_to_points(Footprint::default(), vector_node(points), vector_node(instance), 1., 1., 0., 0, 0., 0).await; - let flatten_path = super::flatten_path(Footprint::default(), copy_to_points).await; - let flattened_copy_to_points = flatten_path.instance_ref_iter().next().unwrap().instance; - - assert_eq!(flattened_copy_to_points.region_bezier_paths().count(), expected_points.len()); - - for (index, (_, subpath)) in flattened_copy_to_points.region_bezier_paths().enumerate() { - let offset = expected_points[index]; - assert_eq!( - &subpath.anchors(), - &[offset + DVec2::NEG_ONE, offset + DVec2::new(1., -1.), offset + DVec2::ONE, offset + DVec2::new(-1., 1.),] - ); - } - } - #[tokio::test] async fn sample_polyline() { let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)); let sample_polyline = super::sample_polyline(Footprint::default(), vector_node(path), PointSpacingType::Separation, 30., 0., 0., 0., false, vec![100.]).await; diff --git a/node-graph/gvector/Cargo.toml b/node-graph/gvector/Cargo.toml new file mode 100644 index 000000000..598ee01cd --- /dev/null +++ b/node-graph/gvector/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "graphene-vector" +version = "0.1.0" +edition = "2024" +description = "graphene vector data format" +authors = ["Graphite Authors "] +license = "MIT OR Apache-2.0" + +[features] +default = ["serde"] +serde = [ + "dep:serde", + "bezier-rs/serde", +] + +[dependencies] +# Local dependencies +dyn-any = { workspace = true } +bezier-rs = { workspace = true } +graphene-core = { workspace = true } +node-macro = { workspace = true } + +# Workspace dependencies +kurbo = { workspace = true } +glam = { workspace = true } +specta = { workspace = true } +log = { workspace = true } +tinyvec = { workspace = true } +rustc-hash = { workspace = true } +petgraph = { workspace = true } + +# Optional workspace dependencies +serde = { workspace = true, optional = true, features = ["derive"] } diff --git a/node-graph/gcore/src/vector/click_target.rs b/node-graph/gvector/src/click_target.rs similarity index 98% rename from node-graph/gcore/src/vector/click_target.rs rename to node-graph/gvector/src/click_target.rs index 365c2c7ee..3a60fcfb1 100644 --- a/node-graph/gcore/src/vector/click_target.rs +++ b/node-graph/gvector/src/click_target.rs @@ -1,8 +1,8 @@ -use crate::math::math_ext::QuadExt; -use crate::math::quad::Quad; -use crate::vector::PointId; +use crate::math_ext::QuadExt; +use crate::vector_data::PointId; use bezier_rs::Subpath; use glam::{DAffine2, DMat2, DVec2}; +use graphene_core::math::quad::Quad; #[derive(Copy, Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)] pub struct FreePoint { diff --git a/node-graph/gvector/src/lib.rs b/node-graph/gvector/src/lib.rs new file mode 100644 index 000000000..d33649764 --- /dev/null +++ b/node-graph/gvector/src/lib.rs @@ -0,0 +1,17 @@ +pub mod click_target; +pub mod math_ext; +pub mod reference_point; +pub mod style; +mod vector_data; + +pub use bezier_rs; + +pub use vector_data::*; + +pub fn point_to_dvec2(point: kurbo::Point) -> glam::DVec2 { + glam::DVec2 { x: point.x, y: point.y } +} + +pub fn dvec2_to_point(value: glam::DVec2) -> kurbo::Point { + kurbo::Point { x: value.x, y: value.y } +} diff --git a/node-graph/gcore/src/math/math_ext.rs b/node-graph/gvector/src/math_ext.rs similarity index 90% rename from node-graph/gcore/src/math/math_ext.rs rename to node-graph/gvector/src/math_ext.rs index bab9c19bf..8f7887708 100644 --- a/node-graph/gcore/src/math/math_ext.rs +++ b/node-graph/gvector/src/math_ext.rs @@ -1,6 +1,6 @@ -use crate::math::quad::Quad; -use crate::math::rect::Rect; use bezier_rs::Bezier; +use graphene_core::math::quad::Quad; +use graphene_core::math::rect::Rect; pub trait QuadExt { /// Get all the edges in the rect as linear bezier curves diff --git a/node-graph/gcore/src/vector/reference_point.rs b/node-graph/gvector/src/reference_point.rs similarity index 98% rename from node-graph/gcore/src/vector/reference_point.rs rename to node-graph/gvector/src/reference_point.rs index 7c6180328..7d049f5e1 100644 --- a/node-graph/gcore/src/vector/reference_point.rs +++ b/node-graph/gvector/src/reference_point.rs @@ -1,5 +1,5 @@ -use crate::math::bbox::AxisAlignedBbox; use glam::DVec2; +use graphene_core::math::bbox::AxisAlignedBbox; #[derive(Clone, Copy, Debug, Default, Hash, Eq, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)] pub enum ReferencePoint { diff --git a/node-graph/gcore/src/vector/style.rs b/node-graph/gvector/src/style.rs similarity index 92% rename from node-graph/gcore/src/vector/style.rs rename to node-graph/gvector/src/style.rs index c1901ce90..f20168e31 100644 --- a/node-graph/gcore/src/vector/style.rs +++ b/node-graph/gvector/src/style.rs @@ -1,9 +1,9 @@ //! Contains stylistic options for SVG elements. -use crate::Color; -pub use crate::gradient::*; use dyn_any::DynAny; use glam::DAffine2; +use graphene_core::color::Color; +use graphene_core::gradient::{Gradient, GradientStops}; /// Describes the fill of a layer. /// @@ -528,8 +528,8 @@ impl PathStyle { /// /// # Example /// ``` - /// # use graphene_core::vector::style::{Fill, PathStyle}; - /// # use graphene_core::raster::color::Color; + /// # use graphene_vector::style::{Fill, PathStyle}; + /// # use graphene_core::color::Color; /// let fill = Fill::solid(Color::RED); /// let style = PathStyle::new(None, fill.clone()); /// @@ -543,8 +543,8 @@ impl PathStyle { /// /// # Example /// ``` - /// # use graphene_core::vector::style::{Fill, Stroke, PathStyle}; - /// # use graphene_core::raster::color::Color; + /// # use graphene_vector::style::{Fill, Stroke, PathStyle}; + /// # use graphene_core::color::Color; /// let stroke = Stroke::new(Some(Color::GREEN), 42.); /// let style = PathStyle::new(Some(stroke.clone()), Fill::None); /// @@ -558,8 +558,8 @@ impl PathStyle { /// /// # Example /// ``` - /// # use graphene_core::vector::style::{Fill, PathStyle}; - /// # use graphene_core::raster::color::Color; + /// # use graphene_vector::style::{Fill, PathStyle}; + /// # use graphene_core::color::Color; /// let mut style = PathStyle::default(); /// /// assert_eq!(*style.fill(), Fill::None); @@ -583,8 +583,8 @@ impl PathStyle { /// /// # Example /// ``` - /// # use graphene_core::vector::style::{Stroke, PathStyle}; - /// # use graphene_core::raster::color::Color; + /// # use graphene_vector::style::{Stroke, PathStyle}; + /// # use graphene_core::color::Color; /// let mut style = PathStyle::default(); /// /// assert_eq!(style.stroke(), None); @@ -602,8 +602,8 @@ impl PathStyle { /// /// # Example /// ``` - /// # use graphene_core::vector::style::{Fill, PathStyle}; - /// # use graphene_core::raster::color::Color; + /// # use graphene_vector::style::{Fill, PathStyle}; + /// # use graphene_core::color::Color; /// let mut style = PathStyle::new(None, Fill::Solid(Color::RED)); /// /// assert_ne!(*style.fill(), Fill::None); @@ -620,8 +620,8 @@ impl PathStyle { /// /// # Example /// ``` - /// # use graphene_core::vector::style::{Fill, Stroke, PathStyle}; - /// # use graphene_core::raster::color::Color; + /// # use graphene_vector::style::{Fill, Stroke, PathStyle}; + /// # use graphene_core::color::Color; /// let mut style = PathStyle::new(Some(Stroke::new(Some(Color::GREEN), 42.)), Fill::None); /// /// assert!(style.stroke().is_some()); @@ -634,15 +634,3 @@ impl PathStyle { self.stroke = None; } } - -/// Represents different ways of rendering an object -#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type)] -pub enum ViewMode { - /// Render with normal coloration at the current viewport resolution - #[default] - Normal, - /// Render only the outlines of shapes at the current viewport resolution - Outline, - /// Render with normal coloration at the document resolution, showing the pixels when the current viewport resolution is higher - Pixels, -} diff --git a/node-graph/gcore/src/vector/vector_data.rs b/node-graph/gvector/src/vector_data.rs similarity index 97% rename from node-graph/gcore/src/vector/vector_data.rs rename to node-graph/gvector/src/vector_data.rs index c9888984c..1e355223f 100644 --- a/node-graph/gcore/src/vector/vector_data.rs +++ b/node-graph/gvector/src/vector_data.rs @@ -1,25 +1,31 @@ mod attributes; mod indexed; -mod modification; -use super::misc::{dvec2_to_point, point_to_dvec2}; -use super::style::{PathStyle, Stroke}; -use crate::bounds::BoundingBox; -use crate::instances::Instances; -use crate::math::quad::Quad; -use crate::transform::Transform; -use crate::vector::click_target::{ClickTargetType, FreePoint}; -use crate::{AlphaBlending, Color, GraphicGroupTable}; +use crate::click_target::{ClickTargetType, FreePoint}; +use crate::dvec2_to_point; +use crate::style::{PathStyle, Stroke}; pub use attributes::*; use bezier_rs::{BezierHandles, ManipulatorGroup}; use core::borrow::Borrow; use core::hash::Hash; use dyn_any::DynAny; use glam::{DAffine2, DVec2}; +use graphene_core::blending::AlphaBlending; +use graphene_core::bounds::BoundingBox; +use graphene_core::color::Color; +use graphene_core::instances::Instances; +use graphene_core::math::quad::Quad; +use graphene_core::transform::Transform; pub use indexed::VectorDataIndex; use kurbo::{Affine, Rect, Shape}; -pub use modification::*; +use std::any::Any; use std::collections::HashMap; +use std::sync::Arc; + +pub trait AnyUpstreamGraphicGroup: Any + serde::Serialize + for<'a> serde::Deserialize<'a> {} + +#[derive(Clone, Debug, PartialEq, DynAny, serde::Serialize, serde::Deserialize)] +pub struct UpstreamGraphicGroup(Arc); // TODO: Eventually remove this migration document upgrade code pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result { @@ -41,7 +47,7 @@ pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) -> pub region_domain: RegionDomain, // Used to store the upstream graphic group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved. - pub upstream_graphic_group: Option, + pub upstream_graphic_group: Option, } #[derive(serde::Serialize, serde::Deserialize)] @@ -91,7 +97,7 @@ pub struct VectorData { pub region_domain: RegionDomain, // Used to store the upstream graphic group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved. - pub upstream_graphic_group: Option, + pub upstream_graphic_group: Option, } impl Default for VectorData { diff --git a/node-graph/gcore/src/vector/vector_data/attributes.rs b/node-graph/gvector/src/vector_data/attributes.rs similarity index 95% rename from node-graph/gcore/src/vector/vector_data/attributes.rs rename to node-graph/gvector/src/vector_data/attributes.rs index bb2ca1f91..e5a8c7505 100644 --- a/node-graph/gcore/src/vector/vector_data/attributes.rs +++ b/node-graph/gvector/src/vector_data/attributes.rs @@ -1,8 +1,10 @@ -use crate::vector::misc::dvec2_to_point; -use crate::vector::vector_data::{HandleId, VectorData}; +use crate::dvec2_to_point; +use crate::vector_data::{HandleId, VectorData}; use bezier_rs::{BezierHandles, ManipulatorGroup}; use dyn_any::DynAny; use glam::{DAffine2, DVec2}; +use graphene_core::uuid::generate_uuid; +use log::warn; use std::collections::HashMap; use std::hash::{Hash, Hasher}; use std::iter::zip; @@ -21,7 +23,7 @@ macro_rules! create_ids { /// Generate a new random id pub fn generate() -> Self { - Self(crate::uuid::generate_uuid()) + Self(generate_uuid()) } pub fn generate_from_hash(self, node_id: u64) -> Self { @@ -82,7 +84,7 @@ impl std::hash::BuildHasher for NoHashBuilder { pub struct PointDomain { id: Vec, #[serde(alias = "positions")] - pub(crate) position: Vec, + pub position: Vec, } impl Hash for PointDomain { @@ -166,7 +168,7 @@ impl PointDomain { pos } - pub(crate) fn resolve_id(&self, id: PointId) -> Option { + pub fn resolve_id(&self, id: PointId) -> Option { self.id.iter().position(|&check_id| check_id == id) } @@ -280,11 +282,11 @@ impl SegmentDomain { self.ids().iter().copied().max_by(|a, b| a.0.cmp(&b.0)).map(|mut id| id.next_id()).unwrap_or(SegmentId::ZERO) } - pub(crate) fn start_point(&self) -> &[usize] { + pub fn start_point(&self) -> &[usize] { &self.start_point } - pub(crate) fn end_point(&self) -> &[usize] { + pub fn end_point(&self) -> &[usize] { &self.end_point } @@ -304,7 +306,7 @@ impl SegmentDomain { &self.stroke } - pub(crate) fn push(&mut self, id: SegmentId, start: usize, end: usize, handles: BezierHandles, stroke: StrokeId) { + pub fn push(&mut self, id: SegmentId, start: usize, end: usize, handles: BezierHandles, stroke: StrokeId) { debug_assert!(!self.id.contains(&id), "Tried to push an existing point to a point domain"); self.id.push(id); @@ -314,20 +316,20 @@ impl SegmentDomain { self.stroke.push(stroke); } - pub(crate) fn start_point_mut(&mut self) -> impl Iterator { + pub fn start_point_mut(&mut self) -> impl Iterator { self.id.iter().copied().zip(self.start_point.iter_mut()) } - pub(crate) fn end_point_mut(&mut self) -> impl Iterator { + pub fn end_point_mut(&mut self) -> impl Iterator { self.id.iter().copied().zip(self.end_point.iter_mut()) } - pub(crate) fn handles_mut(&mut self) -> impl Iterator { + pub fn handles_mut(&mut self) -> impl Iterator { let nested = self.id.iter().zip(&mut self.handles).zip(&self.start_point).zip(&self.end_point); nested.map(|(((&a, b), &c), &d)| (a, b, c, d)) } - pub(crate) fn handles_and_points_mut(&mut self) -> impl Iterator { + pub fn handles_and_points_mut(&mut self) -> impl Iterator { let nested = self.handles.iter_mut().zip(&mut self.start_point).zip(&mut self.end_point); nested.map(|((a, b), c)| (a, b, c)) } @@ -336,26 +338,26 @@ impl SegmentDomain { self.id.iter().copied().zip(self.stroke.iter_mut()) } - pub(crate) fn segment_start_from_id(&self, segment: SegmentId) -> Option { + pub fn segment_start_from_id(&self, segment: SegmentId) -> Option { self.id_to_index(segment).and_then(|index| self.start_point.get(index)).copied() } - pub(crate) fn segment_end_from_id(&self, segment: SegmentId) -> Option { + pub fn segment_end_from_id(&self, segment: SegmentId) -> Option { self.id_to_index(segment).and_then(|index| self.end_point.get(index)).copied() } /// Returns an array for the start and end points of a segment. - pub(crate) fn points_from_id(&self, segment: SegmentId) -> Option<[usize; 2]> { + pub fn points_from_id(&self, segment: SegmentId) -> Option<[usize; 2]> { self.segment_start_from_id(segment).and_then(|start| self.segment_end_from_id(segment).map(|end| [start, end])) } /// Attempts to find another point in the segment that is not the one passed in. - pub(crate) fn other_point(&self, segment: SegmentId, current: usize) -> Option { + pub fn other_point(&self, segment: SegmentId, current: usize) -> Option { self.points_from_id(segment).and_then(|points| points.into_iter().find(|&point| point != current)) } /// Gets all points connected to the current one but not including the current one. - pub(crate) fn connected_points(&self, current: usize) -> impl Iterator + '_ { + pub fn connected_points(&self, current: usize) -> impl Iterator + '_ { self.start_point.iter().zip(&self.end_point).filter_map(move |(&a, &b)| match (a == current, b == current) { (true, false) => Some(b), (false, true) => Some(a), @@ -400,22 +402,22 @@ impl SegmentDomain { } /// Enumerate all segments that start at the point. - pub(crate) fn start_connected(&self, point: usize) -> impl Iterator + '_ { + pub fn start_connected(&self, point: usize) -> impl Iterator + '_ { self.start_point.iter().zip(&self.id).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg) } /// Enumerate all segments that end at the point. - pub(crate) fn end_connected(&self, point: usize) -> impl Iterator + '_ { + pub fn end_connected(&self, point: usize) -> impl Iterator + '_ { self.end_point.iter().zip(&self.id).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg) } /// Enumerate all segments that start or end at a point, converting them to [`HandleId`s]. Note that the handles may not exist e.g. for a linear segment. - pub(crate) fn all_connected(&self, point: usize) -> impl Iterator + '_ { + pub fn all_connected(&self, point: usize) -> impl Iterator + '_ { self.start_connected(point).map(HandleId::primary).chain(self.end_connected(point).map(HandleId::end)) } /// Enumerate the number of segments connected to a point. If a segment starts and ends at a point then it is counted twice. - pub(crate) fn connected_count(&self, point: usize) -> usize { + pub fn connected_count(&self, point: usize) -> usize { self.all_connected(point).count() } @@ -433,7 +435,7 @@ impl SegmentDomain { /// Iterates over segments in the domain, mutably. /// /// Tuple is: (id, start point, end point, handles) - pub(crate) fn iter_mut(&mut self) -> impl Iterator + '_ { + pub fn iter_mut(&mut self) -> impl Iterator + '_ { let ids = self.id.iter_mut(); let start_point = self.start_point.iter_mut(); let end_point = self.end_point.iter_mut(); diff --git a/node-graph/gcore/src/vector/vector_data/indexed.rs b/node-graph/gvector/src/vector_data/indexed.rs similarity index 94% rename from node-graph/gcore/src/vector/vector_data/indexed.rs rename to node-graph/gvector/src/vector_data/indexed.rs index e3e1c3cdf..2f2c64487 100644 --- a/node-graph/gcore/src/vector/vector_data/indexed.rs +++ b/node-graph/gvector/src/vector_data/indexed.rs @@ -16,10 +16,10 @@ pub struct VectorDataIndex { /// Points and segments form a graph. Store it here in a form amenable to graph algorithms. /// /// Currently, segment data is not stored as it is not used, but it could easily be added. - pub(crate) point_graph: UnGraph, - pub(crate) segment_to_edge: FxHashMap, + pub point_graph: UnGraph, + pub segment_to_edge: FxHashMap, /// Get the offset from the point ID. - pub(crate) point_to_offset: FxHashMap, + pub point_to_offset: FxHashMap, // TODO: faces } diff --git a/node-graph/interpreted-executor/benches/run_cached.rs b/node-graph/interpreted-executor/benches/run_cached.rs index e6b132668..6c9ceabf7 100644 --- a/node-graph/interpreted-executor/benches/run_cached.rs +++ b/node-graph/interpreted-executor/benches/run_cached.rs @@ -2,7 +2,7 @@ mod benchmark_util; use benchmark_util::{bench_for_each_demo, setup_network}; use criterion::{Criterion, criterion_group, criterion_main}; -use graphene_std::Context; +use graphene_std::context::Context; fn subsequent_evaluations(c: &mut Criterion) { let mut group = c.benchmark_group("Subsequent Evaluations"); diff --git a/node-graph/interpreted-executor/benches/run_once.rs b/node-graph/interpreted-executor/benches/run_once.rs index a55b6843e..c6d0f204b 100644 --- a/node-graph/interpreted-executor/benches/run_once.rs +++ b/node-graph/interpreted-executor/benches/run_once.rs @@ -2,7 +2,7 @@ mod benchmark_util; use benchmark_util::{bench_for_each_demo, setup_network}; use criterion::{Criterion, criterion_group, criterion_main}; -use graphene_std::Context; +use graphene_std::context::Context; fn run_once(c: &mut Criterion) { let mut group = c.benchmark_group("Run Once"); diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index ddd71abdb..836ff20bd 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -2,22 +2,21 @@ use dyn_any::StaticType; use glam::{DVec2, IVec2, UVec2}; use graph_craft::document::value::RenderOutput; use graph_craft::proto::{NodeConstructor, TypeErasedBox}; -use graphene_core::raster::color::Color; -use graphene_core::raster::*; -use graphene_core::raster_types::{CPU, GPU, RasterDataTable}; -use graphene_core::vector::VectorDataTable; -use graphene_core::{Artboard, GraphicGroupTable, concrete, generic}; -use graphene_core::{Cow, ProtoNodeIdentifier, Type}; -use graphene_core::{NodeIO, NodeIOTypes}; -use graphene_core::{fn_type_fut, future}; -use graphene_std::Context; -use graphene_std::GraphicElement; #[cfg(feature = "gpu")] use graphene_std::any::DowncastBothNode; use graphene_std::any::{ComposeTypeErased, DynAnyNode, IntoTypeErasedNode}; use graphene_std::application_io::{ImageTexture, SurfaceFrame}; +use graphene_std::color::Color; +use graphene_std::context::Context; +use graphene_std::element::{Artboard, GraphicElement, GraphicGroupTable}; +use graphene_std::raster::*; +use graphene_std::vector::VectorDataTable; #[cfg(feature = "gpu")] use graphene_std::wasm_application_io::{WasmEditorApi, WasmSurfaceHandle}; +use graphene_std::{Cow, ProtoNodeIdentifier, Type}; +use graphene_std::{NodeIO, NodeIOTypes}; +use graphene_std::{concrete, generic}; +use graphene_std::{fn_type_fut, future}; use node_registry_macros::{async_node, convert_node, into_node}; use once_cell::sync::Lazy; use std::collections::HashMap; @@ -73,7 +72,7 @@ fn node_registry() -> HashMap, input: Context, fn_params: [Context => Vec]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Color]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Box]), - async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]), + async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::std_nodes::misc::CentroidType]), async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image]), async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => VectorDataTable]), diff --git a/node-graph/interpreted-executor/src/util.rs b/node-graph/interpreted-executor/src/util.rs index ab4c744e3..188b13f9e 100644 --- a/node-graph/interpreted-executor/src/util.rs +++ b/node-graph/interpreted-executor/src/util.rs @@ -4,7 +4,7 @@ use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork}; use graph_craft::generic; use graph_craft::wasm_application_io::WasmEditorApi; -use graphene_std::Context; +use graphene_std::context::Context; use graphene_std::uuid::NodeId; use std::sync::Arc; diff --git a/node-graph/node-macro/src/codegen.rs b/node-graph/node-macro/src/codegen.rs index bb3ac0082..74bbabeb3 100644 --- a/node-graph/node-macro/src/codegen.rs +++ b/node-graph/node-macro/src/codegen.rs @@ -517,7 +517,7 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st } let mut constructors = Vec::new(); - let unit = parse_quote!(gcore::Context); + let unit = parse_quote!(gcore::context::Context); let parameter_types: Vec<_> = parsed .fields .iter() diff --git a/node-graph/node-macro/src/parsing.rs b/node-graph/node-macro/src/parsing.rs index 81151110a..7da9937ec 100644 --- a/node-graph/node-macro/src/parsing.rs +++ b/node-graph/node-macro/src/parsing.rs @@ -643,7 +643,7 @@ impl ParsedNodeFn { })); self.input.ty = parse_quote!(#ident); if self.input.implementations.is_empty() { - self.input.implementations.push(parse_quote!(gcore::Context)); + self.input.implementations.push(parse_quote!(gcore::context::Context)); } } if self.input.pat_ident.ident == "_" { diff --git a/node-graph/wgpu-executor/src/lib.rs b/node-graph/wgpu-executor/src/lib.rs index d65c24814..436cc619a 100644 --- a/node-graph/wgpu-executor/src/lib.rs +++ b/node-graph/wgpu-executor/src/lib.rs @@ -5,7 +5,8 @@ pub use context::Context; use dyn_any::StaticType; use glam::UVec2; use graphene_application_io::{ApplicationIo, EditorApi, SurfaceHandle}; -use graphene_core::{Color, Ctx}; +use graphene_core::color::Color; +use graphene_core::context::Ctx; pub use graphene_svg_renderer::RenderContext; use std::sync::Arc; use vello::{AaConfig, AaSupport, RenderParams, Renderer, RendererOptions, Scene};