Merge branch 'master' into spiral-node

This commit is contained in:
0SlowPoke0
2025-07-01 23:03:45 +05:30
committed by GitHub
24 changed files with 382 additions and 94 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1016,7 +1016,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default() ..Default::default()
}, },
}, },
description: Cow::Borrowed("TODO"), description: Cow::Borrowed(
"Decomposes the X and Y components of a 2D coordinate.\n\nThe inverse of this node is \"Coordinate Value\", which can have either or both its X and Y exposed as graph inputs.",
),
properties: None, properties: None,
}, },
// TODO: Remove this and just use the proto node definition directly // TODO: Remove this and just use the proto node definition directly
@@ -1856,7 +1858,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0), NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0),
NodeInput::network(concrete!(vector::misc::PointSpacingType), 1), NodeInput::network(concrete!(vector::misc::PointSpacingType), 1),
NodeInput::network(concrete!(f64), 2), NodeInput::network(concrete!(f64), 2),
NodeInput::network(concrete!(f64), 3), NodeInput::network(concrete!(u32), 3),
NodeInput::network(concrete!(f64), 4), NodeInput::network(concrete!(f64), 4),
NodeInput::network(concrete!(f64), 5), NodeInput::network(concrete!(f64), 5),
NodeInput::network(concrete!(bool), 6), NodeInput::network(concrete!(bool), 6),
@@ -1895,7 +1897,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true), NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true),
NodeInput::value(TaggedValue::PointSpacingType(Default::default()), false), NodeInput::value(TaggedValue::PointSpacingType(Default::default()), false),
NodeInput::value(TaggedValue::F64(100.), false), NodeInput::value(TaggedValue::F64(100.), false),
NodeInput::value(TaggedValue::F64(100.), false), NodeInput::value(TaggedValue::U32(100), false),
NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::F64(0.), false),
NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::F64(0.), false),
NodeInput::value(TaggedValue::Bool(false), false), NodeInput::value(TaggedValue::Bool(false), false),
@@ -1494,6 +1494,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
} else { } else {
NumberInput::default() NumberInput::default()
.value(Some(uniform_val)) .value(Some(uniform_val))
.unit(" px")
.on_update(update_value(move |x: &NumberInput| TaggedValue::F64(x.value.unwrap()), node_id, CornerRadiusInput::<f64>::INDEX)) .on_update(update_value(move |x: &NumberInput| TaggedValue::F64(x.value.unwrap()), node_id, CornerRadiusInput::<f64>::INDEX))
.on_commit(commit_value) .on_commit(commit_value)
.widget_holder() .widget_holder()
@@ -744,17 +744,62 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false); let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false);
let new_quantity_value = NodeInput::value(TaggedValue::U32(100), false);
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), new_spacing_value, network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 1), new_spacing_value, network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[1].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[1].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[1].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[2].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[2].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[4].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[4].clone(), network_path);
document.network_interface.replace_reference_name(node_id, network_path, "Sample Polyline".to_string()); document.network_interface.replace_reference_name(node_id, network_path, "Sample Polyline".to_string());
} }
// Make the "Quantity" parameter a u32 instead of f64
if reference == "Sample Polyline" {
let node_definition = resolve_document_node_type("Sample Polyline").unwrap();
let new_node_template = node_definition.default_node_template();
let document_node = new_node_template.document_node;
// Get the inputs, obtain the quantity value, and put the inputs back
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
let quantity_value = old_inputs.get(3).cloned();
let _ = document.network_interface.replace_inputs(node_id, old_inputs, network_path);
if let Some(NodeInput::Value { tagged_value, exposed }) = quantity_value {
if let TaggedValue::F64(value) = *tagged_value {
let new_quantity_value = NodeInput::value(TaggedValue::U32(value as u32), exposed);
document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path);
}
}
}
// Make the "Grid" node, if its input of index 3 is a DVec2 for "angles" instead of a u32 for the "columns" input that now succeeds "angles", move the angle to index 5 (after "columns" and "rows")
if reference == "Grid" && inputs_count == 6 {
let node_definition = resolve_document_node_type(reference).unwrap();
let new_node_template = node_definition.default_node_template();
let document_node = new_node_template.document_node;
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
let index_3_value = old_inputs.get(3).cloned();
if let Some(NodeInput::Value { tagged_value, exposed: _ }) = index_3_value {
if matches!(*tagged_value, TaggedValue::DVec2(_)) {
// Move index 3 to the end
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
} else {
// Swap it back if we're not changing anything
let _ = document.network_interface.replace_inputs(node_id, old_inputs, network_path);
}
}
}
} }
// Ensure layers are positioned as stacks if they are upstream siblings of another layer // Ensure layers are positioned as stacks if they are upstream siblings of another layer
+9
View File
@@ -1,5 +1,14 @@
use crate::raster_types::{CPU, RasterDataTable}; use crate::raster_types::{CPU, RasterDataTable};
use crate::vector::VectorDataTable;
use crate::{Color, Ctx}; use crate::{Color, Ctx};
use glam::{DAffine2, DVec2};
#[node_macro::node(category("Debug"), name("Log to Console"))]
fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2, Color, Option<Color>)] value: T) -> T {
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
log::debug!("{:#?}", value);
value
}
/// Meant for debugging purposes, not general use. Returns the size of the input type in bytes. /// Meant for debugging purposes, not general use. Returns the size of the input type in bytes.
#[node_macro::node(category("Debug"))] #[node_macro::node(category("Debug"))]
+3 -1
View File
@@ -2,7 +2,9 @@ use crate::Ctx;
use dyn_any::DynAny; use dyn_any::DynAny;
use glam::{DVec2, IVec2, UVec2}; use glam::{DVec2, IVec2, UVec2};
/// Obtain the X or Y component of a coordinate. /// Obtains the X or Y component of a coordinate point.
///
/// The inverse of this node is "Coordinate Value", which can have either or both its X and Y exposed as graph inputs.
#[node_macro::node(name("Extract XY"), category("Math: Vector"))] #[node_macro::node(name("Extract XY"), category("Math: Vector"))]
fn extract_xy<T: Into<DVec2>>(_: impl Ctx, #[implementations(DVec2, IVec2, UVec2)] vector: T, axis: XY) -> f64 { fn extract_xy<T: Into<DVec2>>(_: impl Ctx, #[implementations(DVec2, IVec2, UVec2)] vector: T, axis: XY) -> f64 {
match axis { match axis {
+27 -10
View File
@@ -1,14 +1,13 @@
use crate::ArtboardGroupTable;
use crate::Color;
use crate::GraphicElement;
use crate::GraphicGroupTable;
use crate::gradient::GradientStops;
use crate::raster_types::{CPU, GPU, RasterDataTable};
use crate::vector::VectorDataTable; use crate::vector::VectorDataTable;
use crate::{Color, Context, Ctx}; use crate::{Context, Ctx};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
#[node_macro::node(category("Debug"), name("Log to Console"))]
fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2, Color, Option<Color>)] value: T) -> T {
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
log::debug!("{:#?}", value);
value
}
#[node_macro::node(category("Text"))] #[node_macro::node(category("Text"))]
fn to_string<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2)] value: T) -> String { fn to_string<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2)] value: T) -> String {
format!("{:?}", value) format!("{:?}", value)
@@ -45,24 +44,42 @@ async fn switch<T, C: Send + 'n + Clone>(
#[implementations( #[implementations(
Context -> String, Context -> String,
Context -> bool, Context -> bool,
Context -> f32,
Context -> f64, Context -> f64,
Context -> u32, Context -> u32,
Context -> u64, Context -> u64,
Context -> DVec2, Context -> DVec2,
Context -> VectorDataTable,
Context -> DAffine2, Context -> DAffine2,
Context -> ArtboardGroupTable,
Context -> VectorDataTable,
Context -> GraphicGroupTable,
Context -> RasterDataTable<CPU>,
Context -> RasterDataTable<GPU>,
Context -> GraphicElement,
Context -> Color,
Context -> Option<Color>,
Context -> GradientStops,
)] )]
if_true: impl Node<C, Output = T>, if_true: impl Node<C, Output = T>,
#[expose] #[expose]
#[implementations( #[implementations(
Context -> String, Context -> String,
Context -> bool, Context -> bool,
Context -> f32,
Context -> f64, Context -> f64,
Context -> u32, Context -> u32,
Context -> u64, Context -> u64,
Context -> DVec2, Context -> DVec2,
Context -> VectorDataTable,
Context -> DAffine2, Context -> DAffine2,
Context -> ArtboardGroupTable,
Context -> VectorDataTable,
Context -> GraphicGroupTable,
Context -> RasterDataTable<CPU>,
Context -> RasterDataTable<GPU>,
Context -> GraphicElement,
Context -> Color,
Context -> Option<Color>,
Context -> GradientStops,
)] )]
if_false: impl Node<C, Output = T>, if_false: impl Node<C, Output = T>,
) -> T { ) -> T {
+9 -1
View File
@@ -73,9 +73,17 @@ pub trait Convert<T>: Sized {
fn convert(self) -> T; fn convert(self) -> T;
} }
impl<T: ToString> Convert<String> for T {
/// Converts this type into a `String` using its `ToString` implementation.
#[inline]
fn convert(self) -> String {
self.to_string()
}
}
/// Implements the [`Convert`] trait for conversion between the cartesian product of Rust's primitive numeric types. /// Implements the [`Convert`] trait for conversion between the cartesian product of Rust's primitive numeric types.
macro_rules! impl_convert { macro_rules! impl_convert {
($from:ty,$to:ty) => { ($from:ty, $to:ty) => {
impl Convert<$to> for $from { impl Convert<$to> for $from {
fn convert(self) -> $to { fn convert(self) -> $to {
self as $to self as $to
+1
View File
@@ -70,6 +70,7 @@ async fn boundless_footprint<T: 'n + 'static>(
transform_target.eval(ctx.into_context()).await transform_target.eval(ctx.into_context()).await
} }
#[node_macro::node(category("Debug"))] #[node_macro::node(category("Debug"))]
async fn freeze_real_time<T: 'n + 'static>( async fn freeze_real_time<T: 'n + 'static>(
ctx: impl Ctx + CloneVarArgs + ExtractAll, ctx: impl Ctx + CloneVarArgs + ExtractAll,
@@ -86,6 +86,7 @@ async fn instance_position(ctx: impl Ctx + ExtractVarArgs) -> DVec2 {
Default::default() Default::default()
} }
// TODO: Make this return a u32 instead of an f64, but we ned to improve math-related compatibility with integer types first.
#[node_macro::node(category("Instancing"), path(graphene_core::vector))] #[node_macro::node(category("Instancing"), path(graphene_core::vector))]
async fn instance_index(ctx: impl Ctx + ExtractIndex) -> f64 { async fn instance_index(ctx: impl Ctx + ExtractIndex) -> f64 {
match ctx.try_index() { match ctx.try_index() {
+42 -14
View File
@@ -38,7 +38,13 @@ impl CornerRadius for [f64; 4] {
} }
#[node_macro::node(category("Vector: Shape"))] #[node_macro::node(category("Vector: Shape"))]
fn circle(_: impl Ctx, _primary: (), #[default(50.)] radius: f64) -> VectorDataTable { fn circle(
_: impl Ctx,
_primary: (),
#[unit(" px")]
#[default(50.)]
radius: f64,
) -> VectorDataTable {
let radius = radius.abs(); let radius = radius.abs();
VectorDataTable::new(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius)))) VectorDataTable::new(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
} }
@@ -47,7 +53,9 @@ fn circle(_: impl Ctx, _primary: (), #[default(50.)] radius: f64) -> VectorDataT
fn arc( fn arc(
_: impl Ctx, _: impl Ctx,
_primary: (), _primary: (),
#[default(50.)] radius: f64, #[unit(" px")]
#[default(50.)]
radius: f64,
start_angle: Angle, start_angle: Angle,
#[default(270.)] #[default(270.)]
#[range((0., 360.))] #[range((0., 360.))]
@@ -92,7 +100,16 @@ fn spiral(
} }
#[node_macro::node(category("Vector: Shape"))] #[node_macro::node(category("Vector: Shape"))]
fn ellipse(_: impl Ctx, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorDataTable { fn ellipse(
_: impl Ctx,
_primary: (),
#[unit(" px")]
#[default(50)]
radius_x: f64,
#[unit(" px")]
#[default(25)]
radius_y: f64,
) -> VectorDataTable {
let radius = DVec2::new(radius_x, radius_y); let radius = DVec2::new(radius_x, radius_y);
let corner1 = -radius; let corner1 = -radius;
let corner2 = radius; let corner2 = radius;
@@ -113,8 +130,12 @@ fn ellipse(_: impl Ctx, _primary: (), #[default(50)] radius_x: f64, #[default(25
fn rectangle<T: CornerRadius>( fn rectangle<T: CornerRadius>(
_: impl Ctx, _: impl Ctx,
_primary: (), _primary: (),
#[default(100)] width: f64, #[unit(" px")]
#[default(100)] height: f64, #[default(100)]
width: f64,
#[unit(" px")]
#[default(100)]
height: f64,
_individual_corner_radii: bool, // TODO: Move this to the bottom once we have a migration capability _individual_corner_radii: bool, // TODO: Move this to the bottom once we have a migration capability
#[implementations(f64, [f64; 4])] corner_radius: T, #[implementations(f64, [f64; 4])] corner_radius: T,
#[default(true)] clamped: bool, #[default(true)] clamped: bool,
@@ -130,7 +151,9 @@ fn regular_polygon<T: AsU64>(
#[hard_min(3.)] #[hard_min(3.)]
#[implementations(u32, u64, f64)] #[implementations(u32, u64, f64)]
sides: T, sides: T,
#[default(50)] radius: f64, #[unit(" px")]
#[default(50)]
radius: f64,
) -> VectorDataTable { ) -> VectorDataTable {
let points = sides.as_u64(); let points = sides.as_u64();
let radius: f64 = radius * 2.; let radius: f64 = radius * 2.;
@@ -145,8 +168,12 @@ fn star<T: AsU64>(
#[hard_min(2.)] #[hard_min(2.)]
#[implementations(u32, u64, f64)] #[implementations(u32, u64, f64)]
sides: T, sides: T,
#[default(50)] radius_1: f64, #[unit(" px")]
#[default(25)] radius_2: f64, #[default(50)]
radius_1: f64,
#[unit(" px")]
#[default(25)]
radius_2: f64,
) -> VectorDataTable { ) -> VectorDataTable {
let points = sides.as_u64(); let points = sides.as_u64();
let diameter: f64 = radius_1 * 2.; let diameter: f64 = radius_1 * 2.;
@@ -156,7 +183,7 @@ fn star<T: AsU64>(
} }
#[node_macro::node(category("Vector: Shape"))] #[node_macro::node(category("Vector: Shape"))]
fn line(_: impl Ctx, _primary: (), #[default((0., -50.))] start: PixelSize, #[default((0., 50.))] end: PixelSize) -> VectorDataTable { fn line(_: impl Ctx, _primary: (), #[default(0., 0.)] start: PixelSize, #[default(100., 100.)] end: PixelSize) -> VectorDataTable {
VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end))) VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end)))
} }
@@ -179,13 +206,14 @@ fn grid<T: GridSpacing>(
_: impl Ctx, _: impl Ctx,
_primary: (), _primary: (),
grid_type: GridType, grid_type: GridType,
#[unit(" px")]
#[hard_min(0.)] #[hard_min(0.)]
#[default(10)] #[default(10)]
#[implementations(f64, DVec2)] #[implementations(f64, DVec2)]
spacing: T, spacing: T,
#[default(30., 30.)] angles: DVec2,
#[default(10)] columns: u32, #[default(10)] columns: u32,
#[default(10)] rows: u32, #[default(10)] rows: u32,
#[default(30., 30.)] angles: DVec2,
) -> VectorDataTable { ) -> VectorDataTable {
let (x_spacing, y_spacing) = spacing.as_dvec2().into(); let (x_spacing, y_spacing) = spacing.as_dvec2().into();
let (angle_a, angle_b) = angles.into(); let (angle_a, angle_b) = angles.into();
@@ -277,11 +305,11 @@ mod tests {
#[test] #[test]
fn isometric_grid_test() { fn isometric_grid_test() {
// Doesn't crash with weird angles // Doesn't crash with weird angles
grid((), (), GridType::Isometric, 0., (0., 0.).into(), 5, 5); grid((), (), GridType::Isometric, 0., 5, 5, (0., 0.).into());
grid((), (), GridType::Isometric, 90., (90., 90.).into(), 5, 5); grid((), (), GridType::Isometric, 90., 5, 5, (90., 90.).into());
// Works properly // Works properly
let grid = grid((), (), GridType::Isometric, 10., (30., 30.).into(), 5, 5); let grid = grid((), (), GridType::Isometric, 10., 5, 5, (30., 30.).into());
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.point_domain.ids().len(), 5 * 5); assert_eq!(grid.instance_ref_iter().next().unwrap().instance.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9); assert_eq!(grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter() { for (_, bezier, _, _) in grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter() {
@@ -296,7 +324,7 @@ mod tests {
#[test] #[test]
fn skew_isometric_grid_test() { fn skew_isometric_grid_test() {
let grid = grid((), (), GridType::Isometric, 10., (40., 30.).into(), 5, 5); let grid = grid((), (), GridType::Isometric, 10., 5, 5, (40., 30.).into());
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.point_domain.ids().len(), 5 * 5); assert_eq!(grid.instance_ref_iter().next().unwrap().instance.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9); assert_eq!(grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter() { for (_, bezier, _, _) in grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter() {
+23 -10
View File
@@ -65,6 +65,7 @@ async fn assign_colors<T>(
randomize: bool, randomize: bool,
#[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")] #[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")]
/// The seed used for randomization. /// The seed used for randomization.
/// Seed to determine unique variations on the randomized color selection.
seed: SeedValue, seed: SeedValue,
#[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")] #[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. /// The number of elements to span across the gradient before repeating. A 0 value will span the entire gradient once.
@@ -165,6 +166,7 @@ async fn stroke<C: Into<Option<Color>> + 'n + Send, V>(
#[default(Color::BLACK)] #[default(Color::BLACK)]
/// The stroke color. /// The stroke color.
color: C, color: C,
#[unit(" px")]
#[default(2.)] #[default(2.)]
/// The stroke weight. /// The stroke weight.
weight: f64, weight: f64,
@@ -183,6 +185,7 @@ async fn stroke<C: Into<Option<Color>> + 'n + Send, V>(
/// 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. /// 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<f64>, dash_lengths: Vec<f64>,
/// The phase offset distance from the starting point of the dash pattern. /// The phase offset distance from the starting point of the dash pattern.
#[unit(" px")]
dash_offset: f64, dash_offset: f64,
) -> Instances<V> ) -> Instances<V>
where where
@@ -253,7 +256,9 @@ async fn circular_repeat<I: 'n + Send + Clone>(
// TODO: Implement other GraphicElementRendered types. // TODO: Implement other GraphicElementRendered types.
#[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>)] instance: Instances<I>, #[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>)] instance: Instances<I>,
angle_offset: Angle, angle_offset: Angle,
#[default(5)] radius: f64, #[unit(" px")]
#[default(5)]
radius: f64,
#[default(5)] instances: IntegerCount, #[default(5)] instances: IntegerCount,
) -> Instances<I> { ) -> Instances<I> {
let count = instances.max(1); let count = instances.max(1);
@@ -363,7 +368,7 @@ async fn mirror<I: 'n + Send + Clone>(
_: impl Ctx, _: impl Ctx,
#[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>)] instance: Instances<I>, #[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>)] instance: Instances<I>,
#[default(ReferencePoint::Center)] relative_to_bounds: ReferencePoint, #[default(ReferencePoint::Center)] relative_to_bounds: ReferencePoint,
offset: f64, #[unit(" px")] offset: f64,
#[range((-90., 90.))] angle: Angle, #[range((-90., 90.))] angle: Angle,
#[default(true)] keep_original: bool, #[default(true)] keep_original: bool,
) -> Instances<I> ) -> Instances<I>
@@ -1139,10 +1144,10 @@ async fn sample_polyline(
_: impl Ctx, _: impl Ctx,
vector_data: VectorDataTable, vector_data: VectorDataTable,
spacing: PointSpacingType, spacing: PointSpacingType,
separation: f64, #[unit(" px")] separation: f64,
quantity: f64, quantity: u32,
start_offset: f64, #[unit(" px")] start_offset: f64,
stop_offset: f64, #[unit(" px")] stop_offset: f64,
adaptive_spacing: bool, adaptive_spacing: bool,
subpath_segment_lengths: Vec<f64>, subpath_segment_lengths: Vec<f64>,
) -> VectorDataTable { ) -> VectorDataTable {
@@ -1182,7 +1187,7 @@ async fn sample_polyline(
let amount = match spacing { let amount = match spacing {
PointSpacingType::Separation => separation, PointSpacingType::Separation => separation,
PointSpacingType::Quantity => quantity, PointSpacingType::Quantity => quantity as f64,
}; };
let Some(mut sample_bezpath) = sample_polyline_on_bezpath(bezpath, spacing, amount, start_offset, stop_offset, adaptive_spacing, current_bezpath_segments_length) else { let Some(mut sample_bezpath) = sample_polyline_on_bezpath(bezpath, spacing, amount, start_offset, stop_offset, adaptive_spacing, current_bezpath_segments_length) else {
continue; continue;
@@ -1388,6 +1393,7 @@ async fn tangent_on_path(
async fn poisson_disk_points( async fn poisson_disk_points(
_: impl Ctx, _: impl Ctx,
vector_data: VectorDataTable, vector_data: VectorDataTable,
#[unit(" px")]
#[default(10.)] #[default(10.)]
#[hard_min(0.01)] #[hard_min(0.01)]
separation_disk_diameter: f64, separation_disk_diameter: f64,
@@ -1498,7 +1504,14 @@ async fn spline(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
} }
#[node_macro::node(category("Vector: Modifier"), path(graphene_core::vector))] #[node_macro::node(category("Vector: Modifier"), path(graphene_core::vector))]
async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)] amount: f64, seed: SeedValue) -> VectorDataTable { async fn jitter_points(
_: impl Ctx,
vector_data: VectorDataTable,
#[unit(" px")]
#[default(5.)]
amount: f64,
seed: SeedValue,
) -> VectorDataTable {
let mut result_table = VectorDataTable::default(); let mut result_table = VectorDataTable::default();
for mut vector_data_instance in vector_data.instance_iter() { for mut vector_data_instance in vector_data.instance_iter() {
@@ -2080,7 +2093,7 @@ mod test {
#[tokio::test] #[tokio::test]
async fn sample_polyline() { async fn sample_polyline() {
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)); 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; let sample_polyline = super::sample_polyline(Footprint::default(), vector_node(path), PointSpacingType::Separation, 30., 0, 0., 0., false, vec![100.]).await;
let sample_polyline = sample_polyline.instance_ref_iter().next().unwrap().instance; let sample_polyline = sample_polyline.instance_ref_iter().next().unwrap().instance;
assert_eq!(sample_polyline.point_domain.positions().len(), 4); assert_eq!(sample_polyline.point_domain.positions().len(), 4);
for (pos, expected) in sample_polyline.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) { for (pos, expected) in sample_polyline.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) {
@@ -2090,7 +2103,7 @@ mod test {
#[tokio::test] #[tokio::test]
async fn sample_polyline_adaptive_spacing() { async fn sample_polyline_adaptive_spacing() {
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.)); 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, 18., 0., 45., 10., true, vec![100.]).await; let sample_polyline = super::sample_polyline(Footprint::default(), vector_node(path), PointSpacingType::Separation, 18., 0, 45., 10., true, vec![100.]).await;
let sample_polyline = sample_polyline.instance_ref_iter().next().unwrap().instance; let sample_polyline = sample_polyline.instance_ref_iter().next().unwrap().instance;
assert_eq!(sample_polyline.point_domain.positions().len(), 4); assert_eq!(sample_polyline.point_domain.positions().len(), 4);
for (pos, expected) in sample_polyline.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) { for (pos, expected) in sample_polyline.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) {
+190 -40
View File
@@ -78,8 +78,12 @@ fn math<U: num_traits::float::Float>(
#[node_macro::node(category("Math: Arithmetic"))] #[node_macro::node(category("Math: Arithmetic"))]
fn add<U: Add<T>, T>( fn add<U: Add<T>, T>(
_: impl Ctx, _: impl Ctx,
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] augend: U, /// The left-hand side of the addition operation.
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] addend: T, #[implementations(f64, f32, u32, DVec2, f64, DVec2)]
augend: U,
/// The right-hand side of the addition operation.
#[implementations(f64, f32, u32, DVec2, DVec2, f64)]
addend: T,
) -> <U as Add<T>>::Output { ) -> <U as Add<T>>::Output {
augend + addend augend + addend
} }
@@ -88,8 +92,12 @@ fn add<U: Add<T>, T>(
#[node_macro::node(category("Math: Arithmetic"))] #[node_macro::node(category("Math: Arithmetic"))]
fn subtract<U: Sub<T>, T>( fn subtract<U: Sub<T>, T>(
_: impl Ctx, _: impl Ctx,
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] minuend: U, /// The left-hand side of the subtraction operation.
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] subtrahend: T, #[implementations(f64, f32, u32, DVec2, f64, DVec2)]
minuend: U,
/// The right-hand side of the subtraction operation.
#[implementations(f64, f32, u32, DVec2, DVec2, f64)]
subtrahend: T,
) -> <U as Sub<T>>::Output { ) -> <U as Sub<T>>::Output {
minuend - subtrahend minuend - subtrahend
} }
@@ -98,9 +106,12 @@ fn subtract<U: Sub<T>, T>(
#[node_macro::node(category("Math: Arithmetic"))] #[node_macro::node(category("Math: Arithmetic"))]
fn multiply<U: Mul<T>, T>( fn multiply<U: Mul<T>, T>(
_: impl Ctx, _: impl Ctx,
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] multiplier: U, /// The left-hand side of the multiplication operation.
#[implementations(f64, f32, u32, DVec2, f64, DVec2)]
multiplier: U,
/// The right-hand side of the multiplication operation.
#[default(1.)] #[default(1.)]
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] #[implementations(f64, f32, u32, DVec2, DVec2, f64)]
multiplicand: T, multiplicand: T,
) -> <U as Mul<T>>::Output { ) -> <U as Mul<T>>::Output {
multiplier * multiplicand multiplier * multiplicand
@@ -112,7 +123,10 @@ fn multiply<U: Mul<T>, T>(
#[node_macro::node(category("Math: Arithmetic"))] #[node_macro::node(category("Math: Arithmetic"))]
fn divide<U: Div<T> + Default + PartialEq, T: Default + PartialEq>( fn divide<U: Div<T> + Default + PartialEq, T: Default + PartialEq>(
_: impl Ctx, _: impl Ctx,
#[implementations(f64, f64, f32, f32, u32, u32, DVec2, DVec2, f64)] numerator: U, /// The left-hand side of the division operation.
#[implementations(f64, f64, f32, f32, u32, u32, DVec2, DVec2, f64)]
numerator: U,
/// The right-hand side of the division operation.
#[default(1.)] #[default(1.)]
#[implementations(f64, f64, f32, f32, u32, u32, DVec2, f64, DVec2)] #[implementations(f64, f64, f32, f32, u32, u32, DVec2, f64, DVec2)]
denominator: T, denominator: T,
@@ -130,10 +144,15 @@ where
#[node_macro::node(category("Math: Arithmetic"))] #[node_macro::node(category("Math: Arithmetic"))]
fn modulo<U: Rem<T, Output: Add<T, Output: Rem<T, Output = U::Output>>>, T: Copy>( fn modulo<U: Rem<T, Output: Add<T, Output: Rem<T, Output = U::Output>>>, T: Copy>(
_: impl Ctx, _: impl Ctx,
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, DVec2, f64)] numerator: U, /// The left-hand side of the modulo operation.
#[implementations(f64, f32, u32, DVec2, DVec2, f64)]
numerator: U,
/// The right-hand side of the modulo operation.
#[default(2.)] #[default(2.)]
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, f64, DVec2)] #[implementations(f64, f32, u32, DVec2, f64, DVec2)]
modulus: T, modulus: T,
/// Ensures the result will always be positive, even if the numerator is negative.
#[default(true)]
always_positive: bool, always_positive: bool,
) -> <U as Rem<T>>::Output { ) -> <U as Rem<T>>::Output {
if always_positive { (numerator % modulus + modulus) % modulus } else { numerator % modulus } if always_positive { (numerator % modulus + modulus) % modulus } else { numerator % modulus }
@@ -143,9 +162,12 @@ fn modulo<U: Rem<T, Output: Add<T, Output: Rem<T, Output = U::Output>>>, T: Copy
#[node_macro::node(category("Math: Arithmetic"))] #[node_macro::node(category("Math: Arithmetic"))]
fn exponent<U: Pow<T>, T>( fn exponent<U: Pow<T>, T>(
_: impl Ctx, _: impl Ctx,
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32)] base: U, /// The base number that will be raised to the power.
#[implementations(f64, f32, u32)]
base: U,
/// The power to which the base number will be raised.
#[default(2.)] #[default(2.)]
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32)] #[implementations(f64, f32, u32)]
power: T, power: T,
) -> <U as num_traits::Pow<T>>::Output { ) -> <U as num_traits::Pow<T>>::Output {
base.pow(power) base.pow(power)
@@ -155,9 +177,11 @@ fn exponent<U: Pow<T>, T>(
#[node_macro::node(category("Math: Arithmetic"))] #[node_macro::node(category("Math: Arithmetic"))]
fn root<U: num_traits::float::Float>( fn root<U: num_traits::float::Float>(
_: impl Ctx, _: impl Ctx,
/// The number for which the nth root will be calculated.
#[default(2.)] #[default(2.)]
#[implementations(f64, f32)] #[implementations(f64, f32)]
radicand: U, radicand: U,
/// The degree of the root to be calculated. Square root is 2, cube root is 3, and so on.
#[default(2.)] #[default(2.)]
#[implementations(f64, f32)] #[implementations(f64, f32)]
degree: U, degree: U,
@@ -175,7 +199,10 @@ fn root<U: num_traits::float::Float>(
#[node_macro::node(category("Math: Arithmetic"))] #[node_macro::node(category("Math: Arithmetic"))]
fn logarithm<U: num_traits::float::Float>( fn logarithm<U: num_traits::float::Float>(
_: impl Ctx, _: impl Ctx,
#[implementations(f64, f32)] value: U, /// The number for which the logarithm will be calculated.
#[implementations(f64, f32)]
value: U,
/// The base of the logarithm, such as 2 (binary), 10 (decimal), and e (natural logarithm).
#[default(2.)] #[default(2.)]
#[implementations(f64, f32)] #[implementations(f64, f32)]
base: U, base: U,
@@ -193,39 +220,83 @@ fn logarithm<U: num_traits::float::Float>(
/// The sine trigonometric function (sin) calculates the ratio of the angle's opposite side length to its hypotenuse length. /// The sine trigonometric function (sin) calculates the ratio of the angle's opposite side length to its hypotenuse length.
#[node_macro::node(category("Math: Trig"))] #[node_macro::node(category("Math: Trig"))]
fn sine<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U { fn sine<U: num_traits::float::Float>(
_: impl Ctx,
/// The given angle.
#[implementations(f64, f32)]
theta: U,
/// Whether the given angle should be interpreted as radians instead of degrees.
radians: bool,
) -> U {
if radians { theta.sin() } else { theta.to_radians().sin() } if radians { theta.sin() } else { theta.to_radians().sin() }
} }
/// The cosine trigonometric function (cos) calculates the ratio of the angle's adjacent side length to its hypotenuse length. /// The cosine trigonometric function (cos) calculates the ratio of the angle's adjacent side length to its hypotenuse length.
#[node_macro::node(category("Math: Trig"))] #[node_macro::node(category("Math: Trig"))]
fn cosine<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U { fn cosine<U: num_traits::float::Float>(
_: impl Ctx,
/// The given angle.
#[implementations(f64, f32)]
theta: U,
/// Whether the given angle should be interpreted as radians instead of degrees.
radians: bool,
) -> U {
if radians { theta.cos() } else { theta.to_radians().cos() } if radians { theta.cos() } else { theta.to_radians().cos() }
} }
/// The tangent trigonometric function (tan) calculates the ratio of the angle's opposite side length to its adjacent side length. /// The tangent trigonometric function (tan) calculates the ratio of the angle's opposite side length to its adjacent side length.
#[node_macro::node(category("Math: Trig"))] #[node_macro::node(category("Math: Trig"))]
fn tangent<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U { fn tangent<U: num_traits::float::Float>(
_: impl Ctx,
/// The given angle.
#[implementations(f64, f32)]
theta: U,
/// Whether the given angle should be interpreted as radians instead of degrees.
radians: bool,
) -> U {
if radians { theta.tan() } else { theta.to_radians().tan() } if radians { theta.tan() } else { theta.to_radians().tan() }
} }
/// The inverse sine trigonometric function (asin) calculates the angle whose sine is the specified value. /// The inverse sine trigonometric function (asin) calculates the angle whose sine is the specified value.
#[node_macro::node(category("Math: Trig"))] #[node_macro::node(category("Math: Trig"))]
fn sine_inverse<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U { fn sine_inverse<U: num_traits::float::Float>(
_: impl Ctx,
/// The given value for which the angle will be calculated. Must be in the range [-1, 1] or else the result will be NaN.
#[implementations(f64, f32)]
value: U,
/// Whether the resulting angle should be given in as radians instead of degrees.
radians: bool,
) -> U {
if radians { value.asin() } else { value.asin().to_degrees() } if radians { value.asin() } else { value.asin().to_degrees() }
} }
/// The inverse cosine trigonometric function (acos) calculates the angle whose cosine is the specified value. /// The inverse cosine trigonometric function (acos) calculates the angle whose cosine is the specified value.
#[node_macro::node(category("Math: Trig"))] #[node_macro::node(category("Math: Trig"))]
fn cosine_inverse<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U { fn cosine_inverse<U: num_traits::float::Float>(
_: impl Ctx,
/// The given value for which the angle will be calculated. Must be in the range [-1, 1] or else the result will be NaN.
#[implementations(f64, f32)]
value: U,
/// Whether the resulting angle should be given in as radians instead of degrees.
radians: bool,
) -> U {
if radians { value.acos() } else { value.acos().to_degrees() } if radians { value.acos() } else { value.acos().to_degrees() }
} }
/// The inverse tangent trigonometric function (atan or atan2, depending on input type) calculates: /// The inverse tangent trigonometric function (atan or atan2, depending on input type) calculates:
/// atan: the angle whose tangent is the specified scalar number. /// atan: the angle whose tangent is the specified scalar number.
/// atan2: the angle of a ray from the origin to the specified coordinate. /// atan2: the angle of a ray from the origin to the specified coordinate.
///
/// The resulting angle is always in the range [0°, 180°] or, in radians, [-π/2, π/2].
#[node_macro::node(category("Math: Trig"))] #[node_macro::node(category("Math: Trig"))]
fn tangent_inverse<U: TangentInverse>(_: impl Ctx, #[implementations(f64, f32, DVec2)] value: U, radians: bool) -> U::Output { fn tangent_inverse<U: TangentInverse>(
_: impl Ctx,
/// The given value for which the angle will be calculated.
#[implementations(f64, f32, DVec2)]
value: U,
/// Whether the resulting angle should be given in as radians instead of degrees.
radians: bool,
) -> U::Output {
value.atan(radians) value.atan(radians)
} }
@@ -257,10 +328,13 @@ impl TangentInverse for DVec2 {
fn random<U: num_traits::float::Float>( fn random<U: num_traits::float::Float>(
_: impl Ctx, _: impl Ctx,
_primary: (), _primary: (),
/// Seed to determine the unique variation of which number will be generated.
seed: u64, seed: u64,
/// The smaller end of the range within which the random number will be generated.
#[implementations(f64, f32)] #[implementations(f64, f32)]
#[default(0.)] #[default(0.)]
min: U, min: U,
/// The larger end of the range within which the random number will be generated.
#[implementations(f64, f32)] #[implementations(f64, f32)]
#[default(1.)] #[default(1.)]
max: U, max: U,
@@ -294,37 +368,73 @@ fn to_f64<U: num_traits::int::PrimInt>(_: impl Ctx, #[implementations(u32, u64)]
/// The rounding function (round) maps an input value to its nearest whole number. Halfway values are rounded away from zero. /// The rounding function (round) maps an input value to its nearest whole number. Halfway values are rounded away from zero.
#[node_macro::node(category("Math: Numeric"))] #[node_macro::node(category("Math: Numeric"))]
fn round<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U { fn round<U: num_traits::float::Float>(
_: impl Ctx,
/// The number which will be rounded.
#[implementations(f64, f32)]
value: U,
) -> U {
value.round() value.round()
} }
/// The floor function (floor) reduces an input value to its nearest larger whole number, unless the input number is already whole. /// The floor function (floor) rounds down an input value to the nearest whole number, unless the input number is already whole.
#[node_macro::node(category("Math: Numeric"))] #[node_macro::node(category("Math: Numeric"))]
fn floor<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U { fn floor<U: num_traits::float::Float>(
_: impl Ctx,
/// The number which will be rounded down.
#[implementations(f64, f32)]
value: U,
) -> U {
value.floor() value.floor()
} }
/// The ceiling function (ceil) increases an input value to its nearest smaller whole number, unless the input number is already whole. /// The ceiling function (ceil) rounds up an input value to the nearest whole number, unless the input number is already whole.
#[node_macro::node(category("Math: Numeric"))] #[node_macro::node(category("Math: Numeric"))]
fn ceiling<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U { fn ceiling<U: num_traits::float::Float>(
_: impl Ctx,
/// The number which will be rounded up.
#[implementations(f64, f32)]
value: U,
) -> U {
value.ceil() value.ceil()
} }
/// The absolute value function (abs) removes the negative sign from an input value, if present. /// The absolute value function (abs) removes the negative sign from an input value, if present.
#[node_macro::node(category("Math: Numeric"))] #[node_macro::node(category("Math: Numeric"))]
fn absolute_value<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U { fn absolute_value<U: num_traits::float::Float>(
_: impl Ctx,
/// The number which will be made positive.
#[implementations(f64, f32)]
value: U,
) -> U {
value.abs() value.abs()
} }
/// The minimum function (min) picks the smaller of two numbers. /// The minimum function (min) picks the smaller of two numbers.
#[node_macro::node(category("Math: Numeric"))] #[node_macro::node(category("Math: Numeric"))]
fn min<T: std::cmp::PartialOrd>(_: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T { fn min<T: std::cmp::PartialOrd>(
_: impl Ctx,
/// One of the two numbers, of which the lesser will be returned.
#[implementations(f64, f32, u32, &str)]
value: T,
/// The other of the two numbers, of which the lesser will be returned.
#[implementations(f64, f32, u32, &str)]
other_value: T,
) -> T {
if value < other_value { value } else { other_value } if value < other_value { value } else { other_value }
} }
/// The maximum function (max) picks the larger of two numbers. /// The maximum function (max) picks the larger of two numbers.
#[node_macro::node(category("Math: Numeric"))] #[node_macro::node(category("Math: Numeric"))]
fn max<T: std::cmp::PartialOrd>(_: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T { fn max<T: std::cmp::PartialOrd>(
_: impl Ctx,
/// One of the two numbers, of which the greater will be returned.
#[implementations(f64, f32, u32, &str)]
value: T,
/// The other of the two numbers, of which the greater will be returned.
#[implementations(f64, f32, u32, &str)]
other_value: T,
) -> T {
if value > other_value { value } else { other_value } if value > other_value { value } else { other_value }
} }
@@ -332,9 +442,15 @@ fn max<T: std::cmp::PartialOrd>(_: impl Ctx, #[implementations(f64, &f64, f32, &
#[node_macro::node(category("Math: Numeric"))] #[node_macro::node(category("Math: Numeric"))]
fn clamp<T: std::cmp::PartialOrd>( fn clamp<T: std::cmp::PartialOrd>(
_: impl Ctx, _: impl Ctx,
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, /// The number to be clamped, which will be restricted to the range between the minimum and maximum values.
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] min: T, #[implementations(f64, f32, u32, &str)]
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] max: T, value: T,
/// The left (smaller) side of the range. The output will never be less than this number.
#[implementations(f64, f32, u32, &str)]
min: T,
/// The right (greater) side of the range. The output will never be greater than this number.
#[implementations(f64, f32, u32, &str)]
max: T,
) -> T { ) -> T {
let (min, max) = if min < max { (min, max) } else { (max, min) }; let (min, max) = if min < max { (min, max) } else { (max, min) };
if value < min { if value < min {
@@ -350,8 +466,12 @@ fn clamp<T: std::cmp::PartialOrd>(
#[node_macro::node(category("Math: Logic"))] #[node_macro::node(category("Math: Logic"))]
fn equals<U: std::cmp::PartialEq<T>, T>( fn equals<U: std::cmp::PartialEq<T>, T>(
_: impl Ctx, _: impl Ctx,
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] value: T, /// One of the two numbers to compare for equality.
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] other_value: U, #[implementations(f64, f32, u32, DVec2, &str)]
value: T,
/// The other of the two numbers to compare for equality.
#[implementations(f64, f32, u32, DVec2, &str)]
other_value: U,
) -> bool { ) -> bool {
other_value == value other_value == value
} }
@@ -360,8 +480,12 @@ fn equals<U: std::cmp::PartialEq<T>, T>(
#[node_macro::node(category("Math: Logic"))] #[node_macro::node(category("Math: Logic"))]
fn not_equals<U: std::cmp::PartialEq<T>, T>( fn not_equals<U: std::cmp::PartialEq<T>, T>(
_: impl Ctx, _: impl Ctx,
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] value: T, /// One of the two numbers to compare for inequality.
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] other_value: U, #[implementations(f64, f32, u32, DVec2, &str)]
value: T,
/// The other of the two numbers to compare for inequality.
#[implementations(f64, f32, u32, DVec2, &str)]
other_value: U,
) -> bool { ) -> bool {
other_value != value other_value != value
} }
@@ -371,8 +495,13 @@ fn not_equals<U: std::cmp::PartialEq<T>, T>(
#[node_macro::node(category("Math: Logic"))] #[node_macro::node(category("Math: Logic"))]
fn less_than<T: std::cmp::PartialOrd<T>>( fn less_than<T: std::cmp::PartialOrd<T>>(
_: impl Ctx, _: impl Ctx,
#[implementations(f64, &f64, f32, &f32, u32, &u32)] value: T, /// The number on the left-hand side of the comparison.
#[implementations(f64, &f64, f32, &f32, u32, &u32)] other_value: T, #[implementations(f64, f32, u32)]
value: T,
/// The number on the right-hand side of the comparison.
#[implementations(f64, f32, u32)]
other_value: T,
/// Uses the less-than-or-equal operation (<=) instead of the less-than operation (<).
or_equal: bool, or_equal: bool,
) -> bool { ) -> bool {
if or_equal { value <= other_value } else { value < other_value } if or_equal { value <= other_value } else { value < other_value }
@@ -383,8 +512,13 @@ fn less_than<T: std::cmp::PartialOrd<T>>(
#[node_macro::node(category("Math: Logic"))] #[node_macro::node(category("Math: Logic"))]
fn greater_than<T: std::cmp::PartialOrd<T>>( fn greater_than<T: std::cmp::PartialOrd<T>>(
_: impl Ctx, _: impl Ctx,
#[implementations(f64, &f64, f32, &f32, u32, &u32)] value: T, /// The number on the left-hand side of the comparison.
#[implementations(f64, &f64, f32, &f32, u32, &u32)] other_value: T, #[implementations(f64, f32, u32)]
value: T,
/// The number on the right-hand side of the comparison.
#[implementations(f64, f32, u32)]
other_value: T,
/// Uses the greater-than-or-equal operation (>=) instead of the greater-than operation (>).
or_equal: bool, or_equal: bool,
) -> bool { ) -> bool {
if or_equal { value >= other_value } else { value > other_value } if or_equal { value >= other_value } else { value > other_value }
@@ -392,19 +526,35 @@ fn greater_than<T: std::cmp::PartialOrd<T>>(
/// The logical or operation (||) returns true if either of the two inputs are true, or false if both are false. /// The logical or operation (||) returns true if either of the two inputs are true, or false if both are false.
#[node_macro::node(category("Math: Logic"))] #[node_macro::node(category("Math: Logic"))]
fn logical_or(_: impl Ctx, value: bool, other_value: bool) -> bool { fn logical_or(
_: impl Ctx,
/// One of the two boolean values, either of which may be true for the node to output true.
value: bool,
/// The other of the two boolean values, either of which may be true for the node to output true.
other_value: bool,
) -> bool {
value || other_value value || other_value
} }
/// The logical and operation (&&) returns true if both of the two inputs are true, or false if any are false. /// The logical and operation (&&) returns true if both of the two inputs are true, or false if any are false.
#[node_macro::node(category("Math: Logic"))] #[node_macro::node(category("Math: Logic"))]
fn logical_and(_: impl Ctx, value: bool, other_value: bool) -> bool { fn logical_and(
_: impl Ctx,
/// One of the two boolean values, both of which must be true for the node to output true.
value: bool,
/// The other of the two boolean values, both of which must be true for the node to output true.
other_value: bool,
) -> bool {
value && other_value value && other_value
} }
/// The logical not operation (!) reverses true and false value of the input. /// The logical not operation (!) reverses true and false value of the input.
#[node_macro::node(category("Math: Logic"))] #[node_macro::node(category("Math: Logic"))]
fn logical_not(_: impl Ctx, input: bool) -> bool { fn logical_not(
_: impl Ctx,
/// The boolean value to be reversed.
input: bool,
) -> bool {
!input !input
} }
+1 -1
View File
@@ -56,7 +56,7 @@ impl<P: Pixel + Alpha> Sample for BrushStampGenerator<P> {
} }
#[node_macro::node(skip_impl)] #[node_macro::node(skip_impl)]
fn brush_stamp_generator(diameter: f64, color: Color, hardness: f64, flow: f64) -> BrushStampGenerator<Color> { fn brush_stamp_generator(#[unit(" px")] diameter: f64, color: Color, hardness: f64, flow: f64) -> BrushStampGenerator<Color> {
// Diameter // Diameter
let radius = diameter / 2.; let radius = diameter / 2.;
+2 -2
View File
@@ -231,7 +231,7 @@ fn mask(
} }
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
fn extend_image_to_bounds(_: impl Ctx, image: RasterDataTable<CPU>, bounds: DAffine2) -> RasterDataTable<CPU> { pub fn extend_image_to_bounds(_: impl Ctx, image: RasterDataTable<CPU>, bounds: DAffine2) -> RasterDataTable<CPU> {
let mut result_table = RasterDataTable::default(); let mut result_table = RasterDataTable::default();
for mut image_instance in image.instance_iter() { for mut image_instance in image.instance_iter() {
@@ -284,7 +284,7 @@ fn extend_image_to_bounds(_: impl Ctx, image: RasterDataTable<CPU>, bounds: DAff
} }
#[node_macro::node(category("Debug: Raster"))] #[node_macro::node(category("Debug: Raster"))]
fn empty_image(_: impl Ctx, transform: DAffine2, color: Color) -> RasterDataTable<CPU> { pub fn empty_image(_: impl Ctx, transform: DAffine2, color: Color) -> RasterDataTable<CPU> {
let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32; let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32;
let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32; let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32;
+2 -1
View File
@@ -12,6 +12,7 @@ static NODE_ID: AtomicU64 = AtomicU64::new(0);
pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStream2> { pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStream2> {
let ParsedNodeFn { let ParsedNodeFn {
vis,
attributes, attributes,
fn_name, fn_name,
struct_name, struct_name,
@@ -345,7 +346,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
/// Underlying implementation for [#struct_name] /// Underlying implementation for [#struct_name]
#[inline] #[inline]
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub(crate) #async_keyword fn #fn_name <'n, #(#fn_generics,)*> (#input_ident: #input_type #(, #field_idents: #field_types)*) -> #output_type #where_clause #body #vis #async_keyword fn #fn_name <'n, #(#fn_generics,)*> (#input_ident: #input_type #(, #field_idents: #field_types)*) -> #output_type #where_clause #body
#[automatically_derived] #[automatically_derived]
impl<'n, #(#fn_generics,)* #(#struct_generics,)* #(#future_idents,)*> #graphene_core::Node<'n, #input_type> for #mod_name::#struct_name<#(#struct_generics,)*> impl<'n, #(#fn_generics,)* #(#struct_generics,)* #(#future_idents,)*> #graphene_core::Node<'n, #input_type> for #mod_name::#struct_name<#(#struct_generics,)*>
+12 -2
View File
@@ -7,8 +7,8 @@ use syn::punctuated::Punctuated;
use syn::spanned::Spanned; use syn::spanned::Spanned;
use syn::token::{Comma, RArrow}; use syn::token::{Comma, RArrow};
use syn::{ use syn::{
AttrStyle, Attribute, Error, Expr, ExprTuple, FnArg, GenericParam, Ident, ItemFn, Lit, LitFloat, LitInt, LitStr, Meta, Pat, PatIdent, PatType, Path, ReturnType, Type, TypeParam, WhereClause, AttrStyle, Attribute, Error, Expr, ExprTuple, FnArg, GenericParam, Ident, ItemFn, Lit, LitFloat, LitInt, LitStr, Meta, Pat, PatIdent, PatType, Path, ReturnType, Type, TypeParam, Visibility,
parse_quote, WhereClause, parse_quote,
}; };
use crate::codegen::generate_node_code; use crate::codegen::generate_node_code;
@@ -22,6 +22,7 @@ pub(crate) struct Implementation {
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct ParsedNodeFn { pub(crate) struct ParsedNodeFn {
pub(crate) vis: Visibility,
pub(crate) attributes: NodeFnAttributes, pub(crate) attributes: NodeFnAttributes,
pub(crate) fn_name: Ident, pub(crate) fn_name: Ident,
pub(crate) struct_name: Ident, pub(crate) struct_name: Ident,
@@ -263,6 +264,7 @@ fn parse_node_fn(attr: TokenStream2, item: TokenStream2) -> syn::Result<ParsedNo
let attributes = syn::parse2::<NodeFnAttributes>(attr.clone()).map_err(|e| Error::new(e.span(), format!("Failed to parse node_fn attributes: {}", e)))?; let attributes = syn::parse2::<NodeFnAttributes>(attr.clone()).map_err(|e| Error::new(e.span(), format!("Failed to parse node_fn attributes: {}", e)))?;
let input_fn = syn::parse2::<ItemFn>(item.clone()).map_err(|e| Error::new(e.span(), format!("Failed to parse function: {}. Make sure it's a valid Rust function.", e)))?; let input_fn = syn::parse2::<ItemFn>(item.clone()).map_err(|e| Error::new(e.span(), format!("Failed to parse function: {}. Make sure it's a valid Rust function.", e)))?;
let vis = input_fn.vis;
let fn_name = input_fn.sig.ident.clone(); let fn_name = input_fn.sig.ident.clone();
let struct_name = format_ident!("{}", fn_name.to_string().to_case(Case::Pascal)); let struct_name = format_ident!("{}", fn_name.to_string().to_case(Case::Pascal));
let mod_name = fn_name.clone(); let mod_name = fn_name.clone();
@@ -297,6 +299,7 @@ fn parse_node_fn(attr: TokenStream2, item: TokenStream2) -> syn::Result<ParsedNo
.fold(String::new(), |acc, b| acc + &b + "\n"); .fold(String::new(), |acc, b| acc + &b + "\n");
Ok(ParsedNodeFn { Ok(ParsedNodeFn {
vis,
attributes, attributes,
fn_name, fn_name,
struct_name, struct_name,
@@ -748,6 +751,7 @@ mod tests {
let parsed = parse_node_fn(attr, input).unwrap(); let parsed = parse_node_fn(attr, input).unwrap();
let expected = ParsedNodeFn { let expected = ParsedNodeFn {
vis: Visibility::Inherited,
attributes: NodeFnAttributes { attributes: NodeFnAttributes {
category: Some(parse_quote!("Math: Arithmetic")), category: Some(parse_quote!("Math: Arithmetic")),
display_name: None, display_name: None,
@@ -808,6 +812,7 @@ mod tests {
let parsed = parse_node_fn(attr, input).unwrap(); let parsed = parse_node_fn(attr, input).unwrap();
let expected = ParsedNodeFn { let expected = ParsedNodeFn {
vis: Visibility::Inherited,
attributes: NodeFnAttributes { attributes: NodeFnAttributes {
category: Some(parse_quote!("General")), category: Some(parse_quote!("General")),
display_name: None, display_name: None,
@@ -879,6 +884,7 @@ mod tests {
let parsed = parse_node_fn(attr, input).unwrap(); let parsed = parse_node_fn(attr, input).unwrap();
let expected = ParsedNodeFn { let expected = ParsedNodeFn {
vis: Visibility::Inherited,
attributes: NodeFnAttributes { attributes: NodeFnAttributes {
category: Some(parse_quote!("Vector: Shape")), category: Some(parse_quote!("Vector: Shape")),
display_name: None, display_name: None,
@@ -935,6 +941,7 @@ mod tests {
let parsed = parse_node_fn(attr, input).unwrap(); let parsed = parse_node_fn(attr, input).unwrap();
let expected = ParsedNodeFn { let expected = ParsedNodeFn {
vis: Visibility::Inherited,
attributes: NodeFnAttributes { attributes: NodeFnAttributes {
category: Some(parse_quote!("Raster: Adjustment")), category: Some(parse_quote!("Raster: Adjustment")),
display_name: None, display_name: None,
@@ -1003,6 +1010,7 @@ mod tests {
let parsed = parse_node_fn(attr, input).unwrap(); let parsed = parse_node_fn(attr, input).unwrap();
let expected = ParsedNodeFn { let expected = ParsedNodeFn {
vis: Visibility::Inherited,
attributes: NodeFnAttributes { attributes: NodeFnAttributes {
category: Some(parse_quote!("Math: Arithmetic")), category: Some(parse_quote!("Math: Arithmetic")),
display_name: None, display_name: None,
@@ -1059,6 +1067,7 @@ mod tests {
let parsed = parse_node_fn(attr, input).unwrap(); let parsed = parse_node_fn(attr, input).unwrap();
let expected = ParsedNodeFn { let expected = ParsedNodeFn {
vis: Visibility::Inherited,
attributes: NodeFnAttributes { attributes: NodeFnAttributes {
category: Some(parse_quote!("IO")), category: Some(parse_quote!("IO")),
display_name: None, display_name: None,
@@ -1115,6 +1124,7 @@ mod tests {
let parsed = parse_node_fn(attr, input).unwrap(); let parsed = parse_node_fn(attr, input).unwrap();
let expected = ParsedNodeFn { let expected = ParsedNodeFn {
vis: Visibility::Inherited,
attributes: NodeFnAttributes { attributes: NodeFnAttributes {
category: Some(parse_quote!("Custom")), category: Some(parse_quote!("Custom")),
display_name: Some(parse_quote!("CustomNode2")), display_name: Some(parse_quote!("CustomNode2")),