Skip to content
Merged
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
22 changes: 22 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ members = [
"node-graph/nodes/raster/shaders/entrypoint",
"node-graph/nodes/text",
"node-graph/nodes/transform",
"node-graph/nodes/repeat",
"node-graph/nodes/vector",
"node-graph/graph-craft",
"node-graph/graphene-cli",
Expand Down Expand Up @@ -116,6 +117,7 @@ graphic-nodes = { path = "node-graph/nodes/graphic" }
text-nodes = { path = "node-graph/nodes/text" }
transform-nodes = { path = "node-graph/nodes/transform" }
vector-nodes = { path = "node-graph/nodes/vector" }
repeat-nodes = { path = "node-graph/nodes/repeat" }
math-nodes = { path = "node-graph/nodes/math" }
path-bool-nodes = { path = "node-graph/nodes/path-bool" }
graph-craft = { path = "node-graph/graph-craft" }
Expand Down
2 changes: 1 addition & 1 deletion demo-artwork/changing-seasons.graphite

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demo-artwork/isometric-fountain.graphite

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demo-artwork/marbled-mandelbrot.graphite

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demo-artwork/painted-dreams.graphite

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demo-artwork/parametric-dunescape.graphite

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demo-artwork/procedural-string-lights.graphite

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demo-artwork/red-dress.graphite

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demo-artwork/valley-of-spires.graphite

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -502,10 +502,10 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
// [11:Equals]0 -> 0[17:Switch]
// [9:Transform]0 -> 1[17:Switch]
// [16:Morph]0 -> 2[17:Switch]
// [17:Switch]0 -> 0[18:Instance Repeat]
// [0:Floor]0 -> 1[18:Instance Repeat]
// [IMPORTS]3 -> 2[18:Instance Repeat]
// [18:Instance Repeat]0 -> 0[EXPORTS]
// [17:Switch]0 -> 0[18:Repeat]
// [0:Floor]0 -> 1[18:Repeat]
// [IMPORTS]3 -> 2[18:Repeat]
// [18:Repeat]0 -> 0[EXPORTS]
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
Expand Down Expand Up @@ -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)],
..Default::default()
},
// 18: Instance Repeat
// 18: Repeat
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)],
..Default::default()
},
Expand Down Expand Up @@ -807,7 +807,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
},
..Default::default()
},
// 18: Instance Repeat
// 18: Repeat
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(49, -1)),
Expand Down
46 changes: 21 additions & 25 deletions editor/src/messages/portfolio/document_migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -753,18 +753,10 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
node: graphene_std::vector::centroid::IDENTIFIER,
aliases: &["graphene_core::vector::CentroidNode"],
},
NodeReplacement {
node: graphene_std::vector::circular_repeat::IDENTIFIER,
aliases: &["graphene_core::vector::CircularRepeatNode"],
},
NodeReplacement {
node: graphene_std::vector::close_path::IDENTIFIER,
aliases: &["graphene_core::vector::ClosePathNode"],
},
NodeReplacement {
node: graphene_std::vector::copy_to_points::IDENTIFIER,
aliases: &["graphene_core::vector::CopyToPointsNode"],
},
NodeReplacement {
node: graphene_std::vector::count_elements::IDENTIFIER,
aliases: &["graphene_core::vector::CountElementsNode"],
Expand Down Expand Up @@ -841,22 +833,30 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
node: graphene_std::graphic::map::IDENTIFIER,
aliases: &["graphene_core::vector::InstanceMapNode"],
},
NodeReplacement {
node: graphene_std::vector::instance_on_points::IDENTIFIER,
aliases: &["graphene_core::vector::InstanceOnPointsNode"],
},
NodeReplacement {
node: graphene_std::context::read_position::IDENTIFIER,
aliases: &["graphene_core::vector::InstancePositionNode", "core_types::vector::InstancePositionNode"],
},
NodeReplacement {
node: graphene_std::vector::instance_repeat::IDENTIFIER,
aliases: &["graphene_core::vector::InstanceRepeatNode"],
},
NodeReplacement {
node: graphene_std::context::read_vector::IDENTIFIER,
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 {
node: graphene_std::vector::jitter_points::IDENTIFIER,
aliases: &["graphene_core::vector::JitterPointsNode"],
Expand All @@ -866,8 +866,8 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
aliases: &["graphene_core::vector::MergeByDistanceNode"],
},
NodeReplacement {
node: graphene_std::vector::mirror::IDENTIFIER,
aliases: &["graphene_core::vector::MirrorNode"],
node: graphene_std::graphic::mirror::IDENTIFIER,
aliases: &["graphene_core::vector::MirrorNode", "core_types::vector::MirrorNode"],
},
NodeReplacement {
node: graphene_std::vector::morph::IDENTIFIER,
Expand Down Expand Up @@ -905,10 +905,6 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
node: graphene_std::vector::position_on_path::IDENTIFIER,
aliases: &["graphene_core::vector::PositionOnPathNode"],
},
NodeReplacement {
node: graphene_std::vector::repeat::IDENTIFIER,
aliases: &["graphene_core::vector::RepeatNode"],
},
NodeReplacement {
node: graphene_std::vector::round_corners::IDENTIFIER,
aliases: &["graphene_core::vector::RoundCornersNode"],
Expand Down Expand Up @@ -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
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();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);

Expand All @@ -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`
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();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);

Expand Down Expand Up @@ -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();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);

Expand Down
71 changes: 68 additions & 3 deletions node-graph/nodes/graphic/src/graphic.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
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::uuid::NodeId;
use core_types::{AnyHash, CloneVarArgs, Color, Context, Ctx, ExtractAll, OwnedContextImpl};
use glam::{DAffine2, DVec2};
use graphic_types::graphic::{Graphic, IntoGraphicTable};
use graphic_types::{Artboard, Vector};
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>(
ctx: impl Ctx + CloneVarArgs + ExtractAll,
#[implementations(
Expand Down Expand Up @@ -41,6 +42,70 @@ async fn map<Item: AnyHash + Send + Sync + std::hash::Hash>(
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);
}
Comment on lines +95 to +97

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This loop can be made more idiomatic and slightly more efficient by using extend.

result_table.extend(content.clone());

}

// 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.
/// 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.
Expand Down
1 change: 1 addition & 0 deletions node-graph/nodes/gstd/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ raster-nodes = { workspace = true }
brush-nodes = { workspace = true }
graphene-core = { workspace = true }
graphic-nodes = { workspace = true }
repeat-nodes = { workspace = true }

# Workspace dependencies
log = { workspace = true }
Expand Down
5 changes: 5 additions & 0 deletions node-graph/nodes/gstd/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ pub use graphic_types::{Artboard, Graphic, Vector};
pub use math_nodes;
pub use path_bool_nodes as path_bool;
pub use raster_nodes;
pub use repeat_nodes;
pub use text_nodes;
pub use transform_nodes;
pub use vector_nodes;
Expand Down Expand Up @@ -64,6 +65,10 @@ pub mod transform {
pub use vector_types::ReferencePoint;
}

pub mod repeat {
pub use repeat_nodes::repeat_nodes::*;
}

pub mod math {
pub use core_types::math::quad;

Expand Down
32 changes: 32 additions & 0 deletions node-graph/nodes/repeat/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 }
8 changes: 8 additions & 0 deletions node-graph/nodes/repeat/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
Loading
Loading