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:
parent
b27b4c6be7
commit
21e5e06b0b
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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>> {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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.]);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in New Issue