Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -2175,6 +2175,8 @@ fn static_node_properties() -> NodeProperties {
map.insert("grid_properties".to_string(), Box::new(node_properties::grid_properties));
map.insert("spiral_properties".to_string(), Box::new(node_properties::spiral_properties));
map.insert("sample_polyline_properties".to_string(), Box::new(node_properties::sample_polyline_properties));
map.insert("repeat_properties".to_string(), Box::new(node_properties::repeat_properties));
map.insert("circular_repeat_properties".to_string(), Box::new(node_properties::circular_repeat_properties));
map.insert(
"monitor_properties".to_string(),
Box::new(|_node_id, _context| node_properties::string_properties("Used internally by the editor to obtain a layer thumbnail.")),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use graphene_std::text::{Font, TextAlign};
use graphene_std::transform::{Footprint, ReferencePoint, Transform};
use graphene_std::vector::misc::{ArcType, CentroidType, ExtrudeJoiningAlgorithm, GridType, MergeByDistanceAlgorithm, PointSpacingType, SpiralType};
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
use graphene_std::vector::{AngularSpacingMethod, RepeatSpacingMethod};

pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {
let widget = TextLabel::new(text).widget_holder();
Expand Down Expand Up @@ -221,6 +222,8 @@ pub(crate) fn property_from_type(
Some(x) if x == TypeId::of::<MergeByDistanceAlgorithm>() => enum_choice::<MergeByDistanceAlgorithm>().for_socket(default_info).property_row(),
Some(x) if x == TypeId::of::<ExtrudeJoiningAlgorithm>() => enum_choice::<ExtrudeJoiningAlgorithm>().for_socket(default_info).property_row(),
Some(x) if x == TypeId::of::<PointSpacingType>() => enum_choice::<PointSpacingType>().for_socket(default_info).property_row(),
Some(x) if x == TypeId::of::<RepeatSpacingMethod>() => enum_choice::<RepeatSpacingMethod>().for_socket(default_info).property_row(),
Some(x) if x == TypeId::of::<AngularSpacingMethod>() => enum_choice::<AngularSpacingMethod>().for_socket(default_info).property_row(),
Some(x) if x == TypeId::of::<BooleanOperation>() => enum_choice::<BooleanOperation>().for_socket(default_info).property_row(),
Some(x) if x == TypeId::of::<CentroidType>() => enum_choice::<CentroidType>().for_socket(default_info).property_row(),
Some(x) if x == TypeId::of::<LuminanceCalculation>() => enum_choice::<LuminanceCalculation>().for_socket(default_info).property_row(),
Expand Down Expand Up @@ -1340,6 +1343,46 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
widgets
}

pub(crate) fn repeat_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
const DIRECTION_INDEX: usize = 1;
const ANGLE_INDEX: usize = 2;
const COUNT_INDEX: usize = 3;
const SPACING_METHOD_INDEX: usize = 4;

let direction = vec2_widget(ParameterWidgetsInfo::new(node_id, DIRECTION_INDEX, true, context), "X", "Y", " px", None, false);
let angle = number_widget(ParameterWidgetsInfo::new(node_id, ANGLE_INDEX, true, context), NumberInput::default().unit("°"));
let count = number_widget(ParameterWidgetsInfo::new(node_id, COUNT_INDEX, true, context), NumberInput::default().min(1.).int());
let spacing_method = enum_choice::<RepeatSpacingMethod>()
.for_socket(ParameterWidgetsInfo::new(node_id, SPACING_METHOD_INDEX, true, context))
.property_row();

vec![direction, LayoutGroup::Row { widgets: angle }, LayoutGroup::Row { widgets: count }, spacing_method]
}

pub(crate) fn circular_repeat_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
const START_ANGLE_INDEX: usize = 1;
const END_ANGLE_INDEX: usize = 2;
const RADIUS_INDEX: usize = 3;
const COUNT_INDEX: usize = 4;
const ANGULAR_SPACING_METHOD_INDEX: usize = 5;

let start_angle = number_widget(ParameterWidgetsInfo::new(node_id, START_ANGLE_INDEX, true, context), NumberInput::default().unit("°"));
let end_angle = number_widget(ParameterWidgetsInfo::new(node_id, END_ANGLE_INDEX, true, context), NumberInput::default().unit("°"));
let radius = number_widget(ParameterWidgetsInfo::new(node_id, RADIUS_INDEX, true, context), NumberInput::default().min(0.).unit(" px"));
let count = number_widget(ParameterWidgetsInfo::new(node_id, COUNT_INDEX, true, context), NumberInput::default().min(1.).int());
let angular_spacing_method = enum_choice::<AngularSpacingMethod>()
.for_socket(ParameterWidgetsInfo::new(node_id, ANGULAR_SPACING_METHOD_INDEX, true, context))
.property_row();

vec![
LayoutGroup::Row { widgets: start_angle },
LayoutGroup::Row { widgets: end_angle },
LayoutGroup::Row { widgets: radius },
LayoutGroup::Row { widgets: count },
angular_spacing_method,
]
}

pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
use graphene_std::vector::generator_nodes::spiral::*;

Expand Down
1 change: 1 addition & 0 deletions node-graph/graph-craft/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ graphene-core = { workspace = true }
graphene-application-io = { workspace = true }
rendering = { workspace = true }
raster-nodes = { workspace = true }
vector-nodes = { workspace = true }
graphic-types = { workspace = true }
text-nodes = { workspace = true }

Expand Down
3 changes: 3 additions & 0 deletions node-graph/graph-craft/src/document/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use std::hash::Hash;
use std::marker::PhantomData;
use std::str::FromStr;
pub use std::sync::Arc;
use vector_nodes::{AngularSpacingMethod, RepeatSpacingMethod};

pub struct TaggedValueTypeError;

Expand Down Expand Up @@ -248,6 +249,8 @@ tagged_value! {
ExtrudeJoiningAlgorithm(vector::misc::ExtrudeJoiningAlgorithm),
PointSpacingType(vector::misc::PointSpacingType),
SpiralType(vector::misc::SpiralType),
RepeatSpacingMethod(RepeatSpacingMethod),
AngularSpacingMethod(AngularSpacingMethod),
#[serde(alias = "LineCap")]
StrokeCap(vector::style::StrokeCap),
#[serde(alias = "LineJoin")]
Expand Down
88 changes: 79 additions & 9 deletions node-graph/nodes/vector/src/vector_nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use core_types::registry::types::{Angle, IntegerCount, Length, Multiplier, Perce
use core_types::table::{Table, TableRow, TableRowMut};
use core_types::transform::{Footprint, Transform};
use core_types::{CloneVarArgs, Color, Context, Ctx, ExtractAll, ExtractVarArgs, OwnedContextImpl};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
use graphic_types::Vector;
use graphic_types::raster_types::{CPU, GPU, Raster};
Expand Down Expand Up @@ -225,8 +226,32 @@ where
content
}

#[node_macro::node(category("Instancing"), path(core_types::vector))]
async fn repeat<I: 'n + Send + Clone>(
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]
#[widget(Radio)]
pub enum RepeatSpacingMethod {
#[default]
#[serde(rename = "span")]
Span,
#[serde(rename = "envelope")]
Envelope,
#[serde(rename = "pitch")]
Pitch,
#[serde(rename = "gap")]
Gap,
}

#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, node_macro::ChoiceType)]
#[widget(Radio)]
pub enum AngularSpacingMethod {
#[default]
#[serde(rename = "span")]
Span,
#[serde(rename = "pitch")]
Pitch,
}

#[node_macro::node(category("Instancing"), path(graphene_core::vector), properties("repeat_properties"))]
async fn repeat<I: 'n + Send + Clone + BoundingBox>(
_: impl Ctx,
// TODO: Implement other graphical types.
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>, Table<GradientStops>)] instance: Table<I>,
Expand All @@ -235,16 +260,38 @@ async fn repeat<I: 'n + Send + Clone>(
direction: PixelSize,
angle: Angle,
#[default(5)] count: IntegerCount,
#[default(RepeatSpacingMethod::Span)] spacing_method: RepeatSpacingMethod,
) -> Table<I> {
let angle = angle.to_radians();
let count = count.max(1);
let total = (count - 1) as f64;
let direction_normalized = direction.normalize();

let width = if matches!(spacing_method, RepeatSpacingMethod::Envelope | RepeatSpacingMethod::Gap) {
match instance.bounding_box(DAffine2::IDENTITY, false) {
RenderBoundingBox::Rectangle([min, max]) => {
let size = max - min;
let dir_abs = direction_normalized.abs();
size.x * dir_abs.x + size.y * dir_abs.y
}
_ => 0.0,
}
} else {
0.0
};

let (pitch, offset) = match spacing_method {
RepeatSpacingMethod::Span => (direction.length() / total.max(1.), DVec2::ZERO),
RepeatSpacingMethod::Envelope => ((direction.length() - width) / total.max(1.), width / 2. * direction_normalized),
RepeatSpacingMethod::Pitch => (direction.length(), DVec2::ZERO),
RepeatSpacingMethod::Gap => (direction.length() + width, DVec2::ZERO),
};

let mut result_table = Table::new();

for index in 0..count {
let angle = index as f64 * angle / total;
let translation = index as f64 * direction / total;
let angle = index as f64 * angle / total.max(1.);
let translation = offset + index as f64 * pitch * direction_normalized;
let transform = DAffine2::from_angle(angle) * DAffine2::from_translation(translation);

for row in instance.iter() {
Expand All @@ -261,22 +308,34 @@ async fn repeat<I: 'n + Send + Clone>(
result_table
}

#[node_macro::node(category("Instancing"), path(core_types::vector))]
#[node_macro::node(category("Instancing"), path(graphene_core::vector), properties("circular_repeat_properties"))]
async fn circular_repeat<I: 'n + Send + Clone>(
_: impl Ctx,
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>, Table<GradientStops>)] instance: Table<I>,
start_angle: Angle,
#[default(0.)] start_angle: Angle,
#[default(360.)] end_angle: Angle,
#[unit(" px")]
#[default(5)]
radius: f64,
#[default(5)] count: IntegerCount,
#[default(AngularSpacingMethod::Span)] angular_spacing_method: AngularSpacingMethod,
) -> Table<I> {
let count = count.max(1);
let start_rad = start_angle.to_radians();
let end_rad = end_angle.to_radians();
let total_angle = end_rad - start_rad;
let total = (count - 1) as f64;

let angular_pitch = match angular_spacing_method {
AngularSpacingMethod::Span => total_angle / total.max(1.),
AngularSpacingMethod::Pitch => total_angle / count as f64,
};

let mut result_table = Table::new();

for index in 0..count {
let angle = DAffine2::from_angle((TAU / count as f64) * index as f64 + start_angle.to_radians());
let angle_rad = start_rad + index as f64 * angular_pitch;
let angle = DAffine2::from_angle(angle_rad);
let translation = DAffine2::from_translation(radius * DVec2::Y);
let transform = angle * translation;

Expand Down Expand Up @@ -2417,6 +2476,7 @@ mod test {
direction,
0.,
count,
super::RepeatSpacingMethod::Span,
)
.await;
let vector_table = super::flatten_path(Footprint::default(), repeated).await;
Expand All @@ -2436,6 +2496,7 @@ mod test {
direction,
0.,
count,
super::RepeatSpacingMethod::Span,
)
.await;
let vector_table = super::flatten_path(Footprint::default(), repeated).await;
Expand All @@ -2447,7 +2508,16 @@ mod test {
}
#[tokio::test]
async fn circular_repeat() {
let repeated = super::circular_repeat(Footprint::default(), vector_node_from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY)), 45., 4., 8).await;
let repeated = super::circular_repeat(
Footprint::default(),
vector_node_from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY)),
45.,
360.,
4.,
8,
super::AngularSpacingMethod::Span,
)
.await;
let vector_table = super::flatten_path(Footprint::default(), repeated).await;
let vector = vector_table.iter().next().unwrap().element;
assert_eq!(vector.region_manipulator_groups().count(), 8);
Expand Down Expand Up @@ -2588,7 +2658,7 @@ mod test {
#[tokio::test]
async fn morph() {
let rectangle = vector_node_from_bezpath(Rect::new(0., 0., 100., 100.).to_path(DEFAULT_ACCURACY));
let rectangles = super::repeat(Footprint::default(), rectangle, DVec2::new(-100., -100.), 0., 2).await;
let rectangles = super::repeat(Footprint::default(), rectangle, DVec2::new(-100., -100.), 0., 2, super::RepeatSpacingMethod::Span).await;
let morphed = super::morph(Footprint::default(), rectangles, 0.5).await;
let element = morphed.iter().next().unwrap().element;
assert_eq!(
Expand Down