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

View file

@ -1,5 +1,14 @@
use crate::raster_types::{CPU, RasterDataTable};
use crate::vector::VectorDataTable;
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.
#[node_macro::node(category("Debug"))]

View file

@ -2,7 +2,9 @@ use crate::Ctx;
use dyn_any::DynAny;
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"))]
fn extract_xy<T: Into<DVec2>>(_: impl Ctx, #[implementations(DVec2, IVec2, UVec2)] vector: T, axis: XY) -> f64 {
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::{Color, Context, Ctx};
use crate::{Context, 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
}
#[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 {
format!("{:?}", value)
@ -45,24 +44,42 @@ async fn switch<T, C: Send + 'n + Clone>(
#[implementations(
Context -> String,
Context -> bool,
Context -> f32,
Context -> f64,
Context -> u32,
Context -> u64,
Context -> DVec2,
Context -> VectorDataTable,
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>,
#[expose]
#[implementations(
Context -> String,
Context -> bool,
Context -> f32,
Context -> f64,
Context -> u32,
Context -> u64,
Context -> DVec2,
Context -> VectorDataTable,
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>,
) -> T {

View file

@ -73,9 +73,17 @@ pub trait Convert<T>: Sized {
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.
macro_rules! impl_convert {
($from:ty,$to:ty) => {
($from:ty, $to:ty) => {
impl Convert<$to> for $from {
fn convert(self) -> $to {
self as $to

View file

@ -70,6 +70,7 @@ async fn boundless_footprint<T: 'n + 'static>(
transform_target.eval(ctx.into_context()).await
}
#[node_macro::node(category("Debug"))]
async fn freeze_real_time<T: 'n + 'static>(
ctx: impl Ctx + CloneVarArgs + ExtractAll,

View file

@ -86,6 +86,7 @@ async fn instance_position(ctx: impl Ctx + ExtractVarArgs) -> DVec2 {
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))]
async fn instance_index(ctx: impl Ctx + ExtractIndex) -> f64 {
match ctx.try_index() {

View file

@ -37,7 +37,13 @@ impl CornerRadius for [f64; 4] {
}
#[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();
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(
_: impl Ctx,
_primary: (),
#[default(50.)] radius: f64,
#[unit(" px")]
#[default(50.)]
radius: f64,
start_angle: Angle,
#[default(270.)]
#[range((0., 360.))]
@ -66,7 +74,16 @@ fn arc(
}
#[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 corner1 = -radius;
let corner2 = radius;
@ -87,8 +104,12 @@ fn ellipse(_: impl Ctx, _primary: (), #[default(50)] radius_x: f64, #[default(25
fn rectangle<T: CornerRadius>(
_: impl Ctx,
_primary: (),
#[default(100)] width: f64,
#[default(100)] height: f64,
#[unit(" px")]
#[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
#[implementations(f64, [f64; 4])] corner_radius: T,
#[default(true)] clamped: bool,
@ -104,7 +125,9 @@ fn regular_polygon<T: AsU64>(
#[hard_min(3.)]
#[implementations(u32, u64, f64)]
sides: T,
#[default(50)] radius: f64,
#[unit(" px")]
#[default(50)]
radius: f64,
) -> VectorDataTable {
let points = sides.as_u64();
let radius: f64 = radius * 2.;
@ -119,8 +142,12 @@ fn star<T: AsU64>(
#[hard_min(2.)]
#[implementations(u32, u64, f64)]
sides: T,
#[default(50)] radius_1: f64,
#[default(25)] radius_2: f64,
#[unit(" px")]
#[default(50)]
radius_1: f64,
#[unit(" px")]
#[default(25)]
radius_2: f64,
) -> VectorDataTable {
let points = sides.as_u64();
let diameter: f64 = radius_1 * 2.;
@ -130,7 +157,7 @@ fn star<T: AsU64>(
}
#[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)))
}
@ -153,13 +180,14 @@ fn grid<T: GridSpacing>(
_: impl Ctx,
_primary: (),
grid_type: GridType,
#[unit(" px")]
#[hard_min(0.)]
#[default(10)]
#[implementations(f64, DVec2)]
spacing: T,
#[default(30., 30.)] angles: DVec2,
#[default(10)] columns: u32,
#[default(10)] rows: u32,
#[default(30., 30.)] angles: DVec2,
) -> VectorDataTable {
let (x_spacing, y_spacing) = spacing.as_dvec2().into();
let (angle_a, angle_b) = angles.into();
@ -251,11 +279,11 @@ mod tests {
#[test]
fn isometric_grid_test() {
// Doesn't crash with weird angles
grid((), (), GridType::Isometric, 0., (0., 0.).into(), 5, 5);
grid((), (), GridType::Isometric, 90., (90., 90.).into(), 5, 5);
grid((), (), GridType::Isometric, 0., 5, 5, (0., 0.).into());
grid((), (), GridType::Isometric, 90., 5, 5, (90., 90.).into());
// 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.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter() {
@ -270,7 +298,7 @@ mod tests {
#[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.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
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,
#[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")]
/// The seed used for randomization.
/// Seed to determine unique variations on the randomized color selection.
seed: SeedValue,
#[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")]
/// The number of elements to span across the gradient before repeating. A 0 value will span the entire gradient once.
@ -165,6 +166,7 @@ async fn stroke<C: Into<Option<Color>> + 'n + Send, V>(
#[default(Color::BLACK)]
/// The stroke color.
color: C,
#[unit(" px")]
#[default(2.)]
/// The stroke weight.
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.
dash_lengths: Vec<f64>,
/// The phase offset distance from the starting point of the dash pattern.
#[unit(" px")]
dash_offset: f64,
) -> Instances<V>
where
@ -253,7 +256,9 @@ async fn circular_repeat<I: 'n + Send + Clone>(
// TODO: Implement other GraphicElementRendered types.
#[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>)] instance: Instances<I>,
angle_offset: Angle,
#[default(5)] radius: f64,
#[unit(" px")]
#[default(5)]
radius: f64,
#[default(5)] instances: IntegerCount,
) -> Instances<I> {
let count = instances.max(1);
@ -363,7 +368,7 @@ async fn mirror<I: 'n + Send + Clone>(
_: impl Ctx,
#[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>)] instance: Instances<I>,
#[default(ReferencePoint::Center)] relative_to_bounds: ReferencePoint,
offset: f64,
#[unit(" px")] offset: f64,
#[range((-90., 90.))] angle: Angle,
#[default(true)] keep_original: bool,
) -> Instances<I>
@ -1139,10 +1144,10 @@ async fn sample_polyline(
_: impl Ctx,
vector_data: VectorDataTable,
spacing: PointSpacingType,
separation: f64,
quantity: f64,
start_offset: f64,
stop_offset: f64,
#[unit(" px")] separation: f64,
quantity: u32,
#[unit(" px")] start_offset: f64,
#[unit(" px")] stop_offset: f64,
adaptive_spacing: bool,
subpath_segment_lengths: Vec<f64>,
) -> VectorDataTable {
@ -1182,7 +1187,7 @@ async fn sample_polyline(
let amount = match spacing {
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 {
continue;
@ -1388,6 +1393,7 @@ async fn tangent_on_path(
async fn poisson_disk_points(
_: impl Ctx,
vector_data: VectorDataTable,
#[unit(" px")]
#[default(10.)]
#[hard_min(0.01)]
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))]
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();
for mut vector_data_instance in vector_data.instance_iter() {
@ -2080,7 +2093,7 @@ mod test {
#[tokio::test]
async fn sample_polyline() {
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
let sample_polyline = super::sample_polyline(Footprint::default(), vector_node(path), PointSpacingType::Separation, 30., 0., 0., 0., false, vec![100.]).await;
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;
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.]) {
@ -2090,7 +2103,7 @@ mod test {
#[tokio::test]
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 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;
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.]) {