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
21 changed files with 366 additions and 89 deletions

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