Replace node definition string-based lookups with DefinitionIdentifier instances (#3451)

* create definition identifier and integrate it

* Bug fixes and code review

* formatting

* Fix migrations

* Fix remove handles migration

* formatting

* Fix test

* Fix tests 2

* fix deserialization

* Code review

* Small fixes

* Consolidate 'Morph' node migrations

* Add old SamplePointsNode name to migrations list

* Fix tests

* Unrelated small fix

* Fix migration crashes

* Fix tests

* Final code review

* fmt

* Add metadata

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
Adam Gerhant
2026-01-12 23:09:43 -08:00
committed by GitHub
co-authored by Keavon Chambers
parent 4fea2b0fe7
commit a6052c5819
63 changed files with 843 additions and 802 deletions
@@ -11,6 +11,7 @@ use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::data_panel::{DataPanelMessageContext, DataPanelMessageHandler};
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::node_graph::NodeGraphMessageContext;
use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;
use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;
use crate::messages::portfolio::document::overlays::grid_overlays::{grid_overlay, overlay_options};
use crate::messages::portfolio::document::overlays::utility_types::{OverlaysType, OverlaysVisibilitySettings, Pivot};
@@ -1548,7 +1549,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
// Create an artboard and set its dimensions to the bounding box size and location
let node_id = NodeId::new();
let node_layer_id = LayerNodeIdentifier::new_unchecked(node_id);
let new_artboard_node = document_node_definitions::resolve_document_node_type("Artboard")
let new_artboard_node = document_node_definitions::resolve_network_node_type("Artboard")
.expect("Failed to create artboard node")
.default_node_template();
responses.add(NodeGraphMessage::InsertNode {
@@ -2133,8 +2134,7 @@ impl DocumentMessageHandler {
network_interface.upstream_flow_back_from_nodes(vec![selected_id.to_node()], &[], FlowType::HorizontalFlow).find(|id| {
network_interface
.reference(id, &[])
.map(|name| name.as_deref().unwrap_or_default() == "Boolean Operation")
.unwrap_or_default()
.is_some_and(|reference| reference == DefinitionIdentifier::Network("Boolean Operation".into()))
})
});
@@ -382,7 +382,7 @@ fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node,
modify_inputs.insert_vector(subpaths, layer, true, path.fill().is_some(), path.stroke().is_some());
if let Some(transform_node_id) = modify_inputs.existing_node_id("Transform", true) {
if let Some(transform_node_id) = modify_inputs.existing_network_node_id("Transform", true) {
transform_utils::update_transform(modify_inputs.network_interface, &transform_node_id, transform * usvg_transform(node.abs_transform()));
}
@@ -1,12 +1,12 @@
use super::transform_utils;
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
use crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_document_node_type, resolve_network_node_type, resolve_proto_node_type};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector};
use crate::messages::prelude::*;
use glam::{DAffine2, IVec2};
use graph_craft::concrete;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput};
use graph_craft::{ProtoNodeIdentifier, concrete};
use graphene_std::Artboard;
use graphene_std::brush::brush_stroke::BrushStroke;
use graphene_std::raster::BlendMode;
@@ -123,14 +123,14 @@ impl<'a> ModifyInputsContext<'a> {
/// Creates a new layer and adds it to the document network. network_interface.move_layer_to_stack should be called after
pub fn create_layer(&mut self, new_id: NodeId) -> LayerNodeIdentifier {
let new_merge_node = resolve_document_node_type("Merge").expect("Merge node").default_node_template();
let new_merge_node = resolve_network_node_type("Merge").expect("Merge node").default_node_template();
self.network_interface.insert_node(new_id, new_merge_node, &[]);
LayerNodeIdentifier::new(new_id, self.network_interface)
}
/// Creates an artboard as the primary export for the document network
pub fn create_artboard(&mut self, new_id: NodeId, artboard: Artboard) -> LayerNodeIdentifier {
let artboard_node_template = resolve_document_node_type("Artboard").expect("Node").node_template_input_override([
let artboard_node_template = resolve_network_node_type("Artboard").expect("Node").node_template_input_override([
Some(NodeInput::value(TaggedValue::Artboard(Default::default()), true)),
Some(NodeInput::value(TaggedValue::Graphic(Default::default()), true)),
Some(NodeInput::value(TaggedValue::DVec2(artboard.location.into()), false)),
@@ -143,7 +143,7 @@ impl<'a> ModifyInputsContext<'a> {
}
pub fn insert_boolean_data(&mut self, operation: graphene_std::path_bool::BooleanOperation, layer: LayerNodeIdentifier) {
let boolean = resolve_document_node_type("Boolean Operation").expect("Boolean node does not exist").node_template_input_override([
let boolean = resolve_network_node_type("Boolean Operation").expect("Boolean node does not exist").node_template_input_override([
Some(NodeInput::value(TaggedValue::Graphic(Default::default()), true)),
Some(NodeInput::value(TaggedValue::BooleanOperation(operation), false)),
]);
@@ -156,7 +156,7 @@ impl<'a> ModifyInputsContext<'a> {
pub fn insert_vector(&mut self, subpaths: Vec<Subpath<PointId>>, layer: LayerNodeIdentifier, include_transform: bool, include_fill: bool, include_stroke: bool) {
let vector = Table::new_from_element(Vector::from_subpaths(subpaths, true));
let shape = resolve_document_node_type("Path")
let shape = resolve_network_node_type("Path")
.expect("Path node does not exist")
.node_template_input_override([Some(NodeInput::value(TaggedValue::Vector(vector), false))]);
let shape_id = NodeId::new();
@@ -164,21 +164,25 @@ impl<'a> ModifyInputsContext<'a> {
self.network_interface.move_node_to_chain_start(&shape_id, layer, &[]);
if include_transform {
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_node_template();
let transform = resolve_network_node_type("Transform").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, &[]);
}
if include_fill {
let fill = resolve_document_node_type("Fill").expect("Fill node does not exist").default_node_template();
let fill = resolve_proto_node_type(graphene_std::vector_nodes::fill::IDENTIFIER)
.expect("Fill node does not exist")
.default_node_template();
let fill_id = NodeId::new();
self.network_interface.insert_node(fill_id, fill, &[]);
self.network_interface.move_node_to_chain_start(&fill_id, layer, &[]);
}
if include_stroke {
let stroke = resolve_document_node_type("Stroke").expect("Stroke 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();
let stroke_id = NodeId::new();
self.network_interface.insert_node(stroke_id, stroke, &[]);
self.network_interface.move_node_to_chain_start(&stroke_id, layer, &[]);
@@ -186,21 +190,27 @@ impl<'a> ModifyInputsContext<'a> {
}
pub fn insert_text(&mut self, text: String, font: Font, typesetting: TypesettingConfig, layer: LayerNodeIdentifier) {
let stroke = resolve_document_node_type("Stroke").expect("Stroke node does not exist").default_node_template();
let fill = resolve_document_node_type("Fill").expect("Fill node does not exist").default_node_template();
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_node_template();
let text = resolve_document_node_type("Text").expect("Text node does not exist").node_template_input_override([
Some(NodeInput::scope("editor-api")),
Some(NodeInput::value(TaggedValue::String(text), false)),
Some(NodeInput::value(TaggedValue::Font(font), false)),
Some(NodeInput::value(TaggedValue::F64(typesetting.font_size), false)),
Some(NodeInput::value(TaggedValue::F64(typesetting.line_height_ratio), false)),
Some(NodeInput::value(TaggedValue::F64(typesetting.character_spacing), false)),
Some(NodeInput::value(TaggedValue::OptionalF64(typesetting.max_width), false)),
Some(NodeInput::value(TaggedValue::OptionalF64(typesetting.max_height), false)),
Some(NodeInput::value(TaggedValue::F64(typesetting.tilt), false)),
Some(NodeInput::value(TaggedValue::TextAlign(typesetting.align), false)),
]);
let stroke = resolve_proto_node_type(graphene_std::vector_nodes::stroke::IDENTIFIER)
.expect("Stroke node does not exist")
.default_node_template();
let fill = resolve_proto_node_type(graphene_std::vector_nodes::fill::IDENTIFIER)
.expect("Fill node does not exist")
.default_node_template();
let transform = resolve_network_node_type("Transform").expect("Transform node does not exist").default_node_template();
let text = resolve_proto_node_type(graphene_std::text::text::IDENTIFIER)
.expect("Text node does not exist")
.node_template_input_override([
Some(NodeInput::scope("editor-api")),
Some(NodeInput::value(TaggedValue::String(text), false)),
Some(NodeInput::value(TaggedValue::Font(font), false)),
Some(NodeInput::value(TaggedValue::F64(typesetting.font_size), false)),
Some(NodeInput::value(TaggedValue::F64(typesetting.line_height_ratio), false)),
Some(NodeInput::value(TaggedValue::F64(typesetting.character_spacing), false)),
Some(NodeInput::value(TaggedValue::OptionalF64(typesetting.max_width), false)),
Some(NodeInput::value(TaggedValue::OptionalF64(typesetting.max_height), false)),
Some(NodeInput::value(TaggedValue::F64(typesetting.tilt), false)),
Some(NodeInput::value(TaggedValue::TextAlign(typesetting.align), false)),
]);
let text_id = NodeId::new();
self.network_interface.insert_node(text_id, text, &[]);
@@ -220,8 +230,8 @@ impl<'a> ModifyInputsContext<'a> {
}
pub fn insert_image_data(&mut self, image_frame: Table<Raster<CPU>>, layer: LayerNodeIdentifier) {
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_node_template();
let image = resolve_document_node_type("Image Value")
let transform = resolve_network_node_type("Transform").expect("Transform node does not exist").default_node_template();
let image = resolve_proto_node_type(graphene_std::raster_nodes::std_nodes::image_value::IDENTIFIER)
.expect("ImageValue node does not exist")
.node_template_input_override([Some(NodeInput::value(TaggedValue::None, false)), Some(NodeInput::value(TaggedValue::Raster(image_frame), false))]);
@@ -245,18 +255,26 @@ impl<'a> ModifyInputsContext<'a> {
})
}
/// Gets the node id of a node with a specific reference that is upstream from the layer node, and optionally creates it if it does not exist.
/// The returned node is based on the selection dots in the layer. The right most dot will always insert/access the path that flows directly into the layer.
/// Each dot after that represents an existing path node. If there is an existing upstream node, then it will always be returned first.
pub fn existing_node_id(&mut self, reference_name: &'static str, create_if_nonexistent: bool) -> Option<NodeId> {
/// Gets the node id of a network node with a specific reference that is upstream from the layer node, and optionally creates it if it does not exist.
pub fn existing_network_node_id(&mut self, reference: &str, create_if_nonexistent: bool) -> Option<NodeId> {
self.existing_node_id(&DefinitionIdentifier::Network(reference.into()), create_if_nonexistent)
}
/// Gets the node id of a proto node with a specific reference that is upstream from the layer node, and optionally creates it if it does not exist.
pub fn existing_proto_node_id(&mut self, reference: ProtoNodeIdentifier, create_if_nonexistent: bool) -> Option<NodeId> {
self.existing_node_id(&DefinitionIdentifier::ProtoNode(reference), create_if_nonexistent)
}
/// Gets the node id of a document node with a specific reference that is upstream from the layer node, and optionally creates it if it does not exist.
fn existing_node_id(&mut self, reference: &DefinitionIdentifier, create_if_nonexistent: bool) -> Option<NodeId> {
// Start from the layer node or export
let output_layer = self.get_output_layer()?;
let existing_node_id = Self::locate_node_in_layer_chain(reference_name, output_layer, self.network_interface);
let existing_node_id = Self::locate_node_in_layer_chain(reference, output_layer, self.network_interface);
// Create a new node if the node does not exist and update its inputs
if create_if_nonexistent {
return existing_node_id.or_else(|| self.create_node(reference_name));
return existing_node_id.or_else(|| self.create_node(reference));
}
existing_node_id
@@ -265,16 +283,13 @@ impl<'a> ModifyInputsContext<'a> {
/// Gets the node id of a node with a specific reference (name) that is upstream (leftward) from the layer node, but before reaching another upstream layer stack.
/// For example, if given a parent layer, this would find a requested "Transform" or "Boolean Operation" node in its chain, between the parent layer and its layer stack child contents.
/// It would also travel up an entire layer that's not fed by a stack until reaching the generator node, such as a "Rectangle" or "Path" layer.
pub fn locate_node_in_layer_chain(reference_name: &str, left_of_layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
pub fn locate_node_in_layer_chain(reference: &DefinitionIdentifier, left_of_layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<NodeId> {
let upstream = network_interface.upstream_flow_back_from_nodes(vec![left_of_layer.to_node()], &[], network_interface::FlowType::HorizontalFlow);
// Look at all of the upstream nodes
for upstream_node in upstream {
// Check if this is the node we have been searching for.
if network_interface
.reference(&upstream_node, &[])
.is_some_and(|node_reference| *node_reference == Some(reference_name.to_string()))
{
if network_interface.reference(&upstream_node, &[]).is_some_and(|node_reference| node_reference == *reference) {
if !network_interface.is_visible(&upstream_node, &[]) {
continue;
}
@@ -293,10 +308,10 @@ impl<'a> ModifyInputsContext<'a> {
}
/// Create a new node inside the layer
pub fn create_node(&mut self, reference: &str) -> Option<NodeId> {
pub fn create_node(&mut self, reference: &DefinitionIdentifier) -> Option<NodeId> {
let output_layer = self.get_output_layer()?;
let Some(node_definition) = resolve_document_node_type(reference) else {
log::error!("Node type {reference} does not exist in ModifyInputsContext::existing_node_id");
log::error!("Node {reference:?} does not exist in ModifyInputsContext::existing_node_id");
return None;
};
@@ -305,7 +320,7 @@ impl<'a> ModifyInputsContext<'a> {
if node_definition.identifier == "Path" {
let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]);
if layer_input_type.compiled_nested_type() == Some(&concrete!(Table<Graphic>)) {
let Some(flatten_path_definition) = resolve_document_node_type("Flatten Path") else {
let Some(flatten_path_definition) = resolve_proto_node_type(graphene_std::vector_nodes::flatten_path::IDENTIFIER) else {
log::error!("Flatten Path does not exist in ModifyInputsContext::existing_node_id");
return None;
};
@@ -325,7 +340,9 @@ impl<'a> ModifyInputsContext<'a> {
let backup_color_index = 2;
let backup_gradient_index = 3;
let Some(fill_node_id) = self.existing_node_id("Fill", true) else { return };
let Some(fill_node_id) = self.existing_proto_node_id(graphene_std::vector_nodes::fill::IDENTIFIER, true) else {
return;
};
match &fill {
Fill::None => {
let input_connector = InputConnector::node(fill_node_id, backup_color_index);
@@ -345,32 +362,42 @@ impl<'a> ModifyInputsContext<'a> {
}
pub fn blend_mode_set(&mut self, blend_mode: BlendMode) {
let Some(blend_node_id) = self.existing_node_id("Blending", true) else { return };
let Some(blend_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::blending::IDENTIFIER, true) else {
return;
};
let input_connector = InputConnector::node(blend_node_id, 1);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::BlendMode(blend_mode), false), false);
}
pub fn opacity_set(&mut self, opacity: f64) {
let Some(blend_node_id) = self.existing_node_id("Blending", true) else { return };
let Some(blend_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::blending::IDENTIFIER, true) else {
return;
};
let input_connector = InputConnector::node(blend_node_id, 2);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(opacity * 100.), false), false);
}
pub fn blending_fill_set(&mut self, fill: f64) {
let Some(blend_node_id) = self.existing_node_id("Blending", true) else { return };
let Some(blend_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::blending::IDENTIFIER, true) else {
return;
};
let input_connector = InputConnector::node(blend_node_id, 3);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(fill * 100.), false), false);
}
pub fn clip_mode_toggle(&mut self, clip_mode: Option<bool>) {
let clip = !clip_mode.unwrap_or(false);
let Some(clip_node_id) = self.existing_node_id("Blending", true) else { return };
let Some(clip_node_id) = self.existing_proto_node_id(graphene_std::blending_nodes::blending::IDENTIFIER, true) else {
return;
};
let input_connector = InputConnector::node(clip_node_id, 4);
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Bool(clip), false), false);
}
pub fn stroke_set(&mut self, stroke: Stroke) {
let Some(stroke_node_id) = self.existing_node_id("Stroke", true) else { return };
let Some(stroke_node_id) = self.existing_proto_node_id(graphene_std::vector::stroke::IDENTIFIER, true) else {
return;
};
let stroke_color = if let Some(color) = stroke.color { Table::new_from_element(color) } else { Table::new() };
@@ -399,7 +426,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_node_id("Transform", false)
.existing_network_node_id("Transform", 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))
@@ -423,7 +450,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_node_id("Transform", false);
let transform_node_id = self.existing_network_node_id("Transform", false);
// Get a transform appropriate for the requested space
let to_transform = match transform_in {
@@ -448,7 +475,7 @@ impl<'a> ModifyInputsContext<'a> {
}
// Create the Transform node
self.existing_node_id("Transform", true)
self.existing_network_node_id("Transform", true)
}) else {
return;
};
@@ -464,19 +491,23 @@ impl<'a> ModifyInputsContext<'a> {
}
pub fn vector_modify(&mut self, modification_type: VectorModificationType) {
let Some(path_node_id) = self.existing_node_id("Path", true) else { return };
let Some(path_node_id) = self.existing_network_node_id("Path", true) else {
return;
};
self.network_interface.vector_modify(&path_node_id, modification_type);
self.responses.add(PropertiesPanelMessage::Refresh);
self.responses.add(NodeGraphMessage::RunDocumentGraph);
}
pub fn brush_modify(&mut self, strokes: Vec<BrushStroke>) {
let Some(brush_node_id) = self.existing_node_id("Brush", true) else { return };
let Some(brush_node_id) = self.existing_network_node_id("Brush", true) else {
return;
};
self.set_input_with_refresh(InputConnector::node(brush_node_id, 1), NodeInput::value(TaggedValue::BrushStrokes(strokes), false), false);
}
pub fn resize_artboard(&mut self, location: IVec2, dimensions: IVec2) {
let Some(artboard_node_id) = self.existing_node_id("Artboard", true) else {
let Some(artboard_node_id) = self.existing_network_node_id("Artboard", true) else {
return;
};
@@ -26,7 +26,8 @@ use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::transform::Footprint;
use graphene_std::vector::Vector;
use graphene_std::*;
use std::collections::{HashMap, HashSet, VecDeque};
use serde_json::Value;
use std::collections::{HashMap, VecDeque};
pub struct NodePropertiesContext<'a> {
pub persistent_data: &'a PersistentData,
@@ -54,10 +55,63 @@ impl NodePropertiesContext<'_> {
}
}
/// The key used to access definitions for a network node or proto node.
/// For proto nodes, this is their [`ProtoNodeIdentifier`].
/// For network nodes, it doesn't necessarily have to be the same as the network's display name, but it often is.
#[derive(Debug, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
#[serde(tag = "type", content = "data")]
pub enum DefinitionIdentifier {
ProtoNode(ProtoNodeIdentifier),
Network(String),
}
impl DefinitionIdentifier {
pub fn implementation_name_from_identifier(&self) -> String {
match self {
DefinitionIdentifier::Network(name) => name.clone(),
DefinitionIdentifier::ProtoNode(proto_node_identifier) => registry::NODE_METADATA
.lock()
.unwrap()
.get(proto_node_identifier)
.map(|metadata| metadata.display_name.to_string())
.unwrap_or_else(|| {
let mut last_segment = proto_node_identifier.as_str().split("::").last().unwrap_or_default().to_string();
last_segment = last_segment.strip_suffix("Node").unwrap_or(&last_segment).to_string();
last_segment
}),
}
}
}
impl From<Value> for DefinitionIdentifier {
fn from(value: Value) -> Self {
match value {
Value::Object(mut map) => {
let ty = map.remove("type").unwrap().as_str().unwrap().to_owned();
match ty.as_ref() {
"Network" => {
let data = map.remove("data").unwrap().as_str().unwrap().to_owned();
DefinitionIdentifier::Network(data)
}
"ProtoNode" => {
let value = map.remove("data").unwrap();
let proto: ProtoNodeIdentifier = serde_json::from_value(value).unwrap();
DefinitionIdentifier::ProtoNode(proto)
}
_ => panic!("Unknown `DefinitionIdentifier` type: {:?}", ty),
}
}
_ => panic!("Expected a JSON object to convert to `DefinitionIdentifier`"),
}
}
}
/// Acts as a description for a [DocumentNode] before it gets instantiated as one.
#[derive(Clone)]
#[derive(Debug, Clone)]
pub struct DocumentNodeDefinition {
/// Used by the reference field in [`DocumentNodeMetadata`] to prevent storing a copy of the implementation, if it is unchanged from the definition.
/// Used to create the [`DefinitionIdentifier::Network`] identifier.
pub identifier: &'static str,
/// All data required to construct a [`DocumentNode`] and [`DocumentNodeMetadata`]
@@ -75,14 +129,14 @@ pub struct DocumentNodeDefinition {
pub properties: Option<&'static str>,
}
// We use the once cell for lazy initialization to avoid the overhead of reconstructing the node list every time.
// TODO: make document nodes not require a `'static` lifetime to avoid having to split the construction into const and non-const parts.
static DOCUMENT_NODE_TYPES: once_cell::sync::Lazy<Vec<DocumentNodeDefinition>> = once_cell::sync::Lazy::new(static_nodes);
// We use the once_cell to use the document node definitions throughout the editor without passing a reference
// TODO: If dynamic node library is required, use a Mutex as well
static DOCUMENT_NODE_TYPES: once_cell::sync::Lazy<HashMap<DefinitionIdentifier, DocumentNodeDefinition>> = once_cell::sync::Lazy::new(document_node_definitions);
// TODO: Dynamic node library
/// Defines the "signature" or "header file"-like metadata for the document nodes, but not the implementation (which is defined in the node registry).
/// The [`DocumentNode`] is the instance while these [`DocumentNodeDefinition`]s are the "classes" or "blueprints" from which the instances are built.
fn static_nodes() -> Vec<DocumentNodeDefinition> {
/// 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 {
@@ -100,7 +154,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
},
description: Cow::Borrowed("Returns the input value without changing it. This is useful for rerouting wires for organization purposes."),
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
@@ -140,6 +194,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
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",
@@ -224,7 +279,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "To Graphic".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -3)),
..Default::default()
},
@@ -232,7 +286,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Wrap Graphic".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -1)),
..Default::default()
},
@@ -240,7 +293,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Source Node ID".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -1)),
..Default::default()
},
@@ -248,7 +300,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Monitor".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-7, -1)),
..Default::default()
},
@@ -256,7 +307,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Extend".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, -3)),
..Default::default()
},
@@ -375,7 +425,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Create Artboard".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -3)),
..Default::default()
},
@@ -383,7 +432,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Source Node ID".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-14, -3)),
..Default::default()
},
@@ -391,7 +439,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Monitor".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-7, -3)),
..Default::default()
},
@@ -399,7 +446,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Extend".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, -4)),
..Default::default()
},
@@ -989,7 +1035,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Load Resource".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
@@ -997,7 +1042,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Decode Image".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
@@ -1071,7 +1115,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Create Surface".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 2)),
..Default::default()
},
@@ -1079,7 +1122,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Cache".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 2)),
..Default::default()
},
@@ -1087,7 +1129,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Rasterize".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
..Default::default()
},
@@ -1108,12 +1149,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
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(raster_nodes::std_nodes::noise_pattern::IDENTIFIER),
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),
@@ -1228,7 +1270,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Extract Channel".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
@@ -1236,7 +1277,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Extract Channel".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 2)),
..Default::default()
},
@@ -1244,7 +1284,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Extract Channel".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 4)),
..Default::default()
},
@@ -1252,7 +1291,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Extract Channel".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 6)),
..Default::default()
},
@@ -1312,7 +1350,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Extract XY".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
@@ -1320,7 +1357,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Extract XY".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 2)),
..Default::default()
},
@@ -1382,7 +1418,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Brush".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
@@ -1402,6 +1437,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("TODO"),
properties: None,
},
// TODO: Auto-generate this from its proto node macro
DocumentNodeDefinition {
identifier: "Memoize",
category: "Debug",
@@ -1463,7 +1499,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Extract Executor".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-7, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
@@ -1471,20 +1513,11 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Upload Texture".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Cache".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
@@ -1592,7 +1625,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Monitor".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
@@ -1600,7 +1632,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Path Modify".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
@@ -1621,6 +1652,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("TODO"),
properties: None,
},
// TODO: Auto-generate this from its proto node macro
DocumentNodeDefinition {
identifier: "Text",
category: "Text",
@@ -1788,7 +1820,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Monitor".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
@@ -1796,15 +1827,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Transform".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
input_metadata: vec![
("Value", "TODO").into(),
("Translation", "TODO").into(),
("Rotation", "TODO").into(),
("Scale", "TODO").into(),
("Skew", "TODO").into(),
],
..Default::default()
},
..Default::default()
@@ -1891,7 +1914,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Boolean Operation".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
@@ -1899,7 +1921,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Memoize".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
@@ -1981,7 +2002,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Subpath Segment Lengths".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 7)),
..Default::default()
},
@@ -1989,7 +2009,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Sample Polyline".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
@@ -1997,7 +2016,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Memoize".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
..Default::default()
},
@@ -2104,7 +2122,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Poisson-Disk Points".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
@@ -2112,7 +2129,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Memoize".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
@@ -2637,102 +2653,48 @@ fn static_input_properties() -> InputProperties {
map
}
pub fn resolve_document_node_type(identifier: &str) -> Option<&DocumentNodeDefinition> {
DOCUMENT_NODE_TYPES.iter().find(|definition| definition.identifier == identifier)
pub fn resolve_network_node_type(identifier: &str) -> Option<&'static DocumentNodeDefinition> {
resolve_document_node_type(&DefinitionIdentifier::Network(identifier.into()))
}
pub fn resolve_proto_node_type(identifier: ProtoNodeIdentifier) -> Option<&'static DocumentNodeDefinition> {
resolve_document_node_type(&DefinitionIdentifier::ProtoNode(identifier))
}
pub fn resolve_document_node_type(identifier: &DefinitionIdentifier) -> Option<&'static DocumentNodeDefinition> {
DOCUMENT_NODE_TYPES.get(identifier)
}
pub fn collect_node_types() -> Vec<FrontendNodeType> {
// Create a mapping from registry ID to document node identifier
let id_to_identifier_map: HashMap<ProtoNodeIdentifier, &'static str> = DOCUMENT_NODE_TYPES
DOCUMENT_NODE_TYPES
.iter()
.filter_map(|definition| {
if let DocumentNodeImplementation::ProtoNode(name) = &definition.node_template.document_node.implementation {
Some((name.clone(), definition.identifier))
} else {
None
}
})
.collect();
let mut extracted_node_types = Vec::new();
let node_registry = registry::NODE_REGISTRY.lock().unwrap();
let node_metadata = registry::NODE_METADATA.lock().unwrap();
for (id, metadata) in node_metadata.iter() {
if let Some(implementations) = node_registry.get(id) {
let identifier = match id_to_identifier_map.get(id) {
Some(&id) => id,
None => continue,
};
// Extract category from metadata (already creates an owned String)
let category = metadata.category.unwrap_or_default();
// Extract input types (already creates owned Strings)
let input_types = implementations
.iter()
.flat_map(|(_, node_io)| node_io.inputs.iter().map(|ty| ty.nested_type().to_cow_string()))
.collect::<HashSet<Cow<'static, str>>>()
.into_iter()
.collect::<Vec<Cow<'static, str>>>();
// Create a FrontendNodeType
let node_type = FrontendNodeType::with_input_types(identifier, category, input_types);
// Store the created node_type
extracted_node_types.push(node_type);
}
}
let node_types: Vec<FrontendNodeType> = DOCUMENT_NODE_TYPES
.iter()
.filter(|definition| !definition.category.is_empty())
.map(|definition| {
.filter(|(_, definition)| !definition.category.is_empty())
.map(|(identifier, definition)| {
let input_types = definition
.node_template
.document_node
.inputs
.iter()
.filter_map(|node_input| node_input.as_value().map(|node_value| node_value.ty().nested_type().to_cow_string()))
.collect::<Vec<Cow<'static, str>>>();
FrontendNodeType::with_input_types(definition.identifier, definition.category, input_types)
})
.collect();
// Update categories in extracted_node_types from node_types
for extracted_node in &mut extracted_node_types {
if extracted_node.category.is_empty() {
// Find matching node in node_types and update category if found
if let Some(matching_node) = node_types.iter().find(|node_type| node_type.name == extracted_node.name) {
extracted_node.category = matching_node.category.clone();
.map(|node_input| node_input.as_value().map(|node_value| node_value.ty().nested_type().to_string()).unwrap_or_default())
.collect::<Vec<String>>();
let mut name = definition.node_template.persistent_node_metadata.display_name.clone();
if name.is_empty() {
name = identifier.implementation_name_from_identifier()
}
}
}
let missing_nodes: Vec<FrontendNodeType> = node_types
.iter()
.filter(|node| !extracted_node_types.iter().any(|extracted| extracted.name == node.name))
.cloned()
.collect();
// Add the missing nodes to extracted_node_types
for node in missing_nodes {
extracted_node_types.push(node);
}
// Remove entries with empty categories
extracted_node_types.retain(|node| !node.category.is_empty());
extracted_node_types
FrontendNodeType {
identifier: identifier.clone(),
name,
category: definition.category.to_string(),
input_types,
}
})
.collect()
}
pub fn collect_node_descriptions() -> Vec<(String, String)> {
pub fn collect_node_descriptions() -> Vec<(DefinitionIdentifier, String)> {
DOCUMENT_NODE_TYPES
.iter()
.map(|definition| {
(
definition.identifier.to_string(),
if definition.description != "TODO" { definition.description.to_string() } else { String::new() },
)
})
.map(|(identifier, definition)| (identifier.clone(), if definition.description != "TODO" { definition.description.to_string() } else { String::new() }))
.collect()
}
@@ -2744,8 +2706,9 @@ impl DocumentNodeDefinition {
// TODO: Replace the .enumerate() with changing the iterator to take a tuple of (index, input) so the user is forced to provide the correct index
input_override.into_iter().enumerate().for_each(|(index, input_override)| {
if let Some(input_override) = input_override {
// Only value inputs can be overridden, since node inputs change graph structure and must be handled by the network interface
// assert!(matches!(input_override, NodeInput::Value { .. }), "Only value inputs are supported for input overrides");
// Only value inputs should be overridden, since node inputs change graph structure and must be handled by the network interface.
// However, this would require changing some tooling which creates multiple nodes at once, before they are inserted into the network.
// debug_assert!(input_override.as_node().is_none(), "Node inputs are not supported in input overrides");
template.document_node.inputs[index] = input_override;
}
});
@@ -2806,12 +2769,6 @@ impl DocumentNodeDefinition {
}
populate_input_properties(&mut template, Vec::new());
// Set the reference to the node definition
template.persistent_node_metadata.reference = Some(self.identifier.to_string());
// If the display name is empty and it is not a merge node, then set it to the reference
if template.persistent_node_metadata.display_name.is_empty() && self.identifier != "Merge" {
template.persistent_node_metadata.display_name = self.identifier.to_string();
}
template
}
@@ -1,72 +1,34 @@
use super::DocumentNodeDefinition;
use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, InputMetadata, NodeTemplate, WidgetOverride};
use graph_craft::ProtoNodeIdentifier;
use graph_craft::document::*;
use graphene_std::registry::*;
use graphene_std::*;
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
/// Traverses a document node template and metadata in parallel to link the protonodes to their reference
fn traverse_node(node: &DocumentNode, node_metadata: &mut DocumentNodePersistentMetadata) {
match &node.implementation {
DocumentNodeImplementation::Network(node_network) => {
for (nested_node_id, nested_node) in node_network.nodes.iter() {
let nested_metadata = node_metadata
.network_metadata
.as_mut()
.expect("Network node must have network metadata")
.persistent_metadata
.node_metadata
.get_mut(nested_node_id)
.expect("Network metadata must have corresponding node id");
traverse_node(nested_node, &mut nested_metadata.persistent_metadata);
}
}
DocumentNodeImplementation::ProtoNode(proto_node_identifier) => {
if let Some(metadata) = NODE_METADATA.lock().unwrap().get(proto_node_identifier) {
node_metadata.reference = Some(metadata.display_name.to_string());
}
}
DocumentNodeImplementation::Extract => {}
}
}
pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec<DocumentNodeDefinition> {
// Link the protonodes with custom networks to their reference
for node in custom.iter_mut() {
traverse_node(&node.node_template.document_node, &mut node.node_template.persistent_node_metadata);
}
// Remove struct generics
for DocumentNodeDefinition { node_template, .. } in custom.iter_mut() {
let NodeTemplate {
document_node: DocumentNode { implementation, .. },
..
} = node_template;
if let DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) = implementation
&& let Some((new_name, _suffix)) = name.rsplit_once("<")
{
*name = Cow::Owned(new_name.to_string())
};
}
pub(super) fn post_process_nodes(custom: Vec<DocumentNodeDefinition>) -> HashMap<DefinitionIdentifier, DocumentNodeDefinition> {
// Create hashmap for the protonodes added by the macro.
let mut definitions_map = HashMap::new();
// First remove the custom protonodes and add them to the definitions map since they contain different metadata
// from the macro and must be inserted first so that network nodes which reference them use the correct metadata.
let network_nodes = custom
.into_iter()
.filter_map(|definition| {
if let DocumentNodeImplementation::ProtoNode(proto_node_identifier) = &definition.node_template.document_node.implementation {
definitions_map.insert(DefinitionIdentifier::ProtoNode(proto_node_identifier.clone()), definition);
return None;
};
Some(definition)
})
.collect::<Vec<_>>();
// Add the rest of the protonodes from the macro
let node_registry = NODE_REGISTRY.lock().unwrap();
'outer: for (id, metadata) in NODE_METADATA.lock().unwrap().iter() {
for node in custom.iter() {
let DocumentNodeDefinition {
node_template: NodeTemplate {
document_node: DocumentNode { implementation, .. },
..
},
..
} = node;
match implementation {
DocumentNodeImplementation::ProtoNode(name) if name == id => continue 'outer,
_ => (),
}
}
for (id, metadata) in NODE_METADATA.lock().unwrap().iter() {
let identifier = DefinitionIdentifier::ProtoNode(id.clone());
if definitions_map.contains_key(&identifier) {
continue;
};
let NodeMetadata {
display_name,
category,
@@ -78,48 +40,96 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
let Some(implementations) = &node_registry.get(id) else { continue };
let valid_inputs: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect();
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 output_type = &first_node_io.return_value;
let inputs = preprocessor::node_inputs(fields, first_node_io);
let node = DocumentNodeDefinition {
identifier: display_name,
node_template: NodeTemplate {
document_node: DocumentNode {
inputs,
call_argument: input_type.clone(),
implementation: DocumentNodeImplementation::ProtoNode(id.clone()),
visible: true,
skip_deduplication: false,
context_features: ContextDependencies::from(context_features.as_slice()),
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
// TODO: Store information for input overrides in the node macro
input_metadata: fields
.iter()
.map(|f| match f.widget_override {
RegistryWidgetOverride::None => (f.name, f.description).into(),
RegistryWidgetOverride::Hidden => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::Hidden),
RegistryWidgetOverride::String(str) => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::String(str.to_string())),
RegistryWidgetOverride::Custom(str) => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::Custom(str.to_string())),
})
.collect(),
output_names: vec![output_type.to_string()],
locked: false,
..Default::default()
definitions_map.insert(
identifier,
DocumentNodeDefinition {
identifier: display_name,
node_template: NodeTemplate {
document_node: DocumentNode {
inputs,
call_argument: input_type.clone(),
implementation: DocumentNodeImplementation::ProtoNode(id.clone()),
visible: true,
skip_deduplication: false,
context_features: ContextDependencies::from(context_features.as_slice()),
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
// TODO: Store information for input overrides in the node macro
input_metadata: fields
.iter()
.map(|f| match f.widget_override {
RegistryWidgetOverride::None => (f.name, f.description).into(),
RegistryWidgetOverride::Hidden => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::Hidden),
RegistryWidgetOverride::String(str) => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::String(str.to_string())),
RegistryWidgetOverride::Custom(str) => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::Custom(str.to_string())),
})
.collect(),
locked: false,
..Default::default()
},
},
category: category.unwrap_or("UNCATEGORIZED"),
description: Cow::Borrowed(description),
properties: *properties,
},
category: category.unwrap_or("UNCATEGORIZED"),
description: Cow::Borrowed(description),
properties: *properties,
};
custom.push(node);
);
}
custom
// 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);
// Set the reference to the node identifier
if let Some(nested_metadata) = network_node.node_template.persistent_node_metadata.network_metadata.as_mut() {
nested_metadata.persistent_metadata.reference = Some(network_node.identifier.to_string());
// If it is not a merge node, then set the display name to the identifier/reference
if network_node.identifier != "Merge" {
network_node.node_template.persistent_node_metadata.display_name = network_node.identifier.to_string();
}
}
definitions_map.insert(DefinitionIdentifier::Network(network_node.identifier.to_string()), network_node);
}
definitions_map
}
/// Traverses a document node template and metadata in parallel to add metadata to the protonodes
fn traverse_node(node: &DocumentNode, node_metadata: &mut DocumentNodePersistentMetadata, definitions_map: &HashMap<DefinitionIdentifier, DocumentNodeDefinition>) {
match &node.implementation {
DocumentNodeImplementation::Network(node_network) => {
for (nested_node_id, nested_node) in node_network.nodes.iter() {
let nested_metadata = node_metadata.network_metadata.as_mut().unwrap().persistent_metadata.node_metadata.get_mut(nested_node_id).unwrap();
traverse_node(nested_node, &mut nested_metadata.persistent_metadata, definitions_map);
}
}
DocumentNodeImplementation::ProtoNode(id) => {
// Set all the metadata except the position to the proto node information from the macro
// TODO: Use options in the template to specify what you want to default and what you want to override
// If this fails then the proto node id in the definition doesn't match what is generated by the macro
let Some(definition) = definitions_map.get(&DefinitionIdentifier::ProtoNode(id.clone())) else {
// log::error!("Could not get definition for id {} when filling in protonode metadata for a custom node", id.clone());
return;
};
let mut new_metadata = definition.node_template.persistent_node_metadata.clone();
new_metadata.node_type_metadata = node_metadata.node_type_metadata.clone();
*node_metadata = new_metadata
}
DocumentNodeImplementation::Extract => {}
}
}
@@ -1,5 +1,6 @@
use super::utility_types::Direction;
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{ImportOrExport, InputConnector, NodeTemplate, OutputConnector};
use crate::messages::prelude::*;
@@ -27,16 +28,16 @@ pub enum NodeGraphMessage {
SelectedNodesUpdated,
Copy,
CreateNodeInLayerNoTransaction {
node_type: String,
node_type: DefinitionIdentifier,
layer: LayerNodeIdentifier,
},
CreateNodeInLayerWithTransaction {
node_type: String,
node_type: DefinitionIdentifier,
layer: LayerNodeIdentifier,
},
CreateNodeFromContextMenu {
node_id: Option<NodeId>,
node_type: String,
node_type: DefinitionIdentifier,
xy: Option<(i32, i32)>,
add_transaction: bool,
},
@@ -1,12 +1,14 @@
use super::node_properties;
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendNode};
use super::{document_node_definitions, node_properties};
use crate::consts::GRID_SIZE;
use crate::messages::clipboard::utility_types::ClipboardContent;
use crate::messages::input_mapper::utility_types::macros::{action_shortcut, action_shortcut_manual};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::document_message_handler::navigation_controls;
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;
use crate::messages::portfolio::document::node_graph::document_node_definitions::NodePropertiesContext;
use crate::messages::portfolio::document::node_graph::document_node_definitions::{
DefinitionIdentifier, NodePropertiesContext, resolve_document_node_type, resolve_network_node_type, resolve_proto_node_type,
};
use crate::messages::portfolio::document::node_graph::utility_types::{ContextMenuData, Direction, FrontendGraphDataType, NodeGraphErrorDiagnostic};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::misc::GroupFolderType;
@@ -131,7 +133,10 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
}
NodeGraphMessage::AddPathNode => {
if let Some(layer) = make_path_editable_is_allowed(network_interface) {
responses.add(NodeGraphMessage::CreateNodeInLayerWithTransaction { node_type: "Path".to_string(), layer });
responses.add(NodeGraphMessage::CreateNodeInLayerWithTransaction {
node_type: DefinitionIdentifier::Network("Path".into()),
layer,
});
responses.add(EventMessage::SelectionChanged);
}
}
@@ -208,7 +213,7 @@ 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(document_node_definitions::resolve_document_node_type("Passthrough").unwrap().default_node_template());
let node_template = Box::new(resolve_proto_node_type(graphene_core::ops::identity::IDENTIFIER).unwrap().default_node_template());
let node_id = NodeId::new();
responses.add(NodeGraphMessage::InsertNode { node_id, node_template });
@@ -274,10 +279,10 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
let node_id = node_id.unwrap_or_else(NodeId::new);
let Some(document_node_type) = document_node_definitions::resolve_document_node_type(&node_type) else {
let Some(document_node_type) = resolve_document_node_type(&node_type) else {
responses.add(DialogMessage::DisplayDialogError {
title: "Cannot insert node".to_string(),
description: format!("The document node '{node_type}' does not exist in the document node list"),
description: format!("The document node '{node_type:?}' does not exist in the document node list"),
});
return;
};
@@ -639,9 +644,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
// Use the network interface to add a default node, then set the imports, exports, paste the nodes inside, and connect them to the imports/exports
let encapsulating_node_id = NodeId::new();
let mut default_node_template = document_node_definitions::resolve_document_node_type("Default Network")
.expect("Default Network node should exist")
.default_node_template();
let mut default_node_template = resolve_network_node_type("Default Network").expect("Default Network node should exist").default_node_template();
let Some(center_of_selected_nodes) = network_interface.selected_nodes_bounding_box(breadcrumb_network_path).map(|[a, b]| (a + b) / 2.) else {
log::error!("Could not get center of selected_nodes");
return;
@@ -1701,7 +1704,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
input,
});
responses.add(PropertiesPanelMessage::Refresh);
if !(network_interface.reference(&node_id, selection_network_path).is_none() || input_index == 0) && network_interface.connected_to_output(&node_id, selection_network_path) {
if network_interface.connected_to_output(&node_id, selection_network_path) {
responses.add(NodeGraphMessage::RunDocumentGraph);
}
}
@@ -2596,8 +2599,9 @@ impl NodeGraphMessageHandler {
.node_metadata(&node_id, breadcrumb_network_path)
.is_some_and(|node_metadata| node_metadata.persistent_metadata.is_layer()),
can_be_layer: network_interface.is_eligible_to_be_layer(&node_id, breadcrumb_network_path),
reference: network_interface.reference(&node_id, breadcrumb_network_path).cloned().unwrap_or_default(),
reference: network_interface.reference(&node_id, breadcrumb_network_path),
display_name: network_interface.display_name(&node_id, breadcrumb_network_path),
implementation_name: network_interface.implementation_name(&node_id, breadcrumb_network_path),
primary_input,
exposed_inputs,
primary_output,
@@ -2711,10 +2715,16 @@ impl NodeGraphMessageHandler {
}
});
let Some(reference) = network_interface.reference(&node_id, &[]) else {
log::error!("Could not get reference for layer {node_id} in update_layer_panel");
continue;
};
let clippable = layer.can_be_clipped(network_interface.document_metadata());
let data = LayerPanelEntry {
id: node_id,
reference: network_interface.reference(&node_id, &[]).and_then(|x| x.as_ref()).cloned().unwrap_or_default(),
reference,
alias: network_interface.display_name(&node_id, &[]),
in_selected_network: selection_network_path.is_empty(),
children_allowed,
@@ -3,6 +3,7 @@
use super::document_node_definitions::{NODE_OVERRIDES, NodePropertiesContext};
use super::utility_types::FrontendGraphDataType;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
use crate::messages::portfolio::document::utility_types::network_interface::InputConnector;
use crate::messages::portfolio::utility_types::{FontCatalogStyle, PersistentData};
use crate::messages::prelude::*;
@@ -1705,10 +1706,8 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper
if let Some(properties_override) = context
.network_interface
.reference(&node_id, context.selection_network_path)
.cloned()
.unwrap_or_default()
.as_ref()
.and_then(|reference| super::document_node_definitions::resolve_document_node_type(reference))
.and_then(|identifier| resolve_document_node_type(identifier))
.and_then(|definition| definition.properties)
.and_then(|properties| NODE_OVERRIDES.get(properties))
{
@@ -1773,25 +1772,20 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper
if layout.is_empty() {
layout = node_no_properties(node_id, context);
}
let name = context
let name = context.network_interface.implementation_name(&node_id, context.selection_network_path);
let description = context
.network_interface
.reference(&node_id, context.selection_network_path)
.cloned()
.unwrap_or_default() // If there is an error getting the reference, default to empty string
.or_else(|| {
// If there is no reference, try to get the proto node name
context.network_interface.implementation(&node_id, context.selection_network_path).and_then(|implementation|{
if let DocumentNodeImplementation::ProtoNode(protonode) = implementation {
Some(protonode.name.clone().into_owned())
} else {
None
}
})
})
.unwrap_or("Custom Node".to_string());
let description = context.network_interface.description(&node_id, context.selection_network_path);
.as_ref()
.and_then(|identifier| resolve_document_node_type(identifier))
.map(|definition| definition.description.to_string())
.filter(|string| string != "TODO")
.unwrap_or_default();
let visible = context.network_interface.is_visible(&node_id, context.selection_network_path);
let pinned = context.network_interface.is_pinned(&node_id, context.selection_network_path);
LayoutGroup::Section {
name,
description,
@@ -1,8 +1,8 @@
use super::document_node_definitions::DefinitionIdentifier;
use glam::{DVec2, IVec2};
use graph_craft::document::NodeId;
use graph_craft::document::value::TaggedValue;
use graphene_std::Type;
use std::borrow::Cow;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum FrontendGraphDataType {
@@ -79,9 +79,11 @@ pub struct FrontendNode {
pub is_layer: bool,
#[serde(rename = "canBeLayer")]
pub can_be_layer: bool,
pub reference: Option<String>,
pub reference: Option<DefinitionIdentifier>,
#[serde(rename = "displayName")]
pub display_name: String,
#[serde(rename = "implementationName")]
pub implementation_name: String,
#[serde(rename = "primaryInput")]
pub primary_input: Option<FrontendGraphInput>,
#[serde(rename = "exposedInputs")]
@@ -102,29 +104,13 @@ pub struct FrontendNode {
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNodeType {
pub name: Cow<'static, str>,
pub category: Cow<'static, str>,
pub identifier: DefinitionIdentifier,
pub name: String,
pub category: String,
#[serde(rename = "inputTypes")]
pub input_types: Option<Vec<Cow<'static, str>>>,
pub input_types: Vec<String>,
}
impl FrontendNodeType {
pub fn new(name: impl Into<Cow<'static, str>>, category: impl Into<Cow<'static, str>>) -> Self {
Self {
name: name.into(),
category: category.into(),
input_types: None,
}
}
pub fn with_input_types(name: impl Into<Cow<'static, str>>, category: impl Into<Cow<'static, str>>, input_types: Vec<Cow<'static, str>>) -> Self {
Self {
name: name.into(),
category: category.into(),
input_types: Some(input_types),
}
}
}
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct DragStart {
pub start_x: f64,
@@ -1,6 +1,7 @@
use super::network_interface::NodeNetworkInterface;
use crate::messages::portfolio::document::graph_operation::transform_utils;
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;
use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::FlowType;
use crate::messages::tool::common_functionality::graph_modification_utils;
use glam::{DAffine2, DVec2};
@@ -91,7 +92,8 @@ impl DocumentMetadata {
let mut use_local = true;
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface);
if let Some(path_node) = graph_layer.upstream_visible_node_id_from_name_in_layer("Path")
let identifier = DefinitionIdentifier::Network("Path".into());
if let Some(path_node) = graph_layer.upstream_visible_node_id_from_name_in_layer(&identifier)
&& let Some(&source) = self.first_element_source_ids.get(&layer.to_node())
&& !network_interface
.upstream_flow_back_from_nodes(vec![path_node], &[], FlowType::HorizontalFlow)
@@ -114,7 +116,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("Transform", layer, network_interface);
let transform_node_id = ModifyInputsContext::locate_node_in_layer_chain(&DefinitionIdentifier::Network("Transform".into()), 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()
});
@@ -7,7 +7,7 @@ use super::misc::PTZ;
use super::nodes::SelectedNodes;
use crate::consts::{EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP, EXPORTS_TO_TOP_EDGE_PIXEL_GAP, GRID_SIZE, IMPORTS_TO_LEFT_EDGE_PIXEL_GAP, IMPORTS_TO_TOP_EDGE_PIXEL_GAP};
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;
use crate::messages::portfolio::document::node_graph::document_node_definitions::{DocumentNodeDefinition, resolve_document_node_type};
use crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_document_node_type};
use crate::messages::portfolio::document::node_graph::utility_types::{Direction, FrontendClickTargets, FrontendGraphDataType, FrontendGraphInput, FrontendGraphOutput};
use crate::messages::portfolio::document::overlays::utility_functions::text_width;
use crate::messages::portfolio::document::utility_types::network_interface::resolved_types::ResolvedDocumentNodeTypes;
@@ -79,7 +79,7 @@ impl NodeNetworkInterface {
fix_network(network);
}
if let DocumentNodeImplementation::ProtoNode(protonode) = &node.implementation
&& protonode.name.contains("PathModifyNode")
&& protonode.as_str().contains("PathModifyNode")
&& node.inputs.len() < 3
{
node.inputs.push(NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath));
@@ -470,12 +470,6 @@ impl NodeNetworkInterface {
node_template
}
/// Try and get the [`DocumentNodeDefinition`] for a node
pub fn get_node_definition(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeDefinition> {
let metadata = self.node_metadata(node_id, network_path)?;
resolve_document_node_type(metadata.persistent_metadata.reference.as_ref()?)
}
pub fn input_from_connector(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<&NodeInput> {
let Some(network) = self.nested_network(network_path) else {
log::error!("Could not get network in input_from_connector");
@@ -916,12 +910,30 @@ impl NodeNetworkInterface {
}
}
pub fn reference(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&Option<String>> {
let Some(node_metadata) = self.node_metadata(node_id, network_path) else {
log::error!("Could not get reference for node: {node_id:?}");
pub fn reference(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<DefinitionIdentifier> {
let Some(document_node) = self.document_node(node_id, network_path) else {
log::error!("Could not get document_node for node in reference: {node_id:?}");
return None;
};
Some(&node_metadata.persistent_metadata.reference)
match &document_node.implementation {
DocumentNodeImplementation::Network(_) => {
let Some(node_metadata) = self.node_metadata(node_id, network_path) else {
log::error!("Could not get reference for node in reference: {node_id:?}");
return None;
};
node_metadata
.persistent_metadata
.network_metadata
.as_ref()
.expect("Network metadata must exist for network node in reference")
.persistent_metadata
.reference
.clone()
.map(DefinitionIdentifier::Network)
}
DocumentNodeImplementation::ProtoNode(protonode_id) => Some(DefinitionIdentifier::ProtoNode(protonode_id.clone())),
_ => None,
}
}
pub fn implementation(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeImplementation> {
@@ -982,11 +994,6 @@ impl NodeNetworkInterface {
pub fn display_name(&self, node_id: &NodeId, network_path: &[NodeId]) -> String {
let is_layer = self.is_layer(node_id, network_path);
let Some(reference) = self.reference(node_id, network_path) else {
log::error!("Could not get reference in untitled_layer_label");
return "".to_string();
};
let display_name = if let Some(node_metadata) = self.node_metadata(node_id, network_path) {
node_metadata.persistent_metadata.display_name.clone()
} else {
@@ -998,18 +1005,19 @@ impl NodeNetworkInterface {
if is_layer {
"Untitled Layer".to_string()
} else {
reference.clone().unwrap_or("Untitled Node".to_string())
// TODO: Have this displayed in italics in the UI
self.implementation_name(node_id, network_path)
}
} else {
display_name
}
}
/// Returns the description of the node, or an empty string if it is not set.
pub fn description(&self, node_id: &NodeId, network_path: &[NodeId]) -> String {
self.get_node_definition(node_id, network_path)
.map(|node_definition| node_definition.description.to_string())
.unwrap_or_default()
/// The uneditable name in the Properties panel which represents the function name of the node implementation.
pub fn implementation_name(&self, node_id: &NodeId, network_path: &[NodeId]) -> String {
self.reference(node_id, network_path)
.map(|identifier| identifier.implementation_name_from_identifier())
.unwrap_or("Custom Node".to_string())
}
pub fn is_locked(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
@@ -1125,7 +1133,7 @@ impl NodeNetworkInterface {
pub fn is_artboard(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
self.reference(node_id, network_path)
.is_some_and(|reference| *reference == Some("Artboard".to_string()) && self.connected_to_output(node_id, &[]))
.is_some_and(|reference| reference == DefinitionIdentifier::Network("Artboard".into()) && self.connected_to_output(node_id, &[]))
}
pub fn all_artboards(&self) -> HashSet<LayerNodeIdentifier> {
@@ -1134,12 +1142,13 @@ impl NodeNetworkInterface {
.node_metadata
.iter()
.filter_map(|(node_id, node_metadata)| {
if node_metadata
.persistent_metadata
.reference
.as_ref()
.is_some_and(|reference| reference == "Artboard" && self.connected_to_output(node_id, &[]) && self.is_layer(node_id, &[]))
{
if node_metadata.persistent_metadata.network_metadata.as_ref().is_some_and(|network_metadata| {
network_metadata
.persistent_metadata
.reference
.as_ref()
.is_some_and(|reference| reference == "Artboard" && self.connected_to_output(node_id, &[]) && self.is_layer(node_id, &[]))
}) {
Some(LayerNodeIdentifier::new(*node_id, self))
} else {
None
@@ -1345,7 +1354,6 @@ impl NodeNetworkInterface {
node.skip_deduplication = old_node.skip_deduplication;
node.original_location = old_node.original_location;
node_metadata.persistent_metadata.display_name = old_node.alias;
node_metadata.persistent_metadata.reference = if old_node.name.is_empty() { None } else { Some(old_node.name) };
node_metadata.persistent_metadata.locked = old_node.locked;
node_metadata.persistent_metadata.node_type_metadata = if old_node.is_layer {
NodeTypePersistentMetadata::Layer(LayerPersistentMetadata {
@@ -3047,7 +3055,7 @@ impl NodeNetworkInterface {
pub fn compute_modified_vector(&self, layer: LayerNodeIdentifier) -> Option<Vector> {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, self);
if let Some(path_node) = graph_layer.upstream_visible_node_id_from_name_in_layer("Path")
if let Some(path_node) = graph_layer.upstream_visible_node_id_from_name_in_layer(&DefinitionIdentifier::Network("Path".into()))
&& let Some(vector) = self.document_metadata.vector_modify.get(&path_node)
{
let mut modified = vector.clone();
@@ -3200,14 +3208,6 @@ impl NodeNetworkInterface {
}
}
pub fn set_reference(&mut self, node_id: &NodeId, network_path: &[NodeId], reference: Option<String>) {
let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {
log::error!("Could not get node_metadata in set_reference");
return;
};
node_metadata.persistent_metadata.reference = reference;
}
pub fn set_transform(&mut self, transform: DAffine2, network_path: &[NodeId]) {
let Some(network_metadata) = self.network_metadata_mut(network_path) else {
log::error!("Could not get nested network in set_transform");
@@ -3278,7 +3278,9 @@ impl NodeNetworkInterface {
encapsulating_node_metadata.persistent_metadata.output_names.insert(insert_index as usize, output_name.to_string());
}
// Clear the reference to the nodes definition
encapsulating_node_metadata.persistent_metadata.reference = None;
if let Some(network_metadata) = encapsulating_node_metadata.persistent_metadata.network_metadata.as_mut() {
network_metadata.persistent_metadata.reference = None
}
};
// Update the export ports and outward wires for the current network
@@ -3347,7 +3349,9 @@ impl NodeNetworkInterface {
}
// Clear the reference to the nodes definition
node_metadata.persistent_metadata.reference = None;
if let Some(network_metadata) = node_metadata.persistent_metadata.network_metadata.as_mut() {
network_metadata.persistent_metadata.reference = None
}
// Update the metadata for the encapsulating node
self.unload_node_click_targets(&node_id, &encapsulating_network_path);
@@ -3403,7 +3407,9 @@ impl NodeNetworkInterface {
return;
};
encapsulating_node_metadata.persistent_metadata.output_names.remove(export_index);
encapsulating_node_metadata.persistent_metadata.reference = None;
if let Some(network_metadata) = encapsulating_node_metadata.persistent_metadata.network_metadata.as_mut() {
network_metadata.persistent_metadata.reference = None;
}
// Update the metadata for the encapsulating node
self.unload_outward_wires(&encapsulating_network_path);
@@ -3477,7 +3483,9 @@ impl NodeNetworkInterface {
return;
};
encapsulating_node_metadata.persistent_metadata.input_metadata.remove(import_index);
encapsulating_node_metadata.persistent_metadata.reference = None;
if let Some(network_metadata) = encapsulating_node_metadata.persistent_metadata.network_metadata.as_mut() {
network_metadata.persistent_metadata.reference = None;
}
// Update the metadata for the encapsulating node
self.unload_outward_wires(encapsulating_network_path);
@@ -3523,7 +3531,9 @@ impl NodeNetworkInterface {
let name = encapsulating_node_metadata.persistent_metadata.output_names.remove(start_index);
encapsulating_node_metadata.persistent_metadata.output_names.insert(end_index, name);
encapsulating_node_metadata.persistent_metadata.reference = None;
if let Some(network_metadata) = encapsulating_node_metadata.persistent_metadata.network_metadata.as_mut() {
network_metadata.persistent_metadata.reference = None;
}
// Update the metadata for the encapsulating network
self.unload_outward_wires(&encapsulating_network_path);
@@ -3615,7 +3625,9 @@ impl NodeNetworkInterface {
let properties_row = encapsulating_node_metadata.persistent_metadata.input_metadata.remove(start_index);
encapsulating_node_metadata.persistent_metadata.input_metadata.insert(end_index, properties_row);
encapsulating_node_metadata.persistent_metadata.reference = None;
if let Some(network_metadata) = encapsulating_node_metadata.persistent_metadata.network_metadata.as_mut() {
network_metadata.persistent_metadata.reference = None;
}
// Update the metadata for the outer network
self.unload_outward_wires(&encapsulating_network_path);
@@ -3721,28 +3733,17 @@ impl NodeNetworkInterface {
let number_of_inputs = node.inputs.len();
let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { return };
for added_input_index in metadata.persistent_metadata.input_metadata.len()..number_of_inputs {
let reference = metadata.persistent_metadata.reference.as_ref();
let definition = reference.and_then(|reference| resolve_document_node_type(reference));
let input_metadata = definition
let input_metadata = self
.reference(node_id, network_path)
.as_ref()
.and_then(resolve_document_node_type)
.and_then(|definition| definition.node_template.persistent_node_metadata.input_metadata.get(added_input_index))
.cloned();
let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { return };
metadata.persistent_metadata.input_metadata.push(input_metadata.unwrap_or_default());
}
}
/// Used to ensure the display name is the reference name in case it is empty.
pub fn validate_display_name_metadata(&mut self, node_id: &NodeId, network_path: &[NodeId]) {
let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { return };
if metadata.persistent_metadata.display_name.is_empty()
&& let Some(reference) = metadata.persistent_metadata.reference.clone()
{
// Keep the name for merge nodes as empty
if reference != "Merge" {
metadata.persistent_metadata.display_name = reference;
}
}
}
// When opening an old document to ensure the output names match the number of exports
pub fn validate_output_names(&mut self, node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId]) {
if let DocumentNodeImplementation::Network(network) = &node.implementation {
@@ -3755,13 +3756,17 @@ impl NodeNetworkInterface {
}
}
/// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts
pub fn replace_reference_name(&mut self, node_id: &NodeId, network_path: &[NodeId], reference_name: String) {
let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {
log::error!("Could not get node metadata in replace_reference_name");
/// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts.
/// Only works with network nodes. Proto nodes use their ID as the reference.
pub fn set_reference(&mut self, node_id: &NodeId, network_path: &[NodeId], reference_name: Option<String>) {
let Some(node_network_metadata) = self
.node_metadata_mut(node_id, network_path)
.and_then(|node_metadata| node_metadata.persistent_metadata.network_metadata.as_mut())
else {
log::error!("Could not get network metadata in replace_reference_name");
return;
};
node_metadata.persistent_metadata.reference = Some(reference_name);
node_network_metadata.persistent_metadata.reference = reference_name;
}
/// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts
@@ -4397,7 +4402,10 @@ impl NodeNetworkInterface {
node_metadata.persistent_metadata.display_name.clone_from(&display_name);
// Keep the alias in sync with the `ToArtboard` name input
if node_metadata.persistent_metadata.reference.as_ref().is_some_and(|reference| reference == "Artboard") {
if self
.reference(node_id, network_path)
.is_some_and(|reference| reference == DefinitionIdentifier::Network("Artboard".into()))
{
let Some(nested_network) = self.network_mut(network_path) else {
return;
};
@@ -5382,7 +5390,9 @@ impl NodeNetworkInterface {
// If a non artboard layer is attempted to be connected to the exports, and there is already an artboard connected, then connect the layer to the artboard.
if let Some(first_layer) = LayerNodeIdentifier::ROOT_PARENT.children(&self.document_metadata).next()
&& parent == LayerNodeIdentifier::ROOT_PARENT
&& self.reference(&layer.to_node(), network_path).is_none_or(|reference| *reference != Some("Artboard".to_string()))
&& self
.reference(&layer.to_node(), network_path)
.is_none_or(|reference| reference != DefinitionIdentifier::Network("Artboard".into()))
&& self.is_artboard(&first_layer.to_node(), network_path)
{
parent = first_layer;
@@ -5974,6 +5984,12 @@ impl NodeNetworkMetadata {
#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct NodeNetworkPersistentMetadata {
/// The identifier for the node definition created for custom network nodes in [`DocumentNodeDefinition`].
/// It is only used to associate network nodes with their definition. Protonodes use their ProtonodeIdentifier.
/// The reference is removed once the node is modified, since the node now stores its own implementation and inputs.
/// TODO: Used during serialization/deserialization to prevent storing implementation or inputs (and possible other fields) if they are the same as the definition.
/// TODO: Implement node versioning so that references to old nodes can be updated to the new node definition.
pub reference: Option<String>,
/// Node metadata must exist for every document node in the network
#[serde(serialize_with = "graphene_std::vector::serialize_hashmap", deserialize_with = "graphene_std::vector::deserialize_hashmap")]
pub node_metadata: HashMap<NodeId, DocumentNodeMetadata>,
@@ -6218,13 +6234,9 @@ struct InputTransientMetadata {
/// Persistent metadata for each node in the network, which must be included when creating, serializing, and deserializing saving a node.
#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DocumentNodePersistentMetadata {
/// The name of the node definition, as originally set by [`DocumentNodeDefinition`], used to display in the UI and to display the appropriate properties if no display name is set.
// TODO: Used during serialization/deserialization to prevent storing implementation or inputs (and possible other fields) if they are the same as the definition.
// TODO: The reference is removed once the node is modified, since the node now stores its own implementation and inputs.
// TODO: Implement node versioning so that references to old nodes can be updated to the new node definition.
pub reference: Option<String>,
/// A name chosen by the user for this instance of the node. Empty indicates no given name, in which case the reference name is displayed to the user in italics.
/// A name chosen by the user for this instance of the node. Empty indicates no given name, in which case the implementation name is displayed to the user in italics.
#[serde(default)]
pub display_name: String,
/// Stores metadata to override the properties in the properties panel for each input. These can either be generated automatically based on the type, or with a custom function.
@@ -6463,7 +6475,9 @@ mod network_interface_tests {
async fn copy_isolated_node() {
let mut editor = EditorTestUtils::create();
editor.new_document().await;
let rectangle = editor.create_node_by_name("Rectangle").await;
let rectangle = editor
.create_node_by_name(DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::rectangle::IDENTIFIER))
.await;
editor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![rectangle] }).await;
let frontend_messages = editor.handle_message(NodeGraphMessage::Copy).await;
let serialized_nodes = frontend_messages
@@ -19,13 +19,14 @@ pub struct DocumentNodePersistentMetadataInputNames {
pub network_metadata: Option<NodeNetworkMetadata>,
}
impl From<DocumentNodePersistentMetadataInputNames> for DocumentNodePersistentMetadata {
impl From<DocumentNodePersistentMetadataInputNames> for DocumentNodePersistentMetadataPropertiesRow {
fn from(old: DocumentNodePersistentMetadataInputNames) -> Self {
DocumentNodePersistentMetadata {
input_metadata: Vec::new(),
DocumentNodePersistentMetadataPropertiesRow {
reference: old.reference,
input_properties: Vec::new(),
display_name: old.display_name,
output_names: old.output_names,
has_primary_output: old.has_primary_output,
locked: old.locked,
pinned: old.pinned,
node_type_metadata: old.node_type_metadata,
@@ -60,7 +61,7 @@ pub struct PropertiesRow {
pub input_description: String,
}
impl From<DocumentNodePersistentMetadataPropertiesRow> for DocumentNodePersistentMetadata {
impl From<DocumentNodePersistentMetadataPropertiesRow> for DocumentNodePersistentMetadataHasPrimaryOutput {
fn from(old: DocumentNodePersistentMetadataPropertiesRow) -> Self {
let mut input_metadata = Vec::new();
for properties_row in old.input_properties {
@@ -74,11 +75,12 @@ impl From<DocumentNodePersistentMetadataPropertiesRow> for DocumentNodePersisten
..Default::default()
})
}
DocumentNodePersistentMetadata {
DocumentNodePersistentMetadataHasPrimaryOutput {
reference: old.reference,
display_name: old.display_name,
input_metadata: Vec::new(),
output_names: old.output_names,
has_primary_output: old.has_primary_output,
locked: old.locked,
pinned: old.pinned,
node_type_metadata: old.node_type_metadata,
@@ -104,9 +106,9 @@ pub struct DocumentNodePersistentMetadataHasPrimaryOutput {
pub network_metadata: Option<NodeNetworkMetadata>,
}
impl From<DocumentNodePersistentMetadataHasPrimaryOutput> for DocumentNodePersistentMetadata {
impl From<DocumentNodePersistentMetadataHasPrimaryOutput> for DocumentNodePersistentMetadataStringReference {
fn from(old: DocumentNodePersistentMetadataHasPrimaryOutput) -> Self {
DocumentNodePersistentMetadata {
DocumentNodePersistentMetadataStringReference {
reference: old.reference,
display_name: old.display_name,
input_metadata: old.input_metadata,
@@ -119,6 +121,49 @@ impl From<DocumentNodePersistentMetadataHasPrimaryOutput> for DocumentNodePersis
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
struct DocumentNodePersistentMetadataStringReference {
pub reference: Option<String>,
#[serde(default)]
pub display_name: String,
pub input_metadata: Vec<InputMetadata>,
pub output_names: Vec<String>,
#[serde(default)]
pub locked: bool,
#[serde(default)]
pub pinned: bool,
pub node_type_metadata: NodeTypePersistentMetadata,
pub network_metadata: Option<NodeNetworkMetadata>,
}
impl From<DocumentNodePersistentMetadataStringReference> for DocumentNodePersistentMetadata {
fn from(mut old: DocumentNodePersistentMetadataStringReference) -> Self {
if let Some(metadata) = old.network_metadata.as_mut() {
metadata.persistent_metadata.reference = old.reference;
}
DocumentNodePersistentMetadata {
display_name: old.display_name,
input_metadata: old.input_metadata,
output_names: old.output_names,
locked: old.locked,
pinned: old.pinned,
node_type_metadata: old.node_type_metadata,
network_metadata: old.network_metadata,
}
}
}
#[derive(serde::Deserialize)]
#[serde(untagged)]
enum DocumentNodePersistentMetadataVersioned {
// Newest first
Current(DocumentNodePersistentMetadata),
StringReference(DocumentNodePersistentMetadataStringReference),
HasPrimaryOutput(DocumentNodePersistentMetadataHasPrimaryOutput),
PropertiesRow(DocumentNodePersistentMetadataPropertiesRow),
InputNames(DocumentNodePersistentMetadataInputNames),
}
pub fn deserialize_node_persistent_metadata<'de, D>(deserializer: D) -> Result<DocumentNodePersistentMetadata, D::Error>
where
D: serde::Deserializer<'de>,
@@ -126,17 +171,31 @@ where
use serde::Deserialize;
let value = Value::deserialize(deserializer)?;
if let Ok(document) = serde_json::from_value::<DocumentNodePersistentMetadataHasPrimaryOutput>(value.clone()) {
return Ok(document.into());
let versioned_document = serde_json::from_value::<DocumentNodePersistentMetadataVersioned>(value).map_err(serde::de::Error::custom)?;
let current: DocumentNodePersistentMetadata = match versioned_document {
DocumentNodePersistentMetadataVersioned::Current(v) => v,
DocumentNodePersistentMetadataVersioned::StringReference(v) => {
let v: DocumentNodePersistentMetadataStringReference = v;
v.into()
}
DocumentNodePersistentMetadataVersioned::HasPrimaryOutput(v) => {
let v: DocumentNodePersistentMetadataStringReference = v.into();
v.into()
}
DocumentNodePersistentMetadataVersioned::PropertiesRow(v) => {
let v: DocumentNodePersistentMetadataHasPrimaryOutput = v.into();
let v: DocumentNodePersistentMetadataStringReference = v.into();
v.into()
}
DocumentNodePersistentMetadataVersioned::InputNames(v) => {
let v: DocumentNodePersistentMetadataPropertiesRow = v.into();
let v: DocumentNodePersistentMetadataHasPrimaryOutput = v.into();
let v: DocumentNodePersistentMetadataStringReference = v.into();
v.into()
}
};
if let Ok(document) = serde_json::from_value::<DocumentNodePersistentMetadata>(value.clone()) {
return Ok(document);
};
if let Ok(document) = serde_json::from_value::<DocumentNodePersistentMetadataPropertiesRow>(value.clone()) {
return Ok(document.into());
};
match serde_json::from_value::<DocumentNodePersistentMetadataInputNames>(value.clone()) {
Ok(document) => Ok(document.into()),
Err(e) => Err(serde::de::Error::custom(e)),
}
Ok(current)
}
@@ -1,5 +1,6 @@
use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use super::network_interface::NodeNetworkInterface;
use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;
use crate::messages::tool::common_functionality::graph_modification_utils;
use glam::DVec2;
use graph_craft::document::{NodeId, NodeNetwork};
@@ -34,7 +35,7 @@ impl serde::Serialize for JsRawBuffer {
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
pub struct LayerPanelEntry {
pub id: NodeId,
pub reference: String,
pub reference: DefinitionIdentifier,
pub alias: String,
#[serde(rename = "inSelectedNetwork")]
pub in_selected_network: bool,
@@ -2,6 +2,7 @@ use super::network_interface::NodeNetworkInterface;
use crate::consts::{ROTATE_INCREMENT, SCALE_INCREMENT};
use crate::messages::portfolio::document::graph_operation::transform_utils;
use crate::messages::portfolio::document::graph_operation::utility_types::{ModifyInputsContext, TransformIn};
use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
@@ -53,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("Transform", layer, network_interface)?;
let transform_node_id = ModifyInputsContext::locate_node_in_layer_chain(&DefinitionIdentifier::Network("Transform".into()), layer, network_interface)?;
let document_node = network_interface.document_network().nodes.get(&transform_node_id)?;
Some(transform_utils::get_current_transform(&document_node.inputs))