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

@@ -692,7 +692,7 @@ fn import_usvg_path(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node,
modify_inputs.insert_vector(subpaths, layer, has_transform, path.fill().is_some(), path.stroke().is_some());
if has_transform && let Some(transform_node_id) = modify_inputs.existing_network_node_id("Transform", false) {
if has_transform && let Some(transform_node_id) = modify_inputs.existing_proto_node_id(graphene_std::transform_nodes::transform::IDENTIFIER, false) {
transform_utils::update_transform(modify_inputs.network_interface, &transform_node_id, node_transform);
}

View File

@@ -224,7 +224,9 @@ impl<'a> ModifyInputsContext<'a> {
self.network_interface.move_node_to_chain_start(&shape_id, layer, &[], self.import);
if include_transform {
let transform = resolve_network_node_type("Transform").expect("Transform node does not exist").default_node_template();
let transform = resolve_proto_node_type(graphene_std::transform_nodes::transform::IDENTIFIER)
.expect("Transform node does not exist")
.default_node_template();
let transform_id = NodeId::new();
self.network_interface.insert_node(transform_id, transform, &[]);
self.network_interface.move_node_to_chain_start(&transform_id, layer, &[], self.import);
@@ -266,7 +268,9 @@ impl<'a> ModifyInputsContext<'a> {
Some(NodeInput::value(TaggedValue::F64(typesetting.tilt), false)),
Some(NodeInput::value(TaggedValue::TextAlign(typesetting.align), false)),
]);
let transform = resolve_network_node_type("Transform").expect("Transform node does not exist").default_node_template();
let transform = resolve_proto_node_type(graphene_std::transform_nodes::transform::IDENTIFIER)
.expect("Transform node does not exist")
.default_node_template();
let stroke = resolve_proto_node_type(graphene_std::vector_nodes::stroke::IDENTIFIER)
.expect("Stroke node does not exist")
.default_node_template();
@@ -305,7 +309,9 @@ impl<'a> ModifyInputsContext<'a> {
}
pub fn insert_image_data(&mut self, image: Image<Color>, layer: LayerNodeIdentifier) {
let transform = resolve_network_node_type("Transform").expect("Transform node does not exist").default_node_template();
let transform = resolve_proto_node_type(graphene_std::transform_nodes::transform::IDENTIFIER)
.expect("Transform node does not exist")
.default_node_template();
let image_node = resolve_proto_node_type(graphene_std::raster_nodes::std_nodes::image::IDENTIFIER)
.expect("Image node does not exist")
.node_template_input_override([Some(NodeInput::value(TaggedValue::None, false)), Some(NodeInput::value(TaggedValue::ImageData(image), false))]);
@@ -506,7 +512,7 @@ impl<'a> ModifyInputsContext<'a> {
pub fn gradient_line_set(&mut self, new_start: DVec2, new_end: DVec2) {
let Some(output_layer) = self.get_output_layer() else { return };
let transform_reference = DefinitionIdentifier::Network("Transform".into());
let transform_reference = DefinitionIdentifier::ProtoNode(graphene_std::transform_nodes::transform::IDENTIFIER);
let upstream_transforms: Vec<NodeId> = self
.network_interface
.upstream_flow_back_from_nodes(vec![output_layer.to_node()], &[], network_interface::FlowType::HorizontalFlow)
@@ -553,7 +559,9 @@ impl<'a> ModifyInputsContext<'a> {
if last_transform_value.abs_diff_eq(DAffine2::IDENTITY, 1e-6) {
return;
}
let Some(id) = self.existing_network_node_id("Transform", true) else { return };
let Some(id) = self.existing_proto_node_id(graphene_std::transform_nodes::transform::IDENTIFIER, true) else {
return;
};
id
};
@@ -626,7 +634,7 @@ impl<'a> ModifyInputsContext<'a> {
pub fn transform_change_with_parent(&mut self, transform: DAffine2, transform_in: TransformIn, parent_transform: DAffine2, skip_rerender: bool) {
// Get the existing upstream Transform node and its transform, if present, otherwise use the identity transform
let (layer_transform, transform_node_id) = self
.existing_network_node_id("Transform", false)
.existing_proto_node_id(graphene_std::transform_nodes::transform::IDENTIFIER, false)
.and_then(|transform_node_id| {
let document_node = self.network_interface.document_network().nodes.get(&transform_node_id)?;
Some((transform_utils::get_current_transform(&document_node.inputs), transform_node_id))
@@ -650,7 +658,7 @@ impl<'a> ModifyInputsContext<'a> {
/// A new Transform node is created if one does not exist, unless it would be given the identity transform.
pub fn transform_set(&mut self, transform: DAffine2, transform_in: TransformIn, skip_rerender: bool) {
// Get the existing upstream Transform node, if present
let transform_node_id = self.existing_network_node_id("Transform", false);
let transform_node_id = self.existing_proto_node_id(graphene_std::transform_nodes::transform::IDENTIFIER, false);
// Compute the Transform node value so `transform_to_viewport` matches the target after re-render
let final_transform = match transform_in {
@@ -690,7 +698,7 @@ impl<'a> ModifyInputsContext<'a> {
}
// Create the Transform node
self.existing_network_node_id("Transform", true)
self.existing_proto_node_id(graphene_std::transform_nodes::transform::IDENTIFIER, true)
}) else {
return;
};
@@ -715,7 +723,7 @@ impl<'a> ModifyInputsContext<'a> {
}
pub fn brush_modify(&mut self, strokes: Vec<BrushStroke>) {
let Some(brush_node_id) = self.existing_network_node_id("Brush", true) else {
let Some(brush_node_id) = self.existing_proto_node_id(graphene_std::brush::brush::brush::IDENTIFIER, true) else {
return;
};
let strokes_table = strokes.into_iter().map(graphene_std::table::TableRow::new_from_element).collect();

View File

@@ -16,7 +16,6 @@ use graph_craft::ProtoNodeIdentifier;
use graph_craft::concrete;
use graph_craft::document::value::*;
use graph_craft::document::*;
use graphene_std::brush::brush_cache::BrushCache;
use graphene_std::extract_xy::XY;
use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha};
use graphene_std::raster_types::{CPU, Raster};
@@ -133,46 +132,6 @@ static DOCUMENT_NODE_TYPES: once_cell::sync::Lazy<HashMap<DefinitionIdentifier,
/// Only the position can be set for protonodes within a definition. The rest of the metadata comes from the node macro in NODE_METADATA
fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefinition> {
let custom = vec![
// TODO: Auto-generate this from its proto node macro
DocumentNodeDefinition {
identifier: "Passthrough",
category: "General",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::None, true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_metadata: vec![("Content", "TODO").into()],
output_names: vec!["Out".to_string()],
..Default::default()
},
},
description: Cow::Borrowed("Passes-through the input value without changing it. This is useful for rerouting wires for organization purposes."),
properties: None,
},
// TODO: Auto-generate this from its proto node macro
DocumentNodeDefinition {
identifier: "Monitor",
category: "",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::None, true)],
call_argument: generic!(T),
skip_deduplication: true,
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_metadata: vec![("In", "TODO").into()],
output_names: vec!["Out".to_string()],
..Default::default()
},
},
description: Cow::Borrowed("The Monitor node is used by the editor to access the data flowing through it."),
properties: Some("monitor_properties"),
},
DocumentNodeDefinition {
identifier: "Custom Node",
category: "General",
@@ -189,30 +148,6 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
description: Cow::Borrowed("An empty node network you can use to create your own custom nodes."),
properties: None,
},
// TODO: Auto-generate this from its proto node macro
DocumentNodeDefinition {
identifier: "Cache",
category: "General",
node_template: NodeTemplate {
document_node: DocumentNode {
inputs: vec![NodeInput::value(TaggedValue::None, true)],
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
call_argument: generic!(T),
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_metadata: vec![("Data", "TODO").into()],
output_names: vec!["Data".to_string()],
..Default::default()
},
},
description: Cow::Borrowed(
"Improves rendering performance if used in rare circumstances where automatic caching is not yet advanced enough to handle the situation.\n\
\n\
Stores the last evaluated data that flowed through this node, and immediately returns that data on subsequent renders if the context has not changed.",
),
properties: None,
},
DocumentNodeDefinition {
identifier: "Merge",
category: "General",
@@ -384,7 +319,6 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
..Default::default()
},
// The monitor node is used to display a thumbnail in the UI.
// TODO: Check if thumbnail is reversed
DocumentNode {
inputs: vec![NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
@@ -1062,7 +996,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
implementation: DocumentNodeImplementation::ProtoNode(memo::memoize::IDENTIFIER),
..Default::default()
},
DocumentNode {
@@ -1132,59 +1066,6 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
description: Cow::Borrowed("TODO"),
properties: None,
},
// TODO: Auto-generate this from its proto node macro
DocumentNodeDefinition {
identifier: "Noise Pattern",
category: "Raster: Pattern",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(graphene_std::raster_nodes::std_nodes::noise_pattern::IDENTIFIER),
inputs: vec![
NodeInput::value(TaggedValue::None, false),
NodeInput::value(TaggedValue::Bool(true), false),
NodeInput::value(TaggedValue::U32(0), false),
NodeInput::value(TaggedValue::F64(10.), false),
NodeInput::value(TaggedValue::NoiseType(NoiseType::default()), false),
NodeInput::value(TaggedValue::DomainWarpType(DomainWarpType::default()), false),
NodeInput::value(TaggedValue::F64(100.), false),
NodeInput::value(TaggedValue::FractalType(FractalType::default()), false),
NodeInput::value(TaggedValue::U32(3), false),
NodeInput::value(TaggedValue::F64(2.), false),
NodeInput::value(TaggedValue::F64(0.5), false),
NodeInput::value(TaggedValue::F64(0.), false), // 0-1 range
NodeInput::value(TaggedValue::F64(2.), false),
NodeInput::value(TaggedValue::CellularDistanceFunction(CellularDistanceFunction::default()), false),
NodeInput::value(TaggedValue::CellularReturnType(CellularReturnType::default()), false),
NodeInput::value(TaggedValue::F64(1.), false),
],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_metadata: vec![
("Spacer", "TODO").into(),
("Clip", "TODO").into(),
("Seed", "TODO").into(),
InputMetadata::with_name_description_override("Scale", "TODO", WidgetOverride::Custom("noise_properties_scale".to_string())),
InputMetadata::with_name_description_override("Noise Type", "TODO", WidgetOverride::Custom("noise_properties_noise_type".to_string())),
InputMetadata::with_name_description_override("Domain Warp Type", "TODO", WidgetOverride::Custom("noise_properties_domain_warp_type".to_string())),
InputMetadata::with_name_description_override("Domain Warp Amplitude", "TODO", WidgetOverride::Custom("noise_properties_domain_warp_amplitude".to_string())),
InputMetadata::with_name_description_override("Fractal Type", "TODO", WidgetOverride::Custom("noise_properties_fractal_type".to_string())),
InputMetadata::with_name_description_override("Fractal Octaves", "TODO", WidgetOverride::Custom("noise_properties_fractal_octaves".to_string())),
InputMetadata::with_name_description_override("Fractal Lacunarity", "TODO", WidgetOverride::Custom("noise_properties_fractal_lacunarity".to_string())),
InputMetadata::with_name_description_override("Fractal Gain", "TODO", WidgetOverride::Custom("noise_properties_fractal_gain".to_string())),
InputMetadata::with_name_description_override("Fractal Weighted Strength", "TODO", WidgetOverride::Custom("noise_properties_fractal_weighted_strength".to_string())),
InputMetadata::with_name_description_override("Fractal Ping Pong Strength", "TODO", WidgetOverride::Custom("noise_properties_ping_pong_strength".to_string())),
InputMetadata::with_name_description_override("Cellular Distance Function", "TODO", WidgetOverride::Custom("noise_properties_cellular_distance_function".to_string())),
InputMetadata::with_name_description_override("Cellular Return Type", "TODO", WidgetOverride::Custom("noise_properties_cellular_return_type".to_string())),
InputMetadata::with_name_description_override("Cellular Jitter", "TODO", WidgetOverride::Custom("noise_properties_cellular_jitter".to_string())),
],
output_names: vec!["Image".to_string()],
..Default::default()
},
},
description: Cow::Borrowed("Generates customizable procedural noise patterns."),
properties: None,
},
DocumentNodeDefinition {
identifier: "Split Channels",
category: "Raster: Channels",
@@ -1364,81 +1245,6 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
),
properties: None,
},
// TODO: Remove this and just use the proto node definition directly
DocumentNodeDefinition {
identifier: "Brush",
category: "Raster",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(0), 0)],
nodes: vec![DocumentNode {
inputs: vec![
NodeInput::import(concrete!(Table<Raster<CPU>>), 0),
NodeInput::import(concrete!(Vec<brush::brush_stroke::BrushStroke>), 1),
NodeInput::import(concrete!(BrushCache), 2),
],
implementation: DocumentNodeImplementation::ProtoNode(brush::brush::brush::IDENTIFIER),
..Default::default()
}]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
inputs: vec![
NodeInput::value(TaggedValue::Raster(Default::default()), true),
NodeInput::value(TaggedValue::BrushStrokeTable(Default::default()), false),
NodeInput::value(TaggedValue::BrushCache(BrushCache::default()), false),
],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_metadata: vec![("Background", "TODO").into(), ("Trace", "TODO").into(), ("Cache", "TODO").into()],
output_names: vec!["Image".to_string()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
}]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
},
..Default::default()
}),
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
properties: None,
},
// TODO: Auto-generate this from its proto node macro
DocumentNodeDefinition {
identifier: "Memoize",
category: "Debug",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_metadata: vec![("Image", "TODO").into()],
output_names: vec!["Image".to_string()],
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
properties: None,
},
#[cfg(feature = "gpu")]
DocumentNodeDefinition {
identifier: "Upload Texture",
@@ -1462,7 +1268,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
DocumentNode {
call_argument: generic!(T),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
implementation: DocumentNodeImplementation::ProtoNode(memo::memoize::IDENTIFIER),
..Default::default()
},
]
@@ -1641,34 +1447,6 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
),
properties: None,
},
// Aims for interoperable compatibility with:
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=levl%27%20%3D%20Levels-,%27curv%27%20%3D%20Curves,-%27expA%27%20%3D%20Exposure
// https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#:~:text=Max%20input%20range-,Curves,-Curves%20settings%20files
//
// Some further analysis available at:
// https://geraldbakker.nl/psnumbers/curves.html
// TODO: Fix this, it's currently broken
// DocumentNodeDefinition {
// identifier: "Curves",
// category: "Raster: Adjustment",
// node_template: NodeTemplate {
// document_node: DocumentNode {
// implementation: DocumentNodeImplementation::proto("core_types::raster::CurvesNode"),
// inputs: vec![
// NodeInput::value(TaggedValue::Raster(Default::default()), true),
// NodeInput::value(TaggedValue::Curve(Default::default()), false),
// ],
// ..Default::default()
// },
// persistent_node_metadata: DocumentNodePersistentMetadata {
// input_properties: vec![("Image", "TODO").into(), ("Curve", "TODO").into()],
// output_names: vec!["Image".to_string()],
// ..Default::default()
// },
// },
// description: Cow::Borrowed("TODO"),
// properties: None,
// },
DocumentNodeDefinition {
identifier: "Path",
category: "Vector",
@@ -1742,132 +1520,6 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
description: Cow::Borrowed("TODO"),
properties: None,
},
// TODO: Auto-generate this from its proto node macro
DocumentNodeDefinition {
identifier: "Transform",
category: "Math: Transform",
node_template: NodeTemplate {
document_node: DocumentNode {
inputs: vec![
// Value
NodeInput::value(TaggedValue::DAffine2(DAffine2::default()), true),
// Translation
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
// Rotation
NodeInput::value(TaggedValue::F64(0.), false),
// Scale
NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false),
// Skew
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
// Origin Offset
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
// Scale Appearance
NodeInput::value(TaggedValue::Bool(true), false),
],
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![
// From the Transform node
NodeInput::node(NodeId(1), 0),
],
nodes: [
// Monitor node
DocumentNode {
inputs: vec![
// From the Value import
NodeInput::import(generic!(T), 0),
],
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
call_argument: generic!(T),
skip_deduplication: true,
..Default::default()
},
// Transform node
DocumentNode {
inputs: vec![
// From the Monitor node
NodeInput::node(NodeId(0), 0),
// From the Translation import
NodeInput::import(concrete!(DVec2), 1),
// From the Rotation import
NodeInput::import(concrete!(f64), 2),
// From the Scale import
NodeInput::import(concrete!(DVec2), 3),
// From the Skew import
NodeInput::import(concrete!(DVec2), 4),
],
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::transform::IDENTIFIER),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
},
..Default::default()
}),
input_metadata: vec![
("Value", "TODO").into(),
InputMetadata::with_name_description_override(
"Translation",
"TODO",
WidgetOverride::Vec2(Vec2InputSettings {
x: "X".to_string(),
y: "Y".to_string(),
unit: " px".to_string(),
..Default::default()
}),
),
InputMetadata::with_name_description_override("Rotation", "TODO", WidgetOverride::Custom("transform_rotation".to_string())),
InputMetadata::with_name_description_override(
"Scale",
"TODO",
WidgetOverride::Vec2(Vec2InputSettings {
x: "W".to_string(),
y: "H".to_string(),
unit: "x".to_string(),
..Default::default()
}),
),
InputMetadata::with_name_description_override("Skew", "TODO", WidgetOverride::Custom("transform_skew".to_string())),
InputMetadata::with_name_description_override("Origin Offset", "TODO", WidgetOverride::Custom("hidden".to_string())),
InputMetadata::with_name_description_override("Scale Appearance", "TODO", WidgetOverride::Custom("hidden".to_string())),
],
output_names: vec!["Data".to_string()],
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
properties: None,
},
];
document_node_derive::post_process_nodes(custom)
@@ -2323,6 +1975,25 @@ fn static_input_properties() -> InputProperties {
Ok(vec![LayoutGroup::row(widgets)])
}),
);
// Translation uses a Vec2 widget with X/Y labels and a "px" unit suffix
map.insert(
"transform_translation".to_string(),
Box::new(|node_id, index, context| {
Ok(vec![node_properties::vec2_widget(
ParameterWidgetsInfo::new(node_id, index, true, context),
"X",
"Y",
" px",
None,
false,
)])
}),
);
// Scale uses a Vec2 widget with W/H labels and an "x" unit suffix
map.insert(
"transform_scale".to_string(),
Box::new(|node_id, index, context| Ok(vec![node_properties::vec2_widget(ParameterWidgetsInfo::new(node_id, index, true, context), "W", "H", "x", None, false)])),
);
// Skew has a custom override that maps to degrees
map.insert(
"transform_skew".to_string(),

View File

@@ -22,8 +22,15 @@ pub(super) fn post_process_nodes(custom: Vec<DocumentNodeDefinition>) -> HashMap
})
.collect::<Vec<_>>();
// Add the rest of the protonodes from the macro
// Add the rest of the protonodes from the macro.
// Typed nodes are registered in `core_types::NODE_REGISTRY` via the macro's auto-generated `register_node` codegen.
// `skip_impl` nodes (e.g. Cache, Monitor) bypass that registration but are wired up manually in
// `interpreted_executor::node_registry::NODE_REGISTRY` via `async_node!`. We consult that extended registry as a
// fallback when deriving `call_argument` so it reflects the impls actually registered, which will usually be `Context`.
let extended_node_registry = &*interpreted_executor::node_registry::NODE_REGISTRY;
let node_registry = NODE_REGISTRY.lock().unwrap();
let empty_implementations: Vec<(NodeConstructor, NodeIOTypes)> = Vec::new();
let context_type = concrete!(Context);
for (id, metadata) in NODE_METADATA.lock().unwrap().iter() {
let identifier = DefinitionIdentifier::ProtoNode(id.clone());
if definitions_map.contains_key(&identifier) {
@@ -39,12 +46,25 @@ pub(super) fn post_process_nodes(custom: Vec<DocumentNodeDefinition>) -> HashMap
memoize: _,
} = metadata;
let Some(implementations) = &node_registry.get(id) else { continue };
let implementations = node_registry.get(id).unwrap_or(&empty_implementations);
let first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() });
let valid_inputs: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect();
let input_type = if valid_inputs.len() > 1 { &const { generic!(D) } } else { &first_node_io.call_argument };
let call_arguments: Vec<&Type> = if !implementations.is_empty() {
implementations.iter().map(|(_, io)| &io.call_argument).collect()
} else if let Some(impls) = extended_node_registry.get(id) {
impls.keys().map(|io| &io.call_argument).collect()
} else {
Vec::new()
};
let valid_inputs: HashSet<&Type> = call_arguments.iter().copied().collect();
let input_type = if valid_inputs.is_empty() {
&context_type
} else if valid_inputs.len() > 1 {
&const { generic!(D) }
} else {
call_arguments[0]
};
let inputs = preprocessor::node_inputs(fields, first_node_io);
definitions_map.insert(
@@ -83,16 +103,6 @@ pub(super) fn post_process_nodes(custom: Vec<DocumentNodeDefinition>) -> HashMap
);
}
// If any protonode does not have metadata then set its display name to its identifier string
for definition in definitions_map.values_mut() {
let metadata = NODE_METADATA.lock().unwrap();
if let DocumentNodeImplementation::ProtoNode(id) = &definition.node_template.document_node.implementation
&& !metadata.contains_key(id)
{
definition.node_template.persistent_node_metadata.display_name = definition.identifier.to_string();
}
}
// Add the rest of the network nodes to the map and add the metadata for their internal protonodes
for mut network_node in network_nodes {
traverse_node(&network_node.node_template.document_node, &mut network_node.node_template.persistent_node_metadata, &definitions_map);

View File

@@ -214,7 +214,11 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
let mid_point = (network_interface.get_output_center(&output_connector, breadcrumb_network_path).unwrap()
+ network_interface.get_input_center(&input_connector, breadcrumb_network_path).unwrap())
/ 2.;
let node_template = Box::new(resolve_proto_node_type(graphene_core::ops::identity::IDENTIFIER).unwrap().default_node_template());
let Some(passthrough_definition) = resolve_proto_node_type(graphene_core::ops::passthrough::IDENTIFIER) else {
log::error!("Could not resolve passthrough node when wiring an export to an import");
return;
};
let node_template = Box::new(passthrough_definition.default_node_template());
let node_id = NodeId::new();
responses.add(NodeGraphMessage::InsertNode { node_id, node_template });

View File

@@ -126,7 +126,7 @@ impl DocumentMetadata {
let local_transform = self.local_transforms.get(&layer.to_node()).copied();
let transform = local_transform.unwrap_or_else(|| {
let transform_node_id = ModifyInputsContext::locate_node_in_layer_chain(&DefinitionIdentifier::Network("Transform".into()), layer, network_interface);
let transform_node_id = ModifyInputsContext::locate_node_in_layer_chain(&DefinitionIdentifier::ProtoNode(graphene_std::transform_nodes::transform::IDENTIFIER), layer, network_interface);
let transform_node = transform_node_id.and_then(|id| network_interface.document_node(&id, &[]));
transform_node.map(|node| transform_utils::get_current_transform(node.inputs.as_slice())).unwrap_or_default()
});

View File

@@ -236,8 +236,8 @@ impl NodeNetworkInterface {
}
DocumentNodeImplementation::ProtoNode(proto_node_identifier) => {
let Some(implementations) = NODE_REGISTRY.get(proto_node_identifier) else {
// The compiler removes the identity node, so it's expected to be absent from the registry
if proto_node_identifier != &graphene_std::ops::identity::IDENTIFIER {
// The compiler removes the passthrough node, so it's expected to be absent from the registry
if proto_node_identifier != &graphene_std::ops::passthrough::IDENTIFIER {
log::error!("Proto node `{proto_node_identifier:?}` not found in the node registry, in potential_valid_input_types");
}
return Vec::new();

View File

@@ -54,7 +54,7 @@ impl OriginalTransforms {
/// Gets the transform from the most downstream transform node
fn get_layer_transform(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<DAffine2> {
let transform_node_id = ModifyInputsContext::locate_node_in_layer_chain(&DefinitionIdentifier::Network("Transform".into()), layer, network_interface)?;
let transform_node_id = ModifyInputsContext::locate_node_in_layer_chain(&DefinitionIdentifier::ProtoNode(graphene_std::transform_nodes::transform::IDENTIFIER), layer, network_interface)?;
let document_node = network_interface.document_network().nodes.get(&transform_node_id)?;
Some(transform_utils::get_current_transform(&document_node.inputs))

View File

@@ -91,8 +91,9 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
aliases: &["graphene_core::ops::ExtractXyNode"],
},
NodeReplacement {
node: graphene_std::ops::identity::IDENTIFIER,
node: graphene_std::ops::passthrough::IDENTIFIER,
aliases: &[
"graphene_core::ops::IdentityNode",
"graphene_core::transform::CullNode",
"graphene_core::transform::BoundlessFootprintNode",
"graphene_core::transform::FreezeRealTimeNode",
@@ -107,7 +108,7 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
aliases: &["graphene_core::memo::MonitorNode"],
},
NodeReplacement {
node: graphene_std::memo::memo::IDENTIFIER,
node: graphene_std::memo::memoize::IDENTIFIER,
aliases: &["graphene_core::memo::MemoNode", "graphene_core::memo::ImpureMemoNode"],
},
NodeReplacement {
@@ -1064,6 +1065,95 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
}
}
// The "Brush" wrapper network was replaced with the `brush` proto node directly. Convert old `Network("Brush")` instances to the proto node, forwarding all 3 inputs (Background, Trace, Cache) one-to-one.
// This must run as a pre-pass before the recursive iteration below: replacing the outer Brush's network impl orphans its child paths, and the recursive iteration would log errors for those stale paths.
let brush_layers: Vec<(NodeId, Vec<NodeId>)> = document
.network_interface
.document_network()
.recursive_nodes()
.filter_map(|(node_id, _, path)| (document.network_interface.reference(node_id, &path) == Some(DefinitionIdentifier::Network("Brush".into()))).then_some((*node_id, path)))
.collect();
for (node_id, network_path) in &brush_layers {
// Pre-load `outward_wires` so the chain-break check inside `set_input` resolves the original upstream→node wire from cache
// rather than triggering a fresh rebuild from the (already-mutated) post-`replace_inputs` state, which would orphan wires.
let _ = document.network_interface.outward_wires(network_path);
let new_reference = DefinitionIdentifier::ProtoNode(graphene_std::brush::brush::brush::IDENTIFIER);
let Some(definition) = resolve_document_node_type(&new_reference) else { continue };
let mut node_template = definition.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let Some(old_inputs) = document.network_interface.replace_inputs(node_id, network_path, &mut node_template) else {
continue;
};
for (index, input) in old_inputs.iter().take(3).enumerate() {
document.network_interface.set_input(&InputConnector::node(*node_id, index), input.clone(), network_path);
}
}
// The "Transform" wrapper network was replaced with the `transform` proto node directly. Convert old `Network("Transform")` instances to the proto node, forwarding the 5 user-facing inputs (Value, Translation, Rotation, Scale, Skew) and dropping the legacy migration sentinels (Origin Offset, Scale Appearance) at indices 5 and 6 if present.
// Pre-pass for the same reason as the Brush migration above: replacing the outer Transform's network impl orphans its child paths.
let transform_layers: Vec<(NodeId, Vec<NodeId>, usize)> = document
.network_interface
.document_network()
.recursive_nodes()
.filter_map(|(node_id, node, path)| {
(document.network_interface.reference(node_id, &path) == Some(DefinitionIdentifier::Network("Transform".into()))).then_some((*node_id, path, node.inputs.len()))
})
.collect();
for (node_id, network_path, old_inputs_count) in &transform_layers {
// Pre-load `outward_wires` so the chain-break check inside `set_input` resolves the original upstream→node wire from cache
// rather than triggering a fresh rebuild from the (already-mutated) post-`replace_inputs` state, which would orphan wires.
let _ = document.network_interface.outward_wires(network_path);
let new_reference = DefinitionIdentifier::ProtoNode(graphene_std::transform_nodes::transform::IDENTIFIER);
let Some(definition) = resolve_document_node_type(&new_reference) else { continue };
let mut node_template = definition.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let Some(old_inputs) = document.network_interface.replace_inputs(node_id, network_path, &mut node_template) else {
continue;
};
// Forward the first 5 inputs (Value, Translation, Rotation, Scale, Skew); drop indices 5 and 6 if present.
for (index, input) in old_inputs.iter().take(5).enumerate() {
document.network_interface.set_input(&InputConnector::node(*node_id, index), input.clone(), network_path);
}
// Pre-2024 documents stored Transform with 6 inputs and used radians for Rotation and `tan(radians)` for Skew. Detect that legacy
// shape (no input at index 6) and convert the units to degrees so the values match what the new Properties panel widgets expect.
if *old_inputs_count == 6 {
match old_inputs.get(2) {
Some(NodeInput::Value { tagged_value, exposed }) => {
if let TaggedValue::F64(radians) = *tagged_value.clone().into_inner() {
let degrees = NodeInput::value(TaggedValue::F64(radians.to_degrees()), *exposed);
document.network_interface.set_input(&InputConnector::node(*node_id, 2), degrees, network_path);
}
}
Some(NodeInput::Node { .. }) => {
// Wired upstream: splice in a Multiply node by 180/π that converts radians to degrees so the upstream value
// (which represented radians in the legacy format) reaches the now-degrees Rotation input correctly.
if let Some(multiply_node) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::multiply::IDENTIFIER)) {
let mut multiply_template = multiply_node.default_node_template();
multiply_template.document_node.inputs[1] = NodeInput::value(TaggedValue::F64(180. / PI), false);
let multiply_node_id = NodeId::new();
if let Some(transform_position) = document.network_interface.position_from_downstream_node(node_id, network_path) {
let multiply_position = transform_position + IVec2::new(-7, 1);
document.network_interface.insert_node(multiply_node_id, multiply_template, network_path);
document.network_interface.shift_absolute_node_position(&multiply_node_id, multiply_position, network_path);
document.network_interface.insert_node_between(&multiply_node_id, &InputConnector::node(*node_id, 2), 0, network_path);
}
}
}
_ => {}
}
if let Some(NodeInput::Value { tagged_value, exposed }) = old_inputs.get(4)
&& let TaggedValue::DVec2(old_value) = *tagged_value.clone().into_inner()
{
// The previous skew value stored `tan(radians)`, now it stores degrees directly.
let new_value = DVec2::new(old_value.x.atan().to_degrees(), old_value.y.atan().to_degrees());
let new_input = NodeInput::value(TaggedValue::DVec2(new_value), *exposed);
document.network_interface.set_input(&InputConnector::node(*node_id, 4), new_input, network_path);
}
}
}
// Apply upgrades to each unmodified node.
let nodes = document
.network_interface
@@ -1677,79 +1767,6 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
document.network_interface.add_import(TaggedValue::U32(0), false, 1, "Loop Level", "TODO", &node_path);
}
// Migrate the Transform node to use degrees instead of radians
if reference == DefinitionIdentifier::Network("Transform".into()) && node.inputs.get(6).is_none() {
let mut node_template = resolve_network_node_type("Transform")?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
// Value
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
// Translation
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
// Rotation
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
// Scale
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[3].clone(), network_path);
// Skew
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[4].clone(), network_path);
// Origin Offset
document
.network_interface
.set_input(&InputConnector::node(*node_id, 5), NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false), network_path);
// Scale Appearance
document
.network_interface
.set_input(&InputConnector::node(*node_id, 6), NodeInput::value(TaggedValue::Bool(true), false), network_path);
// Migrate rotation from radians to degrees
match node.inputs.get(2)? {
NodeInput::Value { tagged_value, exposed } => {
// Read the existing Properties panel number value, which used to be in radians
let TaggedValue::F64(radians) = *tagged_value.clone().into_inner() else { return None };
// Convert the radians to degrees and set it back as the new input value
let degrees = NodeInput::value(TaggedValue::F64(radians.to_degrees()), *exposed);
document.network_interface.set_input(&InputConnector::node(*node_id, 2), degrees, network_path);
}
NodeInput::Node { .. } => {
// Construct a new Multiply node for converting from degrees to radians
let Some(multiply_node) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::multiply::IDENTIFIER)) else {
log::error!("Could not get multiply node from definition when upgrading transform");
return None;
};
let mut multiply_template = multiply_node.default_node_template();
multiply_template.document_node.inputs[1] = NodeInput::value(TaggedValue::F64(180. / PI), false);
// Decide on the placement position of the new Multiply node
let multiply_node_id = NodeId::new();
let Some(transform_position) = document.network_interface.position_from_downstream_node(node_id, network_path) else {
log::error!("Could not get positon for transform node {node_id}");
return None;
};
let multiply_position = transform_position + IVec2::new(-7, 1);
// Insert the new Multiply node into the network directly before it's used
document.network_interface.insert_node(multiply_node_id, multiply_template, network_path);
document.network_interface.shift_absolute_node_position(&multiply_node_id, multiply_position, network_path);
document.network_interface.insert_node_between(&multiply_node_id, &InputConnector::node(*node_id, 2), 0, network_path);
}
_ => {}
};
// Migrate skew from radians to degrees
if let NodeInput::Value { tagged_value, exposed } = node.inputs.get(4)? {
// Read the existing Properties panel number value, which used to be in radians
let TaggedValue::DVec2(old_value) = *tagged_value.clone().into_inner() else { return None };
// The previous value stored the tangent of the displayed degrees. Now it stores the degrees, so take the arctan of it and convert to degrees.
let new_value = DVec2::new(old_value.x.atan().to_degrees(), old_value.y.atan().to_degrees());
let new_input = NodeInput::value(TaggedValue::DVec2(new_value), *exposed);
document.network_interface.set_input(&InputConnector::node(*node_id, 4), new_input, network_path);
}
}
// Upgrade the "Animation" node to add the "Rate" input
if reference == DefinitionIdentifier::ProtoNode(graphene_std::animation::animation_time::IDENTIFIER) && inputs_count < 2 {
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
@@ -1954,7 +1971,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
if let Some(downstream_input) = downstream {
// Create a Transform node with translation = start
let Some(transform_node_type) = resolve_network_node_type("Transform") else {
let Some(transform_node_type) = resolve_proto_node_type(graphene_std::transform_nodes::transform::IDENTIFIER) else {
log::error!("Transform node definition not found during Arrow migration");
return None;
};
@@ -2015,7 +2032,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
if let Some(downstream_input) = downstream {
// Create a Transform node with translation = start
let Some(transform_node_type) = resolve_network_node_type("Transform") else {
let Some(transform_node_type) = resolve_proto_node_type(graphene_std::transform_nodes::transform::IDENTIFIER) else {
log::error!("Transform node definition not found during Line migration");
return None;
};
@@ -2140,7 +2157,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
fn migrate_removed_catalog_definitions(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document: &mut DocumentMessageHandler) -> Option<()> {
// Collapse the legacy "Sample Polyline" wrapper network into the standalone `sample_polyline` proto node.
// The proto node now computes per-bezpath segment lengths inline, so the wrapper's separate `subpath_segment_lengths`
// and `Memo` nodes are no longer needed. The 7 user-facing inputs are positionally identical between the
// and `Memoize` nodes are no longer needed. The 7 user-facing inputs are positionally identical between the
// old wrapper and the new proto node.
if let Some(DefinitionIdentifier::Network(name)) = document.network_interface.reference(node_id, network_path)
&& name == "Sample Polyline"
@@ -2155,7 +2172,7 @@ fn migrate_removed_catalog_definitions(node_id: &NodeId, node: &DocumentNode, ne
}
// Collapse the legacy "Scatter Points" wrapper network into the standalone `scatter_points` proto node.
// The wrapper's trailing `Memo` node is now produced automatically by the `memoize` attribute on the
// The wrapper's trailing `Memoize` node is now produced automatically by the `memoize` attribute on the
// proto node, so the wrapper itself is redundant. The 3 user-facing inputs are positionally identical
// between the old wrapper and the new proto node.
if let Some(DefinitionIdentifier::Network(name)) = document.network_interface.reference(node_id, network_path)
@@ -2171,7 +2188,7 @@ fn migrate_removed_catalog_definitions(node_id: &NodeId, node: &DocumentNode, ne
}
// Collapse the legacy "Boolean Operation" wrapper network into the standalone `boolean_operation` proto node.
// The wrapper's trailing `Memo` node is now produced automatically by the `memoize` attribute on the
// The wrapper's trailing `Memoize` node is now produced automatically by the `memoize` attribute on the
// proto node, so the wrapper itself is redundant. The 2 user-facing inputs are positionally identical
// between the old wrapper and the new proto node.
if let Some(DefinitionIdentifier::Network(name)) = document.network_interface.reference(node_id, network_path)

View File

@@ -147,7 +147,7 @@ pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIde
// Add a transform node to ensure correct tooling modifications
let transform_node_id = NodeId::new();
let transform_node = document_node_definitions::resolve_network_node_type("Transform")
let transform_node = document_node_definitions::resolve_proto_node_type(graphene_std::transform_nodes::transform::IDENTIFIER)
.expect("Failed to create transform node")
.default_node_template();
responses.add(NodeGraphMessage::InsertNode {
@@ -251,7 +251,9 @@ pub fn new_custom(id: NodeId, nodes: Vec<(NodeId, NodeTemplate)>, parent: LayerN
pub fn get_origin(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<DVec2> {
use graphene_std::transform_nodes::transform::*;
if let TaggedValue::DVec2(origin) = NodeGraphLayer::new(layer, network_interface).find_input(&DefinitionIdentifier::Network("Transform".into()), TranslationInput::INDEX)? {
if let TaggedValue::DVec2(origin) =
NodeGraphLayer::new(layer, network_interface).find_input(&DefinitionIdentifier::ProtoNode(graphene_std::transform_nodes::transform::IDENTIFIER), TranslationInput::INDEX)?
{
Some(*origin)
} else {
None

View File

@@ -1,7 +1,7 @@
use super::tool_prelude::*;
use crate::consts::DEFAULT_BRUSH_SIZE;
use crate::messages::portfolio::document::graph_operation::transform_utils::get_current_transform;
use crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_network_node_type};
use crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_proto_node_type};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::FlowType;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
@@ -319,7 +319,7 @@ impl BrushToolData {
continue;
};
if reference == DefinitionIdentifier::Network("Brush".into()) && node_id != layer.to_node() {
if reference == DefinitionIdentifier::ProtoNode(graphene_std::brush::brush::brush::IDENTIFIER) && node_id != layer.to_node() {
let points_input = node.inputs.get(1)?;
let Some(TaggedValue::BrushStrokeTable(strokes)) = points_input.as_value() else { continue };
self.strokes = strokes.iter_element_values().cloned().collect();
@@ -327,7 +327,7 @@ impl BrushToolData {
return Some(layer);
}
if reference == DefinitionIdentifier::Network("Transform".into()) {
if reference == DefinitionIdentifier::ProtoNode(graphene_std::transform_nodes::transform::IDENTIFIER) {
self.transform = get_current_transform(&node.inputs) * self.transform;
}
}
@@ -478,7 +478,9 @@ impl Fsm for BrushToolFsmState {
fn new_brush_layer(document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) -> LayerNodeIdentifier {
responses.add(DocumentMessage::DeselectAllLayers);
let brush_node = resolve_network_node_type("Brush").expect("Brush node does not exist").default_node_template();
let brush_node = resolve_proto_node_type(graphene_std::brush::brush::brush::IDENTIFIER)
.expect("Brush node does not exist")
.default_node_template();
let id = NodeId::new();
responses.add(GraphOperationMessage::NewCustomLayer {

View File

@@ -344,7 +344,7 @@ struct GradientChainState {
/// Resolve the gradient transform, type, and spread method by walking the chain feeding the layer. Transform composes all
/// 'Transform' nodes. Type and spread method come from the closest-to-layer node of each kind, or the type default.
fn read_gradient_chain_state(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> GradientChainState {
let transform_reference = DefinitionIdentifier::Network("Transform".into());
let transform_reference = DefinitionIdentifier::ProtoNode(graphene_std::transform_nodes::transform::IDENTIFIER);
let gradient_type_reference = DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::gradient_type::IDENTIFIER);
let spread_method_reference = DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::spread_method::IDENTIFIER);

View File

@@ -767,7 +767,7 @@ mod test_transform_layer {
let document = editor.active_document();
let network_interface = &document.network_interface;
let _responses: VecDeque<Message> = VecDeque::new();
let transform_node_id = ModifyInputsContext::locate_node_in_layer_chain(&DefinitionIdentifier::Network("Transform".into()), layer, network_interface)?;
let transform_node_id = ModifyInputsContext::locate_node_in_layer_chain(&DefinitionIdentifier::ProtoNode(graphene_std::transform_nodes::transform::IDENTIFIER), layer, network_interface)?;
let document_node = network_interface.document_network().nodes.get(&transform_node_id)?;
Some(transform_utils::get_current_transform(&document_node.inputs))
}