Clean up document message wrappers around proto nodes so they're now used directly (#4101)

* Rename the 'Identity' node to 'Passthrough' internally

* Rename the 'Memoize'  node to 'Cache' internally

* Let skip_impl proto nodes auto-generate as document node definitions

* Remove the wrapper 'Passthrough' node from document_node_definitions.rs

* Remove the wrapper 'Cache' node from document_node_definitions.rs

* Remove the wrapper 'Monitor' node from document_node_definitions.rs

* Remove the wrapper 'Noise Pattern' node from document_node_definitions.rs

* Remove the wrapper 'Brush' node from document_node_definitions.rs

* Remove the wrapper 'Transform' node from document_node_definitions.rs

* Code review improvements

* Rename Cache node back to Memoize

* More code review
This commit is contained in:
Keavon Chambers
2026-05-03 19:26:36 -07:00
committed by GitHub
parent b27b4c6be7
commit 21e5e06b0b
29 changed files with 408 additions and 662 deletions

View File

@@ -49,7 +49,7 @@ pub struct DocumentNode {
pub call_argument: Type,
// A nested document network or a proto-node identifier.
pub implementation: DocumentNodeImplementation,
/// Represents the eye icon for hiding/showing the node in the graph UI. When hidden, a node gets replaced with an identity node during the graph flattening step.
/// Represents the eye icon for hiding/showing the node in the graph UI. When hidden, a node gets replaced with a passthrough node during the graph flattening step.
#[serde(default = "return_true")]
pub visible: bool,
/// When two different proto nodes hash to the same value (e.g. two value nodes each containing `2_u32` or two multiply nodes that have the same node IDs as input), the duplicates are removed.
@@ -328,7 +328,7 @@ pub enum DocumentNodeImplementation {
impl Default for DocumentNodeImplementation {
fn default() -> Self {
Self::ProtoNode(graphene_core::ops::identity::IDENTIFIER)
Self::ProtoNode(graphene_core::ops::passthrough::IDENTIFIER)
}
}
@@ -433,7 +433,7 @@ pub struct OldDocumentNode {
/// User chosen state for displaying this as a left-to-right node or bottom-to-top layer. Ensure the click target in the encapsulating network is updated when the node changes to a layer by using network.update_click_target(node_id).
#[serde(default)]
pub is_layer: bool,
/// Represents the eye icon for hiding/showing the node in the graph UI. When hidden, a node gets replaced with an identity node during the graph flattening step.
/// Represents the eye icon for hiding/showing the node in the graph UI. When hidden, a node gets replaced with a passthrough node during the graph flattening step.
#[serde(default = "return_true")]
pub visible: bool,
/// Represents the lock icon for locking/unlocking the node in the graph UI. When locked, a node cannot be moved in the graph UI.
@@ -798,10 +798,10 @@ impl NodeNetwork {
return;
};
// If the node is hidden, replace it with an identity node
let identity_node = DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER);
if !node.visible && node.implementation != identity_node {
node.implementation = identity_node;
// If the node is hidden, replace it with a passthrough node
let passthrough_node = DocumentNodeImplementation::ProtoNode(graphene_core::ops::passthrough::IDENTIFIER);
if !node.visible && node.implementation != passthrough_node {
node.implementation = passthrough_node;
// Connect layer node to the group below
node.inputs.drain(1..);
@@ -967,12 +967,12 @@ impl NodeNetwork {
}
}
fn remove_id_node(&mut self, id: NodeId) -> Result<(), String> {
fn remove_passthrough_node(&mut self, id: NodeId) -> Result<(), String> {
let node = self.nodes.get(&id).ok_or_else(|| format!("Node with id {id} does not exist"))?.clone();
if let DocumentNodeImplementation::ProtoNode(ident) = &node.implementation
&& *ident == graphene_core::ops::identity::IDENTIFIER
&& *ident == graphene_core::ops::passthrough::IDENTIFIER
{
assert_eq!(node.inputs.len(), 1, "Id node has more than one input");
assert_eq!(node.inputs.len(), 1, "Passthrough node has more than one input");
if let NodeInput::Node { node_id, output_index, .. } = node.inputs[0] {
let node_input_output_index = output_index;
// TODO fix
@@ -1015,20 +1015,20 @@ impl NodeNetwork {
Ok(())
}
/// Strips out any [`graphene_core::ops::IdentityNode`]s that are unnecessary.
pub fn remove_redundant_id_nodes(&mut self) {
let id_nodes = self
/// Strips out any [`graphene_core::ops::PassthroughNode`]s that are unnecessary.
pub fn remove_redundant_passthrough_nodes(&mut self) {
let passthrough_nodes = self
.nodes
.iter()
.filter(|(_, node)| {
matches!(&node.implementation, DocumentNodeImplementation::ProtoNode(ident) if ident == &graphene_core::ops::identity::IDENTIFIER)
matches!(&node.implementation, DocumentNodeImplementation::ProtoNode(ident) if ident == &graphene_core::ops::passthrough::IDENTIFIER)
&& node.inputs.len() == 1
&& matches!(node.inputs[0], NodeInput::Node { .. })
})
.map(|(id, _)| *id)
.collect::<Vec<_>>();
for id in id_nodes {
if let Err(e) = self.remove_id_node(id) {
for id in passthrough_nodes {
if let Err(e) = self.remove_passthrough_node(id) {
log::warn!("{e}")
}
}
@@ -1234,16 +1234,16 @@ mod test {
#[test]
fn extract_node() {
let id_node = DocumentNode {
let passthrough_node = DocumentNode {
inputs: vec![],
implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),
implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::passthrough::IDENTIFIER),
..Default::default()
};
// TODO: Extend test cases to test nested network
let mut extraction_network = NodeNetwork {
exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: [
id_node.clone(),
passthrough_node.clone(),
DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::Extract,
@@ -1260,7 +1260,7 @@ mod test {
assert_eq!(extraction_network.nodes.len(), 1);
let inputs = extraction_network.nodes.get(&NodeId(1)).unwrap().inputs.clone();
assert_eq!(inputs.len(), 1);
assert!(matches!(&inputs[0].as_value(), &Some(TaggedValue::DocumentNode(network), ..) if network == &id_node));
assert!(matches!(&inputs[0].as_value(), &Some(TaggedValue::DocumentNode(network), ..) if network == &passthrough_node));
}
#[test]
@@ -1475,7 +1475,7 @@ mod test {
}
}
fn two_node_identity() -> NodeNetwork {
fn two_node_passthrough() -> NodeNetwork {
NodeNetwork {
exports: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(2), 0)],
nodes: [
@@ -1483,7 +1483,7 @@ mod test {
NodeId(1),
DocumentNode {
inputs: vec![NodeInput::import(concrete!(u32), 0)],
implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),
implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::passthrough::IDENTIFIER),
..Default::default()
},
),
@@ -1491,7 +1491,7 @@ mod test {
NodeId(2),
DocumentNode {
inputs: vec![NodeInput::import(concrete!(u32), 1)],
implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),
implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::passthrough::IDENTIFIER),
..Default::default()
},
),
@@ -1510,7 +1510,7 @@ mod test {
NodeId(1),
DocumentNode {
inputs: vec![NodeInput::value(TaggedValue::F64(1.), false), NodeInput::value(TaggedValue::F64(2.), false)],
implementation: DocumentNodeImplementation::Network(two_node_identity()),
implementation: DocumentNodeImplementation::Network(two_node_passthrough()),
..Default::default()
},
),
@@ -1518,7 +1518,7 @@ mod test {
NodeId(2),
DocumentNode {
inputs: vec![result_node_input],
implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),
implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::passthrough::IDENTIFIER),
..Default::default()
},
),
@@ -1543,7 +1543,7 @@ mod test {
assert_eq!(result.exports[0], NodeInput::node(NodeId(11), 0), "The outer network output should be from a duplicated inner network");
let mut ids = result.nodes.keys().copied().collect::<Vec<_>>();
ids.sort();
assert_eq!(ids, vec![NodeId(11), NodeId(10010)], "Should only contain identity and values");
assert_eq!(ids, vec![NodeId(11), NodeId(10010)], "Should only contain passthrough and values");
}
// TODO: Write more tests

View File

@@ -12,7 +12,7 @@ impl Compiler {
network.flatten(id);
}
network.resolve_scope_inputs();
network.remove_redundant_id_nodes();
network.remove_redundant_passthrough_nodes();
// network.remove_dead_nodes(0);
let proto_networks = network.into_proto_networks();

View File

@@ -139,7 +139,7 @@ pub struct ProtoNode {
impl Default for ProtoNode {
fn default() -> Self {
Self {
identifier: graphene_core::ops::identity::IDENTIFIER,
identifier: graphene_core::ops::passthrough::IDENTIFIER,
construction_args: ConstructionArgs::Value(value::TaggedValue::U32(0).into()),
call_argument: concrete!(()),
original_location: OriginalLocation::default(),
@@ -317,14 +317,14 @@ impl ProtoNetwork {
p.push(NodeId(10))
}
let memo_node_id = NodeId(self.nodes.len() as u64);
let memoize_node_id = NodeId(self.nodes.len() as u64);
self.nodes.push((
memo_node_id,
memoize_node_id,
ProtoNode {
construction_args: ConstructionArgs::Nodes(vec![node_id]),
call_argument: concrete!(Context),
identifier: graphene_core::memo::memo::IDENTIFIER,
identifier: graphene_core::memo::memoize::IDENTIFIER,
original_location: OriginalLocation {
path: path.clone(),
..Default::default()
@@ -352,7 +352,7 @@ impl ProtoNetwork {
self.nodes.push((
nullification_node_id,
ProtoNode {
construction_args: ConstructionArgs::Nodes(vec![memo_node_id, nullification_value_node_id]),
construction_args: ConstructionArgs::Nodes(vec![memoize_node_id, nullification_value_node_id]),
call_argument: concrete!(Context),
identifier: graphene_core::context_modification::context_modification::IDENTIFIER,
original_location: OriginalLocation {

View File

@@ -216,7 +216,7 @@ impl BorrowTree {
pub async fn update(&mut self, proto_network: ProtoNetwork, typing_context: &TypingContext) -> Result<(Vec<Path>, HashSet<NodeId>), GraphErrors> {
let mut old_nodes: HashSet<_> = self.nodes.keys().copied().collect();
let mut new_nodes: Vec<_> = Vec::new();
// TODO: Problem: When an identity node is connected directly to an export the first input to identity node is not added to the proto network, while the second input is. This means the primary input does not have a type.
// TODO: Problem: When a passthrough node is connected directly to an export the first input to the passthrough node is not added to the proto network, while the second input is. This means the primary input does not have a type.
for (id, node) in proto_network.nodes {
if !self.nodes.contains_key(&id) {
new_nodes.push(node.original_location.path.clone().unwrap_or_default().into());

View File

@@ -6,7 +6,7 @@ pub mod util;
mod tests {
use core_types::*;
use futures::executor::block_on;
use graphene_core::ops::identity;
use graphene_core::ops::passthrough;
#[test]
fn double_number() {
@@ -16,17 +16,17 @@ mod tests {
let network = NodeNetwork {
exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: [
// Simple identity node taking a number as input from outside the graph
// Simple passthrough node taking a number as input from outside the graph
(
NodeId(0),
DocumentNode {
inputs: vec![],
call_argument: concrete!(u32),
implementation: DocumentNodeImplementation::ProtoNode(identity::IDENTIFIER),
implementation: DocumentNodeImplementation::ProtoNode(passthrough::IDENTIFIER),
..Default::default()
},
),
// An add node adding the result of the id node to its self
// An add node adding the result of the passthrough node to its self
(
NodeId(1),
DocumentNode {

View File

@@ -140,86 +140,86 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => RenderOutput, Context => graphene_std::ContextFeatures]),
#[cfg(target_family = "wasm")]
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => CanvasHandle, Context => graphene_std::ContextFeatures]),
// ==========
// MEMO NODES
// ==========
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => ()]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => bool]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Artboard>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Graphic>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Vector>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Raster<CPU>>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<GradientStops>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<String>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<NodeId>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<f64>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<u8>]),
// =============
// MEMOIZE NODES
// =============
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => ()]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => bool]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<Artboard>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<Graphic>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<Vector>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<Raster<CPU>>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<Color>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Image<Color>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<GradientStops>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<String>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<NodeId>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<f64>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<u8>]),
#[cfg(target_family = "wasm")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => CanvasHandle]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f64]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => f32]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => u32]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => u64]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => DVec2]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => String]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => DAffine2]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Footprint]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => &PlatformEditorApi]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => CanvasHandle]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => f64]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => f32]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => u32]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => u64]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => DVec2]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => String]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => DAffine2]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Footprint]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => RenderOutput]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => &PlatformEditorApi]),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Raster<GPU>>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<f64>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Option<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Graphic]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => glam::f32::Vec2]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => glam::f32::Affine2]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Stroke]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Gradient]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::Font]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<BrushStroke>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => BrushCache]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => DocumentNode]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::ContextFeatures]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::curve::Curve]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::Footprint]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Box<graphene_std::vector::VectorModification>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Fill]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::blending::BlendMode]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::LuminanceCalculation]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::QRCodeErrorCorrectionLevel]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::extract_xy::XY]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::text_nodes::StringCapitalization]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RedGreenBlue]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RedGreenBlueAlpha]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::animation::RealTimeMode]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::NoiseType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::FractalType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::CellularDistanceFunction]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::CellularReturnType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::DomainWarpType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RelativeAbsolute]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::SelectiveColorChoice]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::GridType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ArcType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::RowsOrColumns]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::MergeByDistanceAlgorithm]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ExtrudeJoiningAlgorithm]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeCap]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeJoin]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeAlign]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::PaintOrder]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::FillType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::GradientType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::ReferencePoint]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::BooleanOperation]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::TextAlign]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::ScaleType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::InterpolationDistribution]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderIntermediate]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<Raster<GPU>>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Option<f64>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Option<Color>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Graphic]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => glam::f32::Vec2]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => glam::f32::Affine2]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Stroke]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Gradient]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::Font]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Table<BrushStroke>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => BrushCache]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => DocumentNode]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::ContextFeatures]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::curve::Curve]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::Footprint]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Box<graphene_std::vector::VectorModification>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::Fill]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::blending::BlendMode]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::LuminanceCalculation]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::QRCodeErrorCorrectionLevel]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::extract_xy::XY]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::text_nodes::StringCapitalization]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RedGreenBlue]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RedGreenBlueAlpha]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::animation::RealTimeMode]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::NoiseType]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::FractalType]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::CellularDistanceFunction]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::CellularReturnType]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::DomainWarpType]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::RelativeAbsolute]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::raster::SelectiveColorChoice]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::GridType]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ArcType]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::RowsOrColumns]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::MergeByDistanceAlgorithm]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::ExtrudeJoiningAlgorithm]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeCap]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeJoin]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::StrokeAlign]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::PaintOrder]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::FillType]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::style::GradientType]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::ReferencePoint]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::BooleanOperation]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::text::TextAlign]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::transform::ScaleType]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::InterpolationDistribution]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => RenderIntermediate]),
];
// =============
// CONVERT NODES

View File

@@ -106,7 +106,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<PlatformE
inner_network,
render_node,
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::ops::identity::IDENTIFIER),
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::ops::passthrough::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::EditorApi(editor_api), false)],
..Default::default()
},

View File

@@ -74,11 +74,11 @@ pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn
.collect();
// Combined struct type parameters: data field generic idents (T, U, ...) + node generics (Node0, Node1, ...)
// For struct type instantiation: MemoNode<T, Node0>
// For struct type instantiation: MemoizeNode<T, Node0>
let struct_type_params: Vec<Ident> = data_field_generic_idents.iter().cloned().chain(node_generics.iter().cloned()).collect();
// Combined struct generic parameters with bounds for struct definition
// struct MemoNode<T: Clone, Node0>
// struct MemoizeNode<T: Clone, Node0>
let struct_generic_params: Vec<TokenStream2> = data_field_generics.iter().map(|gp| quote!(#gp)).chain(node_generics.iter().map(|id| quote!(#id))).collect();
let input_ident = &input.pat_ident;
@@ -622,8 +622,22 @@ fn generate_phantom_data<'a>(fn_generics: impl Iterator<Item = &'a crate::Generi
}
fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], struct_name: &Ident, identifier: &Ident) -> Result<TokenStream2, Error> {
// On native, `register_node` and `register_metadata` run automatically via `#[ctor]`.
// On Wasm, `ctor` isn't available, so this `extern "C"` fn is invoked from JS to register the same way.
// `skip_impl` nodes don't generate a `register_node`, so the shim calls only `register_metadata` for them.
let registry_name = format_ident!("__node_registry_{}_{}", NODE_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst), struct_name);
let register_node_call = if parsed.attributes.skip_impl { quote!() } else { quote!(register_node();) };
let wasm_shim = quote! {
#[cfg(target_family = "wasm")]
#[unsafe(no_mangle)]
extern "C" fn #registry_name() {
#register_node_call
register_metadata();
}
};
if parsed.attributes.skip_impl {
return Ok(quote!());
return Ok(wasm_shim);
}
let mut constructors = Vec::new();
@@ -708,8 +722,6 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
)
));
}
let registry_name = format_ident!("__node_registry_{}_{}", NODE_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst), struct_name);
let native = quote! {
#[cfg_attr(not(target_family = "wasm"), ctor)]
fn register_node() {
@@ -728,13 +740,7 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
Ok(quote! {
#native
#[cfg(target_family = "wasm")]
#[unsafe(no_mangle)]
extern "C" fn #registry_name() {
register_node();
register_metadata();
}
#wasm_shim
})
}

View File

@@ -52,7 +52,7 @@ pub(crate) struct NodeFnAttributes {
pub(crate) shader_node: Option<ShaderNodeType>,
/// Custom serialization function path (e.g., "my_module::custom_serialize")
pub(crate) serialize: Option<Path>,
/// Whether the preprocessor should add a Memo node after this node in the generated subnetwork
/// Whether the preprocessor should add a Memoize node after this node in the generated subnetwork
pub(crate) memoize: bool,
}
@@ -379,7 +379,7 @@ impl Parse for NodeFnAttributes {
.map_err(|_| Error::new_spanned(meta, "Expected a valid path for 'serialize', e.g., serialize(my_module::custom_serialize)"))?;
serialize = Some(parsed_path);
}
// Instructs the preprocessor to insert a Memo node after this node in the generated subnetwork,
// Instructs the preprocessor to insert a Memoize node after this node in the generated subnetwork,
// caching its output across evaluations with identical inputs.
//
// Example usage:

View File

@@ -187,30 +187,34 @@ pub fn blend_with_mode(background: TableRow<Raster<CPU>>, foreground: TableRow<R
/// Generates the brush strokes painted with the Brush tool as a raster image.
/// If an input image is supplied, strokes are drawn on top of it, expanding bounds as needed.
#[node_macro::node(category(""))]
#[node_macro::node(category("Raster"))]
async fn brush(
_: impl Ctx,
/// Optional raster content that may be drawn onto.
mut image: Table<Raster<CPU>>,
mut background: Table<Raster<CPU>>,
/// The list of brush stroke paths drawn by the Brush tool, with each including both its coordinates and styles.
strokes: Table<BrushStroke>,
trace: Table<BrushStroke>,
/// Internal cache data used to accelerate rendering of the brush content.
cache: BrushCache,
) -> Table<Raster<CPU>> {
if image.is_empty() {
image.push(TableRow::default());
if background.is_empty() {
background.push(TableRow::default());
}
// TODO: Find a way to handle more than one item
let table_row = image.clone_row(0).expect("Expected the one item we just pushed");
let table_row = background.clone_row(0).expect("Expected the one item we just pushed");
let bounds = Table::new_from_row(table_row.clone()).bounding_box(DAffine2::IDENTITY, false);
let [start, end] = if let RenderBoundingBox::Rectangle(rect) = bounds { rect } else { [DVec2::ZERO, DVec2::ZERO] };
let image_bbox = AxisAlignedBbox { start, end };
let stroke_bbox = strokes.iter_element_values().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);
let bbox = if image_bbox.size().length() < 0.1 { stroke_bbox } else { stroke_bbox.union(&image_bbox) };
let background_bbox = AxisAlignedBbox { start, end };
let stroke_bbox = trace.iter_element_values().map(|s| s.bounding_box()).reduce(|a, b| a.union(&b)).unwrap_or(AxisAlignedBbox::ZERO);
let bbox = if background_bbox.size().length() < 0.1 {
stroke_bbox
} else {
stroke_bbox.union(&background_bbox)
};
let background_bounds = bbox.to_transform();
let mut draw_strokes: Vec<_> = strokes
let mut draw_strokes: Vec<_> = trace
.iter_element_values()
.filter(|&s| !matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore))
.cloned()
@@ -278,12 +282,12 @@ async fn brush(
actual_image = blend_with_mode(actual_image, stroke_texture, stroke.style.blend_mode, (stroke.style.color.a() * 100.) as f64);
}
let has_erase_or_restore_strokes = strokes.iter_element_values().any(|s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore));
let has_erase_or_restore_strokes = trace.iter_element_values().any(|s| matches!(s.style.blend_mode, BlendMode::Erase | BlendMode::Restore));
if has_erase_or_restore_strokes {
let opaque_image = Image::new(bbox.size().x as u32, bbox.size().y as u32, Color::WHITE);
let mut erase_restore_mask = TableRow::new_from_element(Raster::new_cpu(opaque_image)).with_attribute(ATTR_TRANSFORM, background_bounds);
for stroke in strokes.into_iter().map(|row| row.into_element()) {
for stroke in trace.into_iter().map(|row| row.into_element()) {
let mut brush_texture = cache.get_cached_brush(&stroke.style);
if brush_texture.is_none() {
let tex = create_brush_texture(&stroke.style).await;
@@ -320,15 +324,15 @@ async fn brush(
let clip: bool = actual_image.attribute_cloned_or_default(ATTR_CLIPPING_MASK);
let layer: Table<NodeId> = actual_image.attribute_cloned_or_default(ATTR_EDITOR_LAYER_PATH);
*image.element_mut(0).unwrap() = actual_image.into_element();
image.set_attribute(ATTR_TRANSFORM, 0, transform);
image.set_attribute(ATTR_BLEND_MODE, 0, blend_mode);
image.set_attribute(ATTR_OPACITY, 0, opacity);
image.set_attribute(ATTR_OPACITY_FILL, 0, fill);
image.set_attribute(ATTR_CLIPPING_MASK, 0, clip);
image.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer);
*background.element_mut(0).unwrap() = actual_image.into_element();
background.set_attribute(ATTR_TRANSFORM, 0, transform);
background.set_attribute(ATTR_BLEND_MODE, 0, blend_mode);
background.set_attribute(ATTR_OPACITY, 0, opacity);
background.set_attribute(ATTR_OPACITY_FILL, 0, fill);
background.set_attribute(ATTR_CLIPPING_MASK, 0, clip);
background.set_attribute(ATTR_EDITOR_LAYER_PATH, 0, layer);
image
background
}
pub fn blend_image_closure(foreground: TableRow<Raster<CPU>>, mut background: TableRow<Raster<CPU>>, map_fn: impl Fn(Color, Color) -> Color) -> TableRow<Raster<CPU>> {

View File

@@ -6,15 +6,19 @@ use std::hash::Hasher;
use std::sync::Arc;
use std::sync::Mutex;
/// Caches the output of a given node called with a specific input.
/// Helps speed up repeated renders in a computationally-heavy part of the node graph.
///
/// A cache miss occurs when the Option is None. In this case, the node evaluates the inner node and memoizes (stores) the result.
///
/// A cache hit occurs when the Option is Some and has a stored hash matching the hash of the call argument. In this case, the node returns the cached value without re-evaluating the inner node.
///
/// Currently, only one input-output pair is cached. Subsequent calls with different inputs will overwrite the previous cache.
#[node_macro::node(category(""), path(graphene_core::memo), skip_impl)]
async fn memo<I: CacheHash + Send + 'n, T: Clone + WasmNotSend>(input: I, #[data] cache: Arc<Mutex<Option<(u64, T)>>>, node: impl Node<I, Output = T>) -> T {
/// Stores the last evaluated data that flowed through this node and immediately returns that data on subsequent renders if the context has not changed.
#[node_macro::node(category("General"), path(graphene_core::memo), skip_impl)]
async fn memoize<I: CacheHash + Send + 'n, T: Clone + WasmNotSend>(input: I, #[data] cache: Arc<Mutex<Option<(u64, T)>>>, content: impl Node<I, Output = T>) -> T {
// Caches the output of a given node called with a specific input.
//
// A cache miss occurs when the Option is None. In this case, the node evaluates the inner node and memoizes (stores) the result.
//
// A cache hit occurs when the Option is Some and has a stored hash matching the hash of the call argument. In this case, the node returns the cached value without re-evaluating the inner node.
//
// Currently, only one input-output pair is cached. Subsequent calls with different inputs will overwrite the previous cache.
let mut hasher = DefaultHasher::new();
input.cache_hash(&mut hasher);
let hash = hasher.finish();
@@ -23,23 +27,23 @@ async fn memo<I: CacheHash + Send + 'n, T: Clone + WasmNotSend>(input: I, #[data
return data;
}
let value = node.eval(input).await;
let value = content.eval(input).await;
*cache.lock().unwrap() = Some((hash, value.clone()));
value
}
type MonitorValue<I, T> = Arc<Mutex<Option<Arc<IORecord<I, T>>>>>;
/// Caches the output of the last graph evaluation for introspection.
#[node_macro::node(category(""), path(graphene_core::memo), serialize(serialize_monitor), skip_impl)]
/// The Monitor node is used by the editor to access the data flowing through it.
#[node_macro::node(category(""), path(graphene_core::memo), serialize(serialize_monitor), properties("monitor_properties"), skip_impl)]
async fn monitor<I: Clone + 'static + Send + Sync, T: Clone + 'static + Send + Sync>(
input: I,
#[allow(clippy::type_complexity)]
#[data]
io: MonitorValue<I, T>,
node: impl Node<I, Output = T>,
content: impl Node<I, Output = T>,
) -> T {
let output = node.eval(input.clone()).await;
let output = content.eval(input.clone()).await;
*io.lock().unwrap() = Some(Arc::new(IORecord { input, output: output.clone() }));
output
}

View File

@@ -4,12 +4,10 @@ use std::marker::PhantomData;
// Re-export TypeNode from core-types for convenience
pub use core_types::ops::TypeNode;
// TODO: Rename to "Passthrough" and make this the node that users use, not the one defined in document_node_definitions.rs
/// Passes-through the input value without changing it.
/// This is useful for rerouting wires for organization purposes.
#[node_macro::node(category(""), skip_impl)]
fn identity<'i, T: 'i + Send>(value: T) -> T {
value
/// Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes.
#[node_macro::node(category("General"), skip_impl)]
fn passthrough<'i, T: 'i + Send>(_: impl Ctx, content: T) -> T {
content
}
#[node_macro::node(category(""), skip_impl)]
@@ -27,7 +25,7 @@ mod test {
use super::*;
#[test]
pub fn identity_node() {
assert_eq!(identity(&4), &4);
pub fn passthrough_node() {
assert_eq!(passthrough((), &4), &4);
}
}

View File

@@ -49,7 +49,7 @@ const TX: f32 = 0.1;
// Paper: <https://www.researchgate.net/publication/220182411_Single_Image_Haze_Removal_Using_Dark_Channel_Prior>
// TODO: Make this algorithm work with negative strength values
fn dehaze_image(image: DynamicImage, strength: f64) -> DynamicImage {
// TODO: Break out this pair of steps into its own node, with a memoize node which caches the pair of outputs, so the strength can be adjusted without recomputing these two steps.
// TODO: Break out this pair of steps into its own node, with a Memoize node which caches the pair of outputs, so the strength can be adjusted without recomputing these two steps.
let dark_channel = compute_dark_channel(&image);
let atmospheric_light = estimate_atmospheric_light(&image, &dark_channel);

View File

@@ -293,25 +293,40 @@ pub fn image(_: impl Ctx, _primary: (), image: Image<Color>) -> Table<Raster<CPU
Table::new_from_element(Raster::new_cpu(image))
}
/// Generates customizable procedural noise patterns.
#[node_macro::node(category("Raster: Pattern"))]
#[allow(clippy::too_many_arguments)]
pub fn noise_pattern(
ctx: impl ExtractFootprint + Ctx,
_primary: (),
clip: bool,
#[default(true)] clip: bool,
seed: u32,
#[widget(ParsedWidgetOverride::Custom = "noise_properties_scale")]
#[default(10.)]
scale: f64,
noise_type: NoiseType,
domain_warp_type: DomainWarpType,
#[widget(ParsedWidgetOverride::Custom = "noise_properties_noise_type")] noise_type: NoiseType,
#[widget(ParsedWidgetOverride::Custom = "noise_properties_domain_warp_type")] domain_warp_type: DomainWarpType,
#[widget(ParsedWidgetOverride::Custom = "noise_properties_domain_warp_amplitude")]
#[default(100.)]
domain_warp_amplitude: f64,
fractal_type: FractalType,
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_type")] fractal_type: FractalType,
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_octaves")]
#[default(3)]
fractal_octaves: u32,
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_lacunarity")]
#[default(2.)]
fractal_lacunarity: f64,
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_gain")]
#[default(0.5)]
fractal_gain: f64,
fractal_weighted_strength: f64,
#[widget(ParsedWidgetOverride::Custom = "noise_properties_fractal_weighted_strength")] fractal_weighted_strength: f64,
#[widget(ParsedWidgetOverride::Custom = "noise_properties_ping_pong_strength")]
#[default(2.)]
fractal_ping_pong_strength: f64,
cellular_distance_function: CellularDistanceFunction,
cellular_return_type: CellularReturnType,
#[widget(ParsedWidgetOverride::Custom = "noise_properties_cellular_distance_function")] cellular_distance_function: CellularDistanceFunction,
#[widget(ParsedWidgetOverride::Custom = "noise_properties_cellular_return_type")] cellular_return_type: CellularReturnType,
#[widget(ParsedWidgetOverride::Custom = "noise_properties_cellular_jitter")]
#[default(1.)]
cellular_jitter: f64,
) -> Table<Raster<CPU>> {
let footprint = ctx.footprint();

View File

@@ -10,7 +10,7 @@ use graphic_types::raster_types::{CPU, GPU, Raster};
use vector_types::GradientStops;
/// Applies the specified transform to the input value, which may be a graphic type or another transform.
#[node_macro::node(category(""))]
#[node_macro::node(category("Math: Transform"))]
async fn transform<T: ApplyTransform + 'n + 'static>(
ctx: impl Ctx + CloneVarArgs + ExtractAll + ModifyFootprint,
#[implementations(
@@ -24,10 +24,12 @@ async fn transform<T: ApplyTransform + 'n + 'static>(
Context -> Table<GradientStops>,
)]
content: impl Node<Context<'static>, Output = T>,
translation: DVec2,
rotation: f64,
#[widget(ParsedWidgetOverride::Custom = "transform_translation")] translation: DVec2,
#[widget(ParsedWidgetOverride::Custom = "transform_rotation")] rotation: f64,
#[widget(ParsedWidgetOverride::Custom = "transform_scale")]
#[default(1., 1.)]
scale: DVec2,
skew: DVec2,
#[widget(ParsedWidgetOverride::Custom = "transform_skew")] skew: DVec2,
) -> T {
let trs = DAffine2::from_scale_angle_translation(scale, rotation.to_radians(), translation);
let skew = DAffine2::from_cols_array(&[1., skew.y.to_radians().tan(), skew.x.to_radians().tan(), 1., 0., 0.]);

View File

@@ -61,7 +61,7 @@ pub fn generate_node_substitutions() -> HashMap<ProtoNodeIdentifier, DocumentNod
let input_count = inputs.len();
let network_inputs = (0..input_count).map(|i| NodeInput::node(NodeId(i as u64), 0)).collect();
let identity_node = ops::identity::IDENTIFIER;
let passthrough_node = ops::passthrough::IDENTIFIER;
let mut generated_nodes = 0;
let mut nodes: HashMap<_, _, _> = node_io_types
@@ -88,7 +88,7 @@ pub fn generate_node_substitutions() -> HashMap<ProtoNodeIdentifier, DocumentNod
inputs.push(NodeInput::value(TaggedValue::None, false));
convert_node_identifier
} else {
identity_node.clone()
passthrough_node.clone()
};
let mut original_location = OriginalLocation::default();
original_location.auto_convert_index = Some(i);
@@ -102,7 +102,7 @@ pub fn generate_node_substitutions() -> HashMap<ProtoNodeIdentifier, DocumentNod
}
_ => DocumentNode {
inputs: vec![NodeInput::import(generic!(X), i)],
implementation: DocumentNodeImplementation::ProtoNode(identity_node.clone()),
implementation: DocumentNodeImplementation::ProtoNode(passthrough_node.clone()),
visible: false,
..Default::default()
},
@@ -127,17 +127,17 @@ pub fn generate_node_substitutions() -> HashMap<ProtoNodeIdentifier, DocumentNod
nodes.insert(NodeId(input_count as u64), document_node);
// If memoize is requested, append a Memo node after the main node and redirect the export through it
// If memoize is requested, append a Memoize node after the main node and redirect the export through it
let export_node_id = if *memoize {
let memo_node_id = NodeId(input_count as u64 + 1);
let memo_node = DocumentNode {
let memoize_node_id = NodeId(input_count as u64 + 1);
let memoize_node = DocumentNode {
inputs: vec![NodeInput::node(NodeId(input_count as u64), 0)],
implementation: DocumentNodeImplementation::ProtoNode(graphene_core::memo::memo::IDENTIFIER.clone()),
implementation: DocumentNodeImplementation::ProtoNode(graphene_core::memo::memoize::IDENTIFIER.clone()),
visible: true,
..Default::default()
};
nodes.insert(memo_node_id, memo_node);
memo_node_id
nodes.insert(memoize_node_id, memoize_node);
memoize_node_id
} else {
NodeId(input_count as u64)
};
@@ -168,10 +168,13 @@ pub fn generate_node_substitutions() -> HashMap<ProtoNodeIdentifier, DocumentNod
pub fn node_inputs(fields: &[registry::FieldMetadata], first_node_io: &NodeIOTypes) -> Vec<NodeInput> {
fields
.iter()
.zip(first_node_io.inputs.iter())
.enumerate()
.map(|(index, (field, node_io_ty))| {
let ty = field.default_type.as_ref().unwrap_or(node_io_ty);
.map(|(index, field)| {
// `skip_impl` nodes have no concrete implementations, so `first_node_io.inputs` is shorter than `fields`.
// When no type info is available for a field, fall through to the unspecified `None` value.
let Some(ty) = field.default_type.as_ref().or_else(|| first_node_io.inputs.get(index)) else {
return NodeInput::value(TaggedValue::None, true);
};
let exposed = if index == 0 { *ty != fn_type_fut!(Context, ()) } else { field.exposed };
match field.value_source {