Clean up node catalog by adding missing units, more tooltips; fix 'Line' node missing parameters (#2813)

* Fix unit usages

* Add node and parameter doc comments

* Fix the parameters panel for the 'Line' node when added from the graph

* Clean up nodes

* Fix tests

* Update the demo artwork
This commit is contained in:
Keavon Chambers 2025-07-01 07:47:54 -07:00 committed by GitHub
parent 0febfaf142
commit 8c5accc069
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 366 additions and 89 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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -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),

View file

@ -1427,6 +1427,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()

View file

@ -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

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"))]

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 {

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 {

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

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,

View file

@ -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() {

View file

@ -37,7 +37,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))))
} }
@ -46,7 +52,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.))]
@ -66,7 +74,16 @@ fn arc(
} }
#[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;
@ -87,8 +104,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,
@ -104,7 +125,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.;
@ -119,8 +142,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.;
@ -130,7 +157,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)))
} }
@ -153,13 +180,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();
@ -251,11 +279,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() {
@ -270,7 +298,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() {

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.]) {

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
} }

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