Rename the repeat nodes to replace "Instance" terminology with "Repeat" (#3794)
* WIP * Move the Mirror node from the module 'vector' to 'graphic' * Update demo art * Fix failing tests Fix tests
This commit is contained in:
parent
5a1503fc98
commit
7ca6470656
|
|
@ -2295,6 +2295,7 @@ dependencies = [
|
||||||
"path-bool-nodes",
|
"path-bool-nodes",
|
||||||
"raster-nodes",
|
"raster-nodes",
|
||||||
"rendering",
|
"rendering",
|
||||||
|
"repeat-nodes",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"text-nodes",
|
"text-nodes",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
@ -5078,6 +5079,25 @@ dependencies = [
|
||||||
"vello",
|
"vello",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "repeat-nodes"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"core-types",
|
||||||
|
"dyn-any",
|
||||||
|
"glam",
|
||||||
|
"graphene-core",
|
||||||
|
"graphic-types",
|
||||||
|
"kurbo 0.12.0",
|
||||||
|
"log",
|
||||||
|
"node-macro",
|
||||||
|
"raster-types",
|
||||||
|
"serde",
|
||||||
|
"tokio",
|
||||||
|
"vector-nodes",
|
||||||
|
"vector-types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.12.23"
|
version = "0.12.23"
|
||||||
|
|
@ -6431,6 +6451,7 @@ dependencies = [
|
||||||
"glam",
|
"glam",
|
||||||
"graphic-types",
|
"graphic-types",
|
||||||
"node-macro",
|
"node-macro",
|
||||||
|
"rand 0.9.2",
|
||||||
"serde",
|
"serde",
|
||||||
"vector-types",
|
"vector-types",
|
||||||
]
|
]
|
||||||
|
|
@ -6734,6 +6755,7 @@ dependencies = [
|
||||||
"node-macro",
|
"node-macro",
|
||||||
"qrcodegen",
|
"qrcodegen",
|
||||||
"rand 0.9.2",
|
"rand 0.9.2",
|
||||||
|
"repeat-nodes",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
"serde",
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ members = [
|
||||||
"node-graph/nodes/raster/shaders/entrypoint",
|
"node-graph/nodes/raster/shaders/entrypoint",
|
||||||
"node-graph/nodes/text",
|
"node-graph/nodes/text",
|
||||||
"node-graph/nodes/transform",
|
"node-graph/nodes/transform",
|
||||||
|
"node-graph/nodes/repeat",
|
||||||
"node-graph/nodes/vector",
|
"node-graph/nodes/vector",
|
||||||
"node-graph/graph-craft",
|
"node-graph/graph-craft",
|
||||||
"node-graph/graphene-cli",
|
"node-graph/graphene-cli",
|
||||||
|
|
@ -116,6 +117,7 @@ graphic-nodes = { path = "node-graph/nodes/graphic" }
|
||||||
text-nodes = { path = "node-graph/nodes/text" }
|
text-nodes = { path = "node-graph/nodes/text" }
|
||||||
transform-nodes = { path = "node-graph/nodes/transform" }
|
transform-nodes = { path = "node-graph/nodes/transform" }
|
||||||
vector-nodes = { path = "node-graph/nodes/vector" }
|
vector-nodes = { path = "node-graph/nodes/vector" }
|
||||||
|
repeat-nodes = { path = "node-graph/nodes/repeat" }
|
||||||
math-nodes = { path = "node-graph/nodes/math" }
|
math-nodes = { path = "node-graph/nodes/math" }
|
||||||
path-bool-nodes = { path = "node-graph/nodes/path-bool" }
|
path-bool-nodes = { path = "node-graph/nodes/path-bool" }
|
||||||
graph-craft = { path = "node-graph/graph-craft" }
|
graph-craft = { path = "node-graph/graph-craft" }
|
||||||
|
|
|
||||||
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
|
|
@ -502,10 +502,10 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
||||||
// [11:Equals]0 -> 0[17:Switch]
|
// [11:Equals]0 -> 0[17:Switch]
|
||||||
// [9:Transform]0 -> 1[17:Switch]
|
// [9:Transform]0 -> 1[17:Switch]
|
||||||
// [16:Morph]0 -> 2[17:Switch]
|
// [16:Morph]0 -> 2[17:Switch]
|
||||||
// [17:Switch]0 -> 0[18:Instance Repeat]
|
// [17:Switch]0 -> 0[18:Repeat]
|
||||||
// [0:Floor]0 -> 1[18:Instance Repeat]
|
// [0:Floor]0 -> 1[18:Repeat]
|
||||||
// [IMPORTS]3 -> 2[18:Instance Repeat]
|
// [IMPORTS]3 -> 2[18:Repeat]
|
||||||
// [18:Instance Repeat]0 -> 0[EXPORTS]
|
// [18:Repeat]0 -> 0[EXPORTS]
|
||||||
node_template: NodeTemplate {
|
node_template: NodeTemplate {
|
||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||||
|
|
@ -635,9 +635,9 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
||||||
inputs: vec![NodeInput::node(NodeId(11), 0), NodeInput::node(NodeId(9), 0), NodeInput::node(NodeId(16), 0)],
|
inputs: vec![NodeInput::node(NodeId(11), 0), NodeInput::node(NodeId(9), 0), NodeInput::node(NodeId(16), 0)],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// 18: Instance Repeat
|
// 18: Repeat
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::instance_repeat::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(repeat_nodes::repeat::IDENTIFIER),
|
||||||
inputs: vec![NodeInput::node(NodeId(17), 0), NodeInput::node(NodeId(0), 0), NodeInput::import(generic!(T), 3)],
|
inputs: vec![NodeInput::node(NodeId(17), 0), NodeInput::node(NodeId(0), 0), NodeInput::import(generic!(T), 3)],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
|
|
@ -807,7 +807,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// 18: Instance Repeat
|
// 18: Repeat
|
||||||
DocumentNodeMetadata {
|
DocumentNodeMetadata {
|
||||||
persistent_metadata: DocumentNodePersistentMetadata {
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(49, -1)),
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(49, -1)),
|
||||||
|
|
|
||||||
|
|
@ -753,18 +753,10 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||||
node: graphene_std::vector::centroid::IDENTIFIER,
|
node: graphene_std::vector::centroid::IDENTIFIER,
|
||||||
aliases: &["graphene_core::vector::CentroidNode"],
|
aliases: &["graphene_core::vector::CentroidNode"],
|
||||||
},
|
},
|
||||||
NodeReplacement {
|
|
||||||
node: graphene_std::vector::circular_repeat::IDENTIFIER,
|
|
||||||
aliases: &["graphene_core::vector::CircularRepeatNode"],
|
|
||||||
},
|
|
||||||
NodeReplacement {
|
NodeReplacement {
|
||||||
node: graphene_std::vector::close_path::IDENTIFIER,
|
node: graphene_std::vector::close_path::IDENTIFIER,
|
||||||
aliases: &["graphene_core::vector::ClosePathNode"],
|
aliases: &["graphene_core::vector::ClosePathNode"],
|
||||||
},
|
},
|
||||||
NodeReplacement {
|
|
||||||
node: graphene_std::vector::copy_to_points::IDENTIFIER,
|
|
||||||
aliases: &["graphene_core::vector::CopyToPointsNode"],
|
|
||||||
},
|
|
||||||
NodeReplacement {
|
NodeReplacement {
|
||||||
node: graphene_std::vector::count_elements::IDENTIFIER,
|
node: graphene_std::vector::count_elements::IDENTIFIER,
|
||||||
aliases: &["graphene_core::vector::CountElementsNode"],
|
aliases: &["graphene_core::vector::CountElementsNode"],
|
||||||
|
|
@ -841,22 +833,30 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||||
node: graphene_std::graphic::map::IDENTIFIER,
|
node: graphene_std::graphic::map::IDENTIFIER,
|
||||||
aliases: &["graphene_core::vector::InstanceMapNode"],
|
aliases: &["graphene_core::vector::InstanceMapNode"],
|
||||||
},
|
},
|
||||||
NodeReplacement {
|
|
||||||
node: graphene_std::vector::instance_on_points::IDENTIFIER,
|
|
||||||
aliases: &["graphene_core::vector::InstanceOnPointsNode"],
|
|
||||||
},
|
|
||||||
NodeReplacement {
|
NodeReplacement {
|
||||||
node: graphene_std::context::read_position::IDENTIFIER,
|
node: graphene_std::context::read_position::IDENTIFIER,
|
||||||
aliases: &["graphene_core::vector::InstancePositionNode", "core_types::vector::InstancePositionNode"],
|
aliases: &["graphene_core::vector::InstancePositionNode", "core_types::vector::InstancePositionNode"],
|
||||||
},
|
},
|
||||||
NodeReplacement {
|
|
||||||
node: graphene_std::vector::instance_repeat::IDENTIFIER,
|
|
||||||
aliases: &["graphene_core::vector::InstanceRepeatNode"],
|
|
||||||
},
|
|
||||||
NodeReplacement {
|
NodeReplacement {
|
||||||
node: graphene_std::context::read_vector::IDENTIFIER,
|
node: graphene_std::context::read_vector::IDENTIFIER,
|
||||||
aliases: &["graphene_core::vector::InstanceVectorNode"],
|
aliases: &["graphene_core::vector::InstanceVectorNode"],
|
||||||
},
|
},
|
||||||
|
NodeReplacement {
|
||||||
|
node: graphene_std::repeat::repeat::IDENTIFIER,
|
||||||
|
aliases: &["graphene_core::vector::InstanceRepeatNode", "core_types::vector::InstanceRepeatNode"],
|
||||||
|
},
|
||||||
|
NodeReplacement {
|
||||||
|
node: graphene_std::repeat::repeat_array::IDENTIFIER,
|
||||||
|
aliases: &["graphene_core::vector::RepeatNode", "core_types::vector::RepeatNode"],
|
||||||
|
},
|
||||||
|
NodeReplacement {
|
||||||
|
node: graphene_std::repeat::repeat_radial::IDENTIFIER,
|
||||||
|
aliases: &["graphene_core::vector::CircularRepeatNode", "core_types::vector::CircularRepeatNode"],
|
||||||
|
},
|
||||||
|
NodeReplacement {
|
||||||
|
node: graphene_std::repeat::repeat_on_points::IDENTIFIER,
|
||||||
|
aliases: &["graphene_core::vector::InstanceOnPointsNode", "core_types::vector::InstanceOnPointsNode"],
|
||||||
|
},
|
||||||
NodeReplacement {
|
NodeReplacement {
|
||||||
node: graphene_std::vector::jitter_points::IDENTIFIER,
|
node: graphene_std::vector::jitter_points::IDENTIFIER,
|
||||||
aliases: &["graphene_core::vector::JitterPointsNode"],
|
aliases: &["graphene_core::vector::JitterPointsNode"],
|
||||||
|
|
@ -866,8 +866,8 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||||
aliases: &["graphene_core::vector::MergeByDistanceNode"],
|
aliases: &["graphene_core::vector::MergeByDistanceNode"],
|
||||||
},
|
},
|
||||||
NodeReplacement {
|
NodeReplacement {
|
||||||
node: graphene_std::vector::mirror::IDENTIFIER,
|
node: graphene_std::graphic::mirror::IDENTIFIER,
|
||||||
aliases: &["graphene_core::vector::MirrorNode"],
|
aliases: &["graphene_core::vector::MirrorNode", "core_types::vector::MirrorNode"],
|
||||||
},
|
},
|
||||||
NodeReplacement {
|
NodeReplacement {
|
||||||
node: graphene_std::vector::morph::IDENTIFIER,
|
node: graphene_std::vector::morph::IDENTIFIER,
|
||||||
|
|
@ -905,10 +905,6 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
||||||
node: graphene_std::vector::position_on_path::IDENTIFIER,
|
node: graphene_std::vector::position_on_path::IDENTIFIER,
|
||||||
aliases: &["graphene_core::vector::PositionOnPathNode"],
|
aliases: &["graphene_core::vector::PositionOnPathNode"],
|
||||||
},
|
},
|
||||||
NodeReplacement {
|
|
||||||
node: graphene_std::vector::repeat::IDENTIFIER,
|
|
||||||
aliases: &["graphene_core::vector::RepeatNode"],
|
|
||||||
},
|
|
||||||
NodeReplacement {
|
NodeReplacement {
|
||||||
node: graphene_std::vector::round_corners::IDENTIFIER,
|
node: graphene_std::vector::round_corners::IDENTIFIER,
|
||||||
aliases: &["graphene_core::vector::RoundCornersNode"],
|
aliases: &["graphene_core::vector::RoundCornersNode"],
|
||||||
|
|
@ -1337,7 +1333,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upgrade the Mirror node to add the `keep_original` boolean input
|
// Upgrade the Mirror node to add the `keep_original` boolean input
|
||||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::mirror::IDENTIFIER) && inputs_count == 3 {
|
if reference == DefinitionIdentifier::ProtoNode(graphene_std::graphic::mirror::IDENTIFIER) && inputs_count == 3 {
|
||||||
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
|
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
|
||||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||||
|
|
||||||
|
|
@ -1352,7 +1348,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upgrade the Mirror node to add the `reference_point` input and change `offset` from `DVec2` to `f64`
|
// Upgrade the Mirror node to add the `reference_point` input and change `offset` from `DVec2` to `f64`
|
||||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::mirror::IDENTIFIER) && inputs_count == 4 {
|
if reference == DefinitionIdentifier::ProtoNode(graphene_std::graphic::mirror::IDENTIFIER) && inputs_count == 4 {
|
||||||
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
|
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
|
||||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||||
|
|
||||||
|
|
@ -1404,7 +1400,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::instance_on_points::IDENTIFIER) && inputs_count == 2 {
|
if reference == DefinitionIdentifier::ProtoNode(graphene_std::repeat::repeat_on_points::IDENTIFIER) && inputs_count == 2 {
|
||||||
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
|
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
|
||||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use core_types::registry::types::SignedInteger;
|
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||||
|
use core_types::registry::types::{Angle, SignedInteger};
|
||||||
use core_types::table::{Table, TableRow};
|
use core_types::table::{Table, TableRow};
|
||||||
use core_types::uuid::NodeId;
|
use core_types::uuid::NodeId;
|
||||||
use core_types::{AnyHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
|
use core_types::{AnyHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
|
||||||
|
|
@ -6,9 +7,9 @@ use glam::{DAffine2, DVec2};
|
||||||
use graphic_types::graphic::{Graphic, IntoGraphicTable};
|
use graphic_types::graphic::{Graphic, IntoGraphicTable};
|
||||||
use graphic_types::{Artboard, Vector};
|
use graphic_types::{Artboard, Vector};
|
||||||
use raster_types::{CPU, GPU, Raster};
|
use raster_types::{CPU, GPU, Raster};
|
||||||
use vector_types::GradientStops;
|
use vector_types::{GradientStops, ReferencePoint};
|
||||||
|
|
||||||
#[node_macro::node(category("General"), path(graphene_core::vector))]
|
#[node_macro::node(category("General"))]
|
||||||
async fn map<Item: AnyHash + Send + Sync + std::hash::Hash>(
|
async fn map<Item: AnyHash + Send + Sync + std::hash::Hash>(
|
||||||
ctx: impl Ctx + CloneVarArgs + ExtractAll,
|
ctx: impl Ctx + CloneVarArgs + ExtractAll,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
|
|
@ -41,6 +42,70 @@ async fn map<Item: AnyHash + Send + Sync + std::hash::Hash>(
|
||||||
rows
|
rows
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[node_macro::node(category("General"))]
|
||||||
|
async fn mirror<T: 'n + Send + Clone>(
|
||||||
|
_: impl Ctx,
|
||||||
|
#[implementations(
|
||||||
|
Table<Graphic>,
|
||||||
|
Table<Vector>,
|
||||||
|
Table<Raster<CPU>>,
|
||||||
|
Table<Color>,
|
||||||
|
Table<GradientStops>,
|
||||||
|
)]
|
||||||
|
content: Table<T>,
|
||||||
|
#[default(ReferencePoint::Center)] relative_to_bounds: ReferencePoint,
|
||||||
|
#[unit(" px")] offset: f64,
|
||||||
|
#[range((-90., 90.))] angle: Angle,
|
||||||
|
#[default(true)] keep_original: bool,
|
||||||
|
) -> Table<T>
|
||||||
|
where
|
||||||
|
Table<T>: BoundingBox,
|
||||||
|
{
|
||||||
|
// Normalize the direction vector
|
||||||
|
let normal = DVec2::from_angle(angle.to_radians());
|
||||||
|
|
||||||
|
// The mirror reference may be based on the bounding box if an explicit reference point is chosen
|
||||||
|
let RenderBoundingBox::Rectangle(bounding_box) = content.bounding_box(DAffine2::IDENTITY, false) else {
|
||||||
|
return content;
|
||||||
|
};
|
||||||
|
|
||||||
|
let reference_point_location = relative_to_bounds.point_in_bounding_box((bounding_box[0], bounding_box[1]).into());
|
||||||
|
let mirror_reference_point = reference_point_location.map(|point| point + normal * offset);
|
||||||
|
|
||||||
|
// Create the reflection matrix
|
||||||
|
let reflection = DAffine2::from_mat2_translation(
|
||||||
|
glam::DMat2::from_cols(
|
||||||
|
DVec2::new(1. - 2. * normal.x * normal.x, -2. * normal.y * normal.x),
|
||||||
|
DVec2::new(-2. * normal.x * normal.y, 1. - 2. * normal.y * normal.y),
|
||||||
|
),
|
||||||
|
DVec2::ZERO,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Apply reflection around the reference point
|
||||||
|
let reflected_transform = if let Some(mirror_reference_point) = mirror_reference_point {
|
||||||
|
DAffine2::from_translation(mirror_reference_point) * reflection * DAffine2::from_translation(-mirror_reference_point)
|
||||||
|
} else {
|
||||||
|
reflection * DAffine2::from_translation(DVec2::from_angle(angle.to_radians()) * DVec2::splat(-offset))
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut result_table = Table::new();
|
||||||
|
|
||||||
|
// Add original instance depending on the keep_original flag
|
||||||
|
if keep_original {
|
||||||
|
for instance in content.clone().into_iter() {
|
||||||
|
result_table.push(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and add mirrored instance
|
||||||
|
for mut row in content.into_iter() {
|
||||||
|
row.transform = reflected_transform * row.transform;
|
||||||
|
result_table.push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
result_table
|
||||||
|
}
|
||||||
|
|
||||||
/// Performs internal editor record-keeping that enables tools to target this network's layer.
|
/// Performs internal editor record-keeping that enables tools to target this network's layer.
|
||||||
/// This node associates the ID of the network's parent layer to every element of output data.
|
/// This node associates the ID of the network's parent layer to every element of output data.
|
||||||
/// This technical detail may be ignored by users, and will be phased out in the future.
|
/// This technical detail may be ignored by users, and will be phased out in the future.
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ raster-nodes = { workspace = true }
|
||||||
brush-nodes = { workspace = true }
|
brush-nodes = { workspace = true }
|
||||||
graphene-core = { workspace = true }
|
graphene-core = { workspace = true }
|
||||||
graphic-nodes = { workspace = true }
|
graphic-nodes = { workspace = true }
|
||||||
|
repeat-nodes = { workspace = true }
|
||||||
|
|
||||||
# Workspace dependencies
|
# Workspace dependencies
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ pub use graphic_types::{Artboard, Graphic, Vector};
|
||||||
pub use math_nodes;
|
pub use math_nodes;
|
||||||
pub use path_bool_nodes as path_bool;
|
pub use path_bool_nodes as path_bool;
|
||||||
pub use raster_nodes;
|
pub use raster_nodes;
|
||||||
|
pub use repeat_nodes;
|
||||||
pub use text_nodes;
|
pub use text_nodes;
|
||||||
pub use transform_nodes;
|
pub use transform_nodes;
|
||||||
pub use vector_nodes;
|
pub use vector_nodes;
|
||||||
|
|
@ -64,6 +65,10 @@ pub mod transform {
|
||||||
pub use vector_types::ReferencePoint;
|
pub use vector_types::ReferencePoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod repeat {
|
||||||
|
pub use repeat_nodes::repeat_nodes::*;
|
||||||
|
}
|
||||||
|
|
||||||
pub mod math {
|
pub mod math {
|
||||||
pub use core_types::math::quad;
|
pub use core_types::math::quad;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
[package]
|
||||||
|
name = "repeat-nodes"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
description = "Repeat operation nodes for Graphene"
|
||||||
|
authors = ["Graphite Authors <contact@graphite.art>"]
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["serde"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Local dependencies
|
||||||
|
core-types = { workspace = true }
|
||||||
|
vector-types = { workspace = true }
|
||||||
|
raster-types = { workspace = true }
|
||||||
|
node-macro = { workspace = true }
|
||||||
|
graphic-types = { workspace = true }
|
||||||
|
|
||||||
|
# Workspace dependencies
|
||||||
|
dyn-any = { workspace = true }
|
||||||
|
glam = { workspace = true }
|
||||||
|
log = { workspace = true }
|
||||||
|
|
||||||
|
# Optional workspace dependencies
|
||||||
|
serde = { workspace = true, optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
graphene-core = { workspace = true }
|
||||||
|
vector-nodes = { workspace = true }
|
||||||
|
tokio = { workspace = true, features = ["macros", "rt"] }
|
||||||
|
kurbo = { workspace = true }
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
pub mod repeat_nodes;
|
||||||
|
|
||||||
|
// Re-export for convenience
|
||||||
|
pub use core_types as gcore;
|
||||||
|
pub use graphic_types;
|
||||||
|
pub use raster_types;
|
||||||
|
pub use repeat_nodes::*;
|
||||||
|
pub use vector_types;
|
||||||
|
|
@ -0,0 +1,291 @@
|
||||||
|
use crate::gcore::Context;
|
||||||
|
use core::f64::consts::TAU;
|
||||||
|
use core_types::registry::types::{Angle, IntegerCount, PixelSize};
|
||||||
|
use core_types::table::{Table, TableRowRef};
|
||||||
|
use core_types::{CloneVarArgs, Color, Ctx, ExtractAll, InjectVarArgs, OwnedContextImpl};
|
||||||
|
use glam::{DAffine2, DVec2};
|
||||||
|
use graphic_types::{Graphic, Vector};
|
||||||
|
use raster_types::{CPU, Raster};
|
||||||
|
use vector_types::GradientStops;
|
||||||
|
|
||||||
|
#[node_macro::node(category("Repeat"))]
|
||||||
|
async fn repeat<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
||||||
|
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
||||||
|
#[implementations(
|
||||||
|
Context -> Table<Graphic>,
|
||||||
|
Context -> Table<Vector>,
|
||||||
|
Context -> Table<Raster<CPU>>,
|
||||||
|
Context -> Table<Color>,
|
||||||
|
Context -> Table<GradientStops>,
|
||||||
|
)]
|
||||||
|
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
|
||||||
|
#[default(1)] count: u64,
|
||||||
|
reverse: bool,
|
||||||
|
) -> Table<T> {
|
||||||
|
// Someday this node can have the option to generate infinitely instead of a fixed count (basically `std::iter::repeat`).
|
||||||
|
|
||||||
|
let count = count.max(1) as usize;
|
||||||
|
|
||||||
|
let mut result_table = Table::new();
|
||||||
|
|
||||||
|
for index in 0..count {
|
||||||
|
let index = if reverse { count - index - 1 } else { index };
|
||||||
|
|
||||||
|
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index);
|
||||||
|
let generated_instance = instance.eval(new_ctx.into_context()).await;
|
||||||
|
|
||||||
|
for generated_row in generated_instance.into_iter() {
|
||||||
|
result_table.push(generated_row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result_table
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_macro::node(category("Repeat"))]
|
||||||
|
pub async fn repeat_array<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
||||||
|
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
||||||
|
#[implementations(
|
||||||
|
Context -> Table<Graphic>,
|
||||||
|
Context -> Table<Vector>,
|
||||||
|
Context -> Table<Raster<CPU>>,
|
||||||
|
Context -> Table<Color>,
|
||||||
|
Context -> Table<GradientStops>,
|
||||||
|
)]
|
||||||
|
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
|
||||||
|
#[default(100., 100.)]
|
||||||
|
// TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed.
|
||||||
|
direction: PixelSize,
|
||||||
|
angle: Angle,
|
||||||
|
#[default(5)] count: IntegerCount,
|
||||||
|
) -> Table<T> {
|
||||||
|
let angle = angle.to_radians();
|
||||||
|
let count = count.max(1);
|
||||||
|
let total = (count - 1) as f64;
|
||||||
|
|
||||||
|
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 transform = DAffine2::from_angle(angle) * DAffine2::from_translation(translation);
|
||||||
|
|
||||||
|
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index as usize);
|
||||||
|
let generated_instance = instance.eval(new_ctx.into_context()).await;
|
||||||
|
|
||||||
|
for row in generated_instance.iter() {
|
||||||
|
let mut row = row.into_cloned();
|
||||||
|
|
||||||
|
let local_translation = DAffine2::from_translation(row.transform.translation);
|
||||||
|
let local_matrix = DAffine2::from_mat2(row.transform.matrix2);
|
||||||
|
row.transform = local_translation * transform * local_matrix;
|
||||||
|
|
||||||
|
result_table.push(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result_table
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_macro::node(category("Repeat"))]
|
||||||
|
async fn repeat_radial<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
||||||
|
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
||||||
|
#[implementations(
|
||||||
|
Context -> Table<Graphic>,
|
||||||
|
Context -> Table<Vector>,
|
||||||
|
Context -> Table<Raster<CPU>>,
|
||||||
|
Context -> Table<Color>,
|
||||||
|
Context -> Table<GradientStops>,
|
||||||
|
)]
|
||||||
|
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
|
||||||
|
start_angle: Angle,
|
||||||
|
#[unit(" px")]
|
||||||
|
#[default(5)]
|
||||||
|
radius: f64,
|
||||||
|
#[default(5)] count: IntegerCount,
|
||||||
|
) -> Table<T> {
|
||||||
|
let count = count.max(1);
|
||||||
|
|
||||||
|
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 translation = DAffine2::from_translation(radius * DVec2::Y);
|
||||||
|
let transform = angle * translation;
|
||||||
|
|
||||||
|
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index as usize);
|
||||||
|
let generated_instance = instance.eval(new_ctx.into_context()).await;
|
||||||
|
|
||||||
|
for row in generated_instance.iter() {
|
||||||
|
let mut row = row.into_cloned();
|
||||||
|
|
||||||
|
let local_translation = DAffine2::from_translation(row.transform.translation);
|
||||||
|
let local_matrix = DAffine2::from_mat2(row.transform.matrix2);
|
||||||
|
row.transform = local_translation * transform * local_matrix;
|
||||||
|
|
||||||
|
result_table.push(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result_table
|
||||||
|
}
|
||||||
|
|
||||||
|
#[node_macro::node(category("Repeat"), name("Repeat on Points"))]
|
||||||
|
async fn repeat_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
||||||
|
ctx: impl ExtractAll + CloneVarArgs + Sync + Ctx + InjectVarArgs,
|
||||||
|
points: Table<Vector>,
|
||||||
|
#[implementations(
|
||||||
|
Context -> Table<Graphic>,
|
||||||
|
Context -> Table<Vector>,
|
||||||
|
Context -> Table<Raster<CPU>>,
|
||||||
|
Context -> Table<Color>,
|
||||||
|
Context -> Table<GradientStops>,
|
||||||
|
)]
|
||||||
|
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
|
||||||
|
reverse: bool,
|
||||||
|
) -> Table<T> {
|
||||||
|
let mut result_table = Table::new();
|
||||||
|
|
||||||
|
for TableRowRef { element: points, transform, .. } in points.iter() {
|
||||||
|
let mut iteration = async |index, point| {
|
||||||
|
let transformed_point = transform.transform_point2(point);
|
||||||
|
|
||||||
|
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_position(transformed_point);
|
||||||
|
let generated_instance = instance.eval(new_ctx.into_context()).await;
|
||||||
|
|
||||||
|
for mut generated_row in generated_instance.into_iter() {
|
||||||
|
generated_row.transform.translation = transformed_point;
|
||||||
|
result_table.push(generated_row);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let range = points.point_domain.positions().iter().enumerate();
|
||||||
|
if reverse {
|
||||||
|
for (index, &point) in range.rev() {
|
||||||
|
iteration(index, point).await;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (index, &point) in range {
|
||||||
|
iteration(index, point).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result_table
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
use core_types::Ctx;
|
||||||
|
use core_types::Node;
|
||||||
|
use core_types::transform::Footprint;
|
||||||
|
use glam::DVec2;
|
||||||
|
use graphene_core::ReadPositionNode;
|
||||||
|
use graphene_core::extract_xy::{ExtractXyNode, XY};
|
||||||
|
use graphic_types::Vector;
|
||||||
|
use kurbo::Shape;
|
||||||
|
use kurbo::{BezPath, DEFAULT_ACCURACY, Rect};
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use vector_nodes::generator_nodes::RectangleNode;
|
||||||
|
use vector_types::subpath::Subpath;
|
||||||
|
|
||||||
|
fn vector_node_from_bezpath(bezpath: BezPath) -> Table<Vector> {
|
||||||
|
Table::new_from_element(Vector::from_bezpath(bezpath))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct FutureWrapperNode<T: Clone>(T);
|
||||||
|
|
||||||
|
impl<'i, I: Ctx, T: 'i + Clone + Send> Node<'i, I> for FutureWrapperNode<T> {
|
||||||
|
type Output = Pin<Box<dyn Future<Output = T> + 'i + Send>>;
|
||||||
|
fn eval(&'i self, _input: I) -> Self::Output {
|
||||||
|
let value = self.0.clone();
|
||||||
|
Box::pin(async move { value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn repeat_on_points_test() {
|
||||||
|
let context = OwnedContextImpl::default().into_context();
|
||||||
|
let rect = RectangleNode::new(
|
||||||
|
FutureWrapperNode(()),
|
||||||
|
ExtractXyNode::new(ReadPositionNode::new(FutureWrapperNode(()), FutureWrapperNode(0)), FutureWrapperNode(XY::Y)),
|
||||||
|
FutureWrapperNode(2_f64),
|
||||||
|
FutureWrapperNode(false),
|
||||||
|
FutureWrapperNode(0_f64),
|
||||||
|
FutureWrapperNode(false),
|
||||||
|
);
|
||||||
|
|
||||||
|
let positions = [DVec2::new(40., 20.), DVec2::ONE, DVec2::new(-42., 9.), DVec2::new(10., 345.)];
|
||||||
|
let points = Table::new_from_element(Vector::from_subpath(Subpath::from_anchors(positions, false)));
|
||||||
|
let generated = super::repeat_on_points(context, points, &rect, false).await;
|
||||||
|
assert_eq!(generated.len(), positions.len());
|
||||||
|
for (position, generated_row) in positions.into_iter().zip(generated.iter()) {
|
||||||
|
let bounds = generated_row.element.bounding_box_with_transform(*generated_row.transform).unwrap();
|
||||||
|
assert!(position.abs_diff_eq((bounds[0] + bounds[1]) / 2., 1e-10));
|
||||||
|
assert_eq!((bounds[1] - bounds[0]).x, position.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn repeat() {
|
||||||
|
let direction = DVec2::X * 1.5;
|
||||||
|
let count = 3;
|
||||||
|
let context = OwnedContextImpl::default().into_context();
|
||||||
|
let repeated = super::repeat_array(
|
||||||
|
context,
|
||||||
|
&FutureWrapperNode(vector_node_from_bezpath(Rect::new(0., 0., 1., 1.).to_path(DEFAULT_ACCURACY))),
|
||||||
|
direction,
|
||||||
|
0.,
|
||||||
|
count,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let vector_table = vector_nodes::flatten_path(Footprint::default(), repeated).await;
|
||||||
|
let vector = vector_table.iter().next().unwrap().element;
|
||||||
|
assert_eq!(vector.region_manipulator_groups().count(), 3);
|
||||||
|
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {
|
||||||
|
assert!((manipulator_groups[0].anchor - direction * index as f64 / (count - 1) as f64).length() < 1e-5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn repeat_transform_position() {
|
||||||
|
let direction = DVec2::new(12., 10.);
|
||||||
|
let count = 8;
|
||||||
|
let context = OwnedContextImpl::default().into_context();
|
||||||
|
let repeated = super::repeat_array(
|
||||||
|
context,
|
||||||
|
&FutureWrapperNode(vector_node_from_bezpath(Rect::new(0., 0., 1., 1.).to_path(DEFAULT_ACCURACY))),
|
||||||
|
direction,
|
||||||
|
0.,
|
||||||
|
count,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let vector_table = vector_nodes::flatten_path(Footprint::default(), repeated).await;
|
||||||
|
let vector = vector_table.iter().next().unwrap().element;
|
||||||
|
assert_eq!(vector.region_manipulator_groups().count(), 8);
|
||||||
|
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {
|
||||||
|
assert!((manipulator_groups[0].anchor - direction * index as f64 / (count - 1) as f64).length() < 1e-5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn repeat_radial() {
|
||||||
|
let context = OwnedContextImpl::default().into_context();
|
||||||
|
let repeated = super::repeat_radial(context, &FutureWrapperNode(vector_node_from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY))), 45., 4., 8).await;
|
||||||
|
let vector_table = vector_nodes::flatten_path(Footprint::default(), repeated).await;
|
||||||
|
let vector = vector_table.iter().next().unwrap().element;
|
||||||
|
assert_eq!(vector.region_manipulator_groups().count(), 8);
|
||||||
|
|
||||||
|
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {
|
||||||
|
let expected_angle = (index as f64 + 1.) * 45.;
|
||||||
|
|
||||||
|
let center = (manipulator_groups[0].anchor + manipulator_groups[2].anchor) / 2.;
|
||||||
|
let actual_angle = DVec2::Y.angle_to(center).to_degrees();
|
||||||
|
|
||||||
|
assert!((actual_angle - expected_angle).abs() % 360. < 1e-5, "Expected {expected_angle} found {actual_angle}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,7 @@ node-macro = { workspace = true }
|
||||||
|
|
||||||
# Workspace dependencies
|
# Workspace dependencies
|
||||||
glam = { workspace = true }
|
glam = { workspace = true }
|
||||||
|
rand = { workspace = true }
|
||||||
|
|
||||||
# Optional workspace dependencies
|
# Optional workspace dependencies
|
||||||
serde = { workspace = true, optional = true }
|
serde = { workspace = true, optional = true }
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ core-types = { workspace = true }
|
||||||
vector-types = { workspace = true }
|
vector-types = { workspace = true }
|
||||||
graphic-types = { workspace = true }
|
graphic-types = { workspace = true }
|
||||||
node-macro = { workspace = true }
|
node-macro = { workspace = true }
|
||||||
|
repeat-nodes = { workspace = true }
|
||||||
|
|
||||||
# Workspace dependencies
|
# Workspace dependencies
|
||||||
dyn-any = { workspace = true }
|
dyn-any = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
use core_types::Color;
|
|
||||||
use core_types::table::{Table, TableRowRef};
|
|
||||||
use core_types::{CloneVarArgs, Context, Ctx, ExtractAll, OwnedContextImpl};
|
|
||||||
use graphic_types::Graphic;
|
|
||||||
use graphic_types::Vector;
|
|
||||||
use graphic_types::raster_types::{CPU, Raster};
|
|
||||||
use vector_types::GradientStops;
|
|
||||||
|
|
||||||
#[node_macro::node(name("Instance on Points"), category("Instancing"), path(core_types::vector))]
|
|
||||||
async fn instance_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
|
||||||
ctx: impl ExtractAll + CloneVarArgs + Sync + Ctx,
|
|
||||||
points: Table<Vector>,
|
|
||||||
#[implementations(
|
|
||||||
Context -> Table<Graphic>,
|
|
||||||
Context -> Table<Vector>,
|
|
||||||
Context -> Table<Raster<CPU>>,
|
|
||||||
Context -> Table<Color>,
|
|
||||||
Context -> Table<GradientStops>,
|
|
||||||
)]
|
|
||||||
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
|
|
||||||
reverse: bool,
|
|
||||||
) -> Table<T> {
|
|
||||||
let mut result_table = Table::new();
|
|
||||||
|
|
||||||
for TableRowRef { element: points, transform, .. } in points.iter() {
|
|
||||||
let mut iteration = async |index, point| {
|
|
||||||
let transformed_point = transform.transform_point2(point);
|
|
||||||
|
|
||||||
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_position(transformed_point);
|
|
||||||
let generated_instance = instance.eval(new_ctx.into_context()).await;
|
|
||||||
|
|
||||||
for mut generated_row in generated_instance.into_iter() {
|
|
||||||
generated_row.transform.translation = transformed_point;
|
|
||||||
result_table.push(generated_row);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let range = points.point_domain.positions().iter().enumerate();
|
|
||||||
if reverse {
|
|
||||||
for (index, &point) in range.rev() {
|
|
||||||
iteration(index, point).await;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (index, &point) in range {
|
|
||||||
iteration(index, point).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result_table
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node_macro::node(category("Instancing"), path(core_types::vector))]
|
|
||||||
async fn instance_repeat<T: Into<Graphic> + Default + Send + Clone + 'static>(
|
|
||||||
ctx: impl ExtractAll + CloneVarArgs + Ctx,
|
|
||||||
#[implementations(
|
|
||||||
Context -> Table<Graphic>,
|
|
||||||
Context -> Table<Vector>,
|
|
||||||
Context -> Table<Raster<CPU>>,
|
|
||||||
Context -> Table<Color>,
|
|
||||||
Context -> Table<GradientStops>,
|
|
||||||
)]
|
|
||||||
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
|
|
||||||
#[default(1)] count: u64,
|
|
||||||
reverse: bool,
|
|
||||||
) -> Table<T> {
|
|
||||||
let count = count.max(1) as usize;
|
|
||||||
|
|
||||||
let mut result_table = Table::new();
|
|
||||||
|
|
||||||
for index in 0..count {
|
|
||||||
let index = if reverse { count - index - 1 } else { index };
|
|
||||||
|
|
||||||
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index);
|
|
||||||
let generated_instance = instance.eval(new_ctx.into_context()).await;
|
|
||||||
|
|
||||||
for generated_row in generated_instance.into_iter() {
|
|
||||||
result_table.push(generated_row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result_table
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
use crate::generator_nodes::RectangleNode;
|
|
||||||
use core_types::Ctx;
|
|
||||||
use core_types::Node;
|
|
||||||
use glam::DVec2;
|
|
||||||
use graphene_core::ReadPositionNode;
|
|
||||||
use graphene_core::extract_xy::{ExtractXyNode, XY};
|
|
||||||
use graphic_types::Vector;
|
|
||||||
use std::future::Future;
|
|
||||||
use std::pin::Pin;
|
|
||||||
use vector_types::subpath::Subpath;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct FutureWrapperNode<T: Clone>(T);
|
|
||||||
|
|
||||||
impl<'i, I: Ctx, T: 'i + Clone + Send> Node<'i, I> for FutureWrapperNode<T> {
|
|
||||||
type Output = Pin<Box<dyn Future<Output = T> + 'i + Send>>;
|
|
||||||
fn eval(&'i self, _input: I) -> Self::Output {
|
|
||||||
let value = self.0.clone();
|
|
||||||
Box::pin(async move { value })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn instance_on_points_test() {
|
|
||||||
let owned = OwnedContextImpl::default().into_context();
|
|
||||||
let rect = RectangleNode::new(
|
|
||||||
FutureWrapperNode(()),
|
|
||||||
ExtractXyNode::new(ReadPositionNode::new(FutureWrapperNode(()), FutureWrapperNode(0)), FutureWrapperNode(XY::Y)),
|
|
||||||
FutureWrapperNode(2_f64),
|
|
||||||
FutureWrapperNode(false),
|
|
||||||
FutureWrapperNode(0_f64),
|
|
||||||
FutureWrapperNode(false),
|
|
||||||
);
|
|
||||||
|
|
||||||
let positions = [DVec2::new(40., 20.), DVec2::ONE, DVec2::new(-42., 9.), DVec2::new(10., 345.)];
|
|
||||||
let points = Table::new_from_element(Vector::from_subpath(Subpath::from_anchors(positions, false)));
|
|
||||||
let generated = super::instance_on_points(owned, points, &rect, false).await;
|
|
||||||
assert_eq!(generated.len(), positions.len());
|
|
||||||
for (position, generated_row) in positions.into_iter().zip(generated.iter()) {
|
|
||||||
let bounds = generated_row.element.bounding_box_with_transform(*generated_row.transform).unwrap();
|
|
||||||
assert!(position.abs_diff_eq((bounds[0] + bounds[1]) / 2., 1e-10));
|
|
||||||
assert_eq!((bounds[1] - bounds[0]).x, position.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
pub mod generator_nodes;
|
pub mod generator_nodes;
|
||||||
pub mod instance;
|
|
||||||
pub mod merge_qr_squares;
|
pub mod merge_qr_squares;
|
||||||
pub mod vector_modification_nodes;
|
pub mod vector_modification_nodes;
|
||||||
mod vector_nodes;
|
mod vector_nodes;
|
||||||
|
|
@ -11,7 +10,6 @@ extern crate log;
|
||||||
pub use core_types as gcore;
|
pub use core_types as gcore;
|
||||||
pub use generator_nodes::*;
|
pub use generator_nodes::*;
|
||||||
pub use graphic_types;
|
pub use graphic_types;
|
||||||
pub use instance::*;
|
|
||||||
pub use vector_modification_nodes::*;
|
pub use vector_modification_nodes::*;
|
||||||
pub use vector_nodes::*;
|
pub use vector_nodes::*;
|
||||||
pub use vector_types;
|
pub use vector_types;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use core::cmp::Ordering;
|
use core::cmp::Ordering;
|
||||||
use core::f64::consts::PI;
|
use core::f64::consts::{PI, TAU};
|
||||||
use core::hash::{Hash, Hasher};
|
use core::hash::{Hash, Hasher};
|
||||||
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
use core_types::bounds::{BoundingBox, RenderBoundingBox};
|
||||||
use core_types::registry::types::{Angle, IntegerCount, Length, Multiplier, Percentage, PixelLength, PixelSize, Progression, SeedValue};
|
use core_types::registry::types::{Angle, Length, Multiplier, Percentage, PixelLength, Progression, SeedValue};
|
||||||
use core_types::table::{Table, TableRow, TableRowMut};
|
use core_types::table::{Table, TableRow, TableRowMut};
|
||||||
use core_types::transform::{Footprint, Transform};
|
use core_types::transform::{Footprint, Transform};
|
||||||
use core_types::{CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
|
use core_types::{CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
|
||||||
|
|
@ -13,22 +13,18 @@ use graphic_types::{Graphic, IntoGraphicTable};
|
||||||
use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, Line, ParamCurve, PathEl, PathSeg, Shape};
|
use kurbo::{Affine, BezPath, DEFAULT_ACCURACY, Line, ParamCurve, PathEl, PathSeg, Shape};
|
||||||
use rand::{Rng, SeedableRng};
|
use rand::{Rng, SeedableRng};
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use std::collections::hash_map::DefaultHasher;
|
||||||
use std::f64::consts::TAU;
|
|
||||||
use vector_types::subpath::{BezierHandles, ManipulatorGroup};
|
use vector_types::subpath::{BezierHandles, ManipulatorGroup};
|
||||||
use vector_types::vector::PointDomain;
|
use vector_types::vector::PointDomain;
|
||||||
use vector_types::vector::ReferencePoint;
|
use vector_types::vector::algorithms::bezpath_algorithms::{self, TValue, eval_pathseg_euclidean, evaluate_bezpath, sample_polyline_on_bezpath, split_bezpath, tangent_on_bezpath};
|
||||||
use vector_types::vector::algorithms::bezpath_algorithms::eval_pathseg_euclidean;
|
|
||||||
use vector_types::vector::algorithms::bezpath_algorithms::{self, TValue, evaluate_bezpath, sample_polyline_on_bezpath, split_bezpath, tangent_on_bezpath};
|
|
||||||
use vector_types::vector::algorithms::merge_by_distance::MergeByDistanceExt;
|
use vector_types::vector::algorithms::merge_by_distance::MergeByDistanceExt;
|
||||||
use vector_types::vector::algorithms::offset_subpath::offset_bezpath;
|
use vector_types::vector::algorithms::offset_subpath::offset_bezpath;
|
||||||
use vector_types::vector::algorithms::spline::{solve_spline_first_handle_closed, solve_spline_first_handle_open};
|
use vector_types::vector::algorithms::spline::{solve_spline_first_handle_closed, solve_spline_first_handle_open};
|
||||||
use vector_types::vector::misc::{CentroidType, ExtrudeJoiningAlgorithm, RowsOrColumns, bezpath_from_manipulator_groups, bezpath_to_manipulator_groups, point_to_dvec2};
|
use vector_types::vector::misc::{
|
||||||
use vector_types::vector::misc::{MergeByDistanceAlgorithm, PointSpacingType, is_linear};
|
CentroidType, ExtrudeJoiningAlgorithm, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, bezpath_from_manipulator_groups, bezpath_to_manipulator_groups, handles_to_segment, is_linear,
|
||||||
use vector_types::vector::misc::{handles_to_segment, segment_to_handles};
|
point_to_dvec2, segment_to_handles,
|
||||||
use vector_types::vector::style::{Fill, Gradient, GradientStops, Stroke};
|
};
|
||||||
use vector_types::vector::style::{PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
|
use vector_types::vector::style::{Fill, Gradient, GradientStops, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
||||||
use vector_types::vector::{FillId, RegionId};
|
use vector_types::vector::{FillId, PointId, RegionId, SegmentDomain, SegmentId, StrokeId, VectorExt};
|
||||||
use vector_types::vector::{PointId, SegmentDomain, SegmentId, StrokeId, VectorExt};
|
|
||||||
|
|
||||||
/// Implemented for types that can be converted to an iterator of vector rows.
|
/// Implemented for types that can be converted to an iterator of vector rows.
|
||||||
/// Used for the fill and stroke node so they can be used on `Table<Graphic>` or `Table<Vector>`.
|
/// Used for the fill and stroke node so they can be used on `Table<Graphic>` or `Table<Vector>`.
|
||||||
|
|
@ -226,75 +222,6 @@ where
|
||||||
content
|
content
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Instancing"), path(core_types::vector))]
|
|
||||||
async fn repeat<I: 'n + Send + Clone>(
|
|
||||||
_: impl Ctx,
|
|
||||||
// TODO: Implement other graphical types.
|
|
||||||
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>, Table<GradientStops>)] instance: Table<I>,
|
|
||||||
#[default(100., 100.)]
|
|
||||||
// TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed.
|
|
||||||
direction: PixelSize,
|
|
||||||
angle: Angle,
|
|
||||||
#[default(5)] count: IntegerCount,
|
|
||||||
) -> Table<I> {
|
|
||||||
let angle = angle.to_radians();
|
|
||||||
let count = count.max(1);
|
|
||||||
let total = (count - 1) as f64;
|
|
||||||
|
|
||||||
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 transform = DAffine2::from_angle(angle) * DAffine2::from_translation(translation);
|
|
||||||
|
|
||||||
for row in instance.iter() {
|
|
||||||
let mut row = row.into_cloned();
|
|
||||||
|
|
||||||
let local_translation = DAffine2::from_translation(row.transform.translation);
|
|
||||||
let local_matrix = DAffine2::from_mat2(row.transform.matrix2);
|
|
||||||
row.transform = local_translation * transform * local_matrix;
|
|
||||||
|
|
||||||
result_table.push(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result_table
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node_macro::node(category("Instancing"), path(core_types::vector))]
|
|
||||||
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,
|
|
||||||
#[unit(" px")]
|
|
||||||
#[default(5)]
|
|
||||||
radius: f64,
|
|
||||||
#[default(5)] count: IntegerCount,
|
|
||||||
) -> Table<I> {
|
|
||||||
let count = count.max(1);
|
|
||||||
|
|
||||||
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 translation = DAffine2::from_translation(radius * DVec2::Y);
|
|
||||||
let transform = angle * translation;
|
|
||||||
|
|
||||||
for row in instance.iter() {
|
|
||||||
let mut row = row.into_cloned();
|
|
||||||
|
|
||||||
let local_translation = DAffine2::from_translation(row.transform.translation);
|
|
||||||
let local_matrix = DAffine2::from_mat2(row.transform.matrix2);
|
|
||||||
row.transform = local_translation * transform * local_matrix;
|
|
||||||
|
|
||||||
result_table.push(row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result_table
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node_macro::node(name("Copy to Points"), category("Instancing"), path(core_types::vector))]
|
#[node_macro::node(name("Copy to Points"), category("Instancing"), path(core_types::vector))]
|
||||||
async fn copy_to_points<I: 'n + Send + Clone>(
|
async fn copy_to_points<I: 'n + Send + Clone>(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
|
|
@ -373,63 +300,6 @@ async fn copy_to_points<I: 'n + Send + Clone>(
|
||||||
result_table
|
result_table
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Instancing"), path(core_types::vector))]
|
|
||||||
async fn mirror<I: 'n + Send + Clone>(
|
|
||||||
_: impl Ctx,
|
|
||||||
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Color>, Table<GradientStops>)] content: Table<I>,
|
|
||||||
#[default(ReferencePoint::Center)] relative_to_bounds: ReferencePoint,
|
|
||||||
#[unit(" px")] offset: f64,
|
|
||||||
#[range((-90., 90.))] angle: Angle,
|
|
||||||
#[default(true)] keep_original: bool,
|
|
||||||
) -> Table<I>
|
|
||||||
where
|
|
||||||
Table<I>: BoundingBox,
|
|
||||||
{
|
|
||||||
// Normalize the direction vector
|
|
||||||
let normal = DVec2::from_angle(angle.to_radians());
|
|
||||||
|
|
||||||
// The mirror reference may be based on the bounding box if an explicit reference point is chosen
|
|
||||||
let RenderBoundingBox::Rectangle(bounding_box) = content.bounding_box(DAffine2::IDENTITY, false) else {
|
|
||||||
return content;
|
|
||||||
};
|
|
||||||
|
|
||||||
let reference_point_location = relative_to_bounds.point_in_bounding_box((bounding_box[0], bounding_box[1]).into());
|
|
||||||
let mirror_reference_point = reference_point_location.map(|point| point + normal * offset);
|
|
||||||
|
|
||||||
// Create the reflection matrix
|
|
||||||
let reflection = DAffine2::from_mat2_translation(
|
|
||||||
glam::DMat2::from_cols(
|
|
||||||
DVec2::new(1. - 2. * normal.x * normal.x, -2. * normal.y * normal.x),
|
|
||||||
DVec2::new(-2. * normal.x * normal.y, 1. - 2. * normal.y * normal.y),
|
|
||||||
),
|
|
||||||
DVec2::ZERO,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Apply reflection around the reference point
|
|
||||||
let reflected_transform = if let Some(mirror_reference_point) = mirror_reference_point {
|
|
||||||
DAffine2::from_translation(mirror_reference_point) * reflection * DAffine2::from_translation(-mirror_reference_point)
|
|
||||||
} else {
|
|
||||||
reflection * DAffine2::from_translation(DVec2::from_angle(angle.to_radians()) * DVec2::splat(-offset))
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut result_table = Table::new();
|
|
||||||
|
|
||||||
// Add original instance depending on the keep_original flag
|
|
||||||
if keep_original {
|
|
||||||
for instance in content.clone().into_iter() {
|
|
||||||
result_table.push(instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create and add mirrored instance
|
|
||||||
for mut row in content.into_iter() {
|
|
||||||
row.transform = reflected_transform * row.transform;
|
|
||||||
result_table.push(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
result_table
|
|
||||||
}
|
|
||||||
|
|
||||||
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
#[node_macro::node(category("Vector: Modifier"), path(core_types::vector))]
|
||||||
async fn round_corners(
|
async fn round_corners(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
|
|
@ -1343,7 +1213,7 @@ async fn map_points(ctx: impl Ctx + CloneVarArgs + ExtractAll, content: Table<Ve
|
||||||
}
|
}
|
||||||
|
|
||||||
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
#[node_macro::node(category("Vector"), path(graphene_core::vector))]
|
||||||
async fn flatten_path<T: 'n + Send>(
|
pub async fn flatten_path<T: 'n + Send>(
|
||||||
_: impl Ctx,
|
_: impl Ctx,
|
||||||
#[implementations(
|
#[implementations(
|
||||||
Table<Graphic>,
|
Table<Graphic>,
|
||||||
|
|
@ -2536,60 +2406,6 @@ mod test {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn repeat() {
|
|
||||||
let direction = DVec2::X * 1.5;
|
|
||||||
let count = 3;
|
|
||||||
let repeated = super::repeat(
|
|
||||||
Footprint::default(),
|
|
||||||
vector_node_from_bezpath(Rect::new(0., 0., 1., 1.).to_path(DEFAULT_ACCURACY)),
|
|
||||||
direction,
|
|
||||||
0.,
|
|
||||||
count,
|
|
||||||
)
|
|
||||||
.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(), 3);
|
|
||||||
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {
|
|
||||||
assert!((manipulator_groups[0].anchor - direction * index as f64 / (count - 1) as f64).length() < 1e-5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[tokio::test]
|
|
||||||
async fn repeat_transform_position() {
|
|
||||||
let direction = DVec2::new(12., 10.);
|
|
||||||
let count = 8;
|
|
||||||
let repeated = super::repeat(
|
|
||||||
Footprint::default(),
|
|
||||||
vector_node_from_bezpath(Rect::new(0., 0., 1., 1.).to_path(DEFAULT_ACCURACY)),
|
|
||||||
direction,
|
|
||||||
0.,
|
|
||||||
count,
|
|
||||||
)
|
|
||||||
.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);
|
|
||||||
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {
|
|
||||||
assert!((manipulator_groups[0].anchor - direction * index as f64 / (count - 1) as f64).length() < 1e-5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[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 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);
|
|
||||||
|
|
||||||
for (index, (_, manipulator_groups)) in vector.region_manipulator_groups().enumerate() {
|
|
||||||
let expected_angle = (index as f64 + 1.) * 45.;
|
|
||||||
|
|
||||||
let center = (manipulator_groups[0].anchor + manipulator_groups[2].anchor) / 2.;
|
|
||||||
let actual_angle = DVec2::Y.angle_to(center).to_degrees();
|
|
||||||
|
|
||||||
assert!((actual_angle - expected_angle).abs() % 360. < 1e-5, "Expected {expected_angle} found {actual_angle}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn bounding_box() {
|
async fn bounding_box() {
|
||||||
let bounding_box = super::bounding_box((), vector_node_from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY))).await;
|
let bounding_box = super::bounding_box((), vector_node_from_bezpath(Rect::new(-1., -1., 1., 1.).to_path(DEFAULT_ACCURACY))).await;
|
||||||
|
|
@ -2716,8 +2532,11 @@ mod test {
|
||||||
}
|
}
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn morph() {
|
async fn morph() {
|
||||||
let rectangle = vector_node_from_bezpath(Rect::new(0., 0., 100., 100.).to_path(DEFAULT_ACCURACY));
|
let mut rectangles = 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 mut second_rectangle = rectangles.get(0).unwrap().into_cloned();
|
||||||
|
second_rectangle.transform *= DAffine2::from_translation((-100., -100.).into());
|
||||||
|
rectangles.push(second_rectangle);
|
||||||
|
|
||||||
let morphed = super::morph(Footprint::default(), rectangles, 0.5).await;
|
let morphed = super::morph(Footprint::default(), rectangles, 0.5).await;
|
||||||
let element = morphed.iter().next().unwrap().element;
|
let element = morphed.iter().next().unwrap().element;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue