Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pub(super) fn post_process_nodes(custom: Vec<DocumentNodeDefinition>) -> HashMap
inputs,
call_argument: input_type.clone(),
implementation: DocumentNodeImplementation::ProtoNode(id.clone()),
visible: true,
visible: None,
skip_deduplication: false,
context_features: ContextDependencies::from(context_features.as_slice()),
..Default::default()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2571,7 +2571,7 @@ impl NodeGraphMessageHandler {
return Vec::new();
};
let mut nodes = Vec::new();
for (node_id, visible) in network.nodes.iter().map(|(node_id, node)| (*node_id, node.visible)).collect::<Vec<_>>() {
for (node_id, visible) in network.nodes.iter().map(|(node_id, node)| (*node_id, node.visible.is_none())).collect::<Vec<_>>() {
let primary_input_connector = InputConnector::node(node_id, 0);

let primary_input = if network_interface
Expand Down Expand Up @@ -2721,7 +2721,7 @@ impl NodeGraphMessageHandler {

let parents_visible = layer.ancestors(network_interface.document_metadata()).filter(|&ancestor| ancestor != layer).all(|layer| {
if layer != LayerNodeIdentifier::ROOT_PARENT {
network_interface.document_node(&layer.to_node(), &[]).map(|node| node.visible).unwrap_or_default()
network_interface.document_node(&layer.to_node(), &[]).map(|node| node.visible.is_none()).unwrap_or_default()
} else {
true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crate::messages::portfolio::document::graph_operation::utility_types::Modify
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;
use crate::messages::portfolio::document::utility_types::network_interface::resolved_types::{ResolvedDocumentNodeTypes, TypeSource};
use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire};
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::tool_messages::tool_prelude::NumberInputMode;
Expand All @@ -19,6 +19,7 @@ use glam::{DAffine2, DVec2, IVec2};
use graph_craft::Type;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork};
use graph_craft::document::{Hidden, HiddenNodeInput};
use graphene_std::ContextDependencies;
use graphene_std::math::quad::Quad;
use graphene_std::subpath::Subpath;
Expand Down Expand Up @@ -1049,7 +1050,7 @@ impl NodeNetworkInterface {
log::error!("Could not get node in is_visible");
return false;
};
node.visible
node.visible.is_none()
}

pub fn is_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
Expand Down Expand Up @@ -1358,7 +1359,7 @@ impl NodeNetworkInterface {

node.inputs = old_node.inputs;
node.call_argument = old_node.manual_composition.unwrap();
node.visible = old_node.visible;
node.visible = if old_node.visible { None } else { Some(Hidden::Passthrough) };
node.skip_deduplication = old_node.skip_deduplication;
node.original_location = old_node.original_location;
node_metadata.persistent_metadata.display_name = old_node.alias;
Expand Down Expand Up @@ -4481,17 +4482,144 @@ impl NodeNetworkInterface {
}

pub fn set_visibility(&mut self, node_id: &NodeId, network_path: &[NodeId], is_visible: bool) {
let Some(network) = self.network_mut(network_path) else {
return;
};
let Some(node) = network.nodes.get_mut(node_id) else {
log::error!("Could not get node {node_id} in set_visibility");
if is_visible {
if let Some(network) = self.network_mut(network_path) {
if let Some(node) = network.nodes.get_mut(node_id) {
node.visible = None;
}
}
self.transaction_modified();
return;
}

let input_count = self.document_node(node_id, network_path).map_or(0, |node| node.inputs.len());

let visibility = if input_count > 0 {
// Nodes with inputs: use Passthrough (become identity node passing first input)
Some(Hidden::Passthrough)
} else {
// Nodes with NO inputs - these generate values dynamically
// We need to check if they're connected to anything
let output_connector = OutputConnector::Node { node_id: *node_id, output_index: 0 };

// Get all downstream connections
let downstream_connectors = self
.outward_wires(network_path)
.and_then(|outward_wires| outward_wires.get(&output_connector))
.cloned()
.unwrap_or_default();

if downstream_connectors.is_empty() {
// No downstream connections - safe to hide with a dummy value
// Use Passthrough which will make it output unit type
Some(Hidden::Passthrough)
} else {
// Has downstream connections - need to provide actual values
// For dynamic nodes, we try to infer from downstream what values they expect
let tagged_inputs: Vec<HiddenNodeInput> = downstream_connectors
.into_iter()
.filter_map(|connector| {
let InputConnector::Node { node_id: downstream_node_id, input_index } = connector else {
return None;
};
let input_connector = InputConnector::node(downstream_node_id, input_index);
let tagged_value = self.tagged_value_from_downstream_connector(&input_connector, network_path);
if matches!(tagged_value, TaggedValue::None) {
log::warn!("Could not infer hidden replacement value for node {downstream_node_id} input {input_index}");
return None;
}

Some(HiddenNodeInput {
node_id: downstream_node_id,
input_index,
tagged_value,
})
})
.collect();

if tagged_inputs.is_empty() {
// Couldn't infer any valid values - fall back to Passthrough
log::warn!("Cannot properly hide node {node_id} with no inputs - using Passthrough fallback");
Some(Hidden::Passthrough)
} else {
Some(Hidden::TaggedValues(tagged_inputs))
}
}
};

node.visible = is_visible;
if let Some(network) = self.network_mut(network_path) {
if let Some(node) = network.nodes.get_mut(node_id) {
node.visible = visibility;
}
}

self.transaction_modified();
}

fn tagged_value_from_downstream_connector(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> TaggedValue {
let tagged_value = self.tagged_value_from_input(input_connector, network_path);
if !matches!(tagged_value, TaggedValue::None) {
return tagged_value;
}

// If inference falls back to `None`, try extracting the concrete type directly from this connector.
if let TypeSource::Compiled(ty) | TypeSource::TaggedValue(ty) = self.input_type_not_invalid(input_connector, network_path) {
let typed = TaggedValue::from_type_or_none(&ty);
if !matches!(typed, TaggedValue::None) {
return typed;
}
}

let InputConnector::Node { node_id, input_index } = *input_connector else {
return tagged_value;
};
let Some(DocumentNodeImplementation::Network(_)) = self.implementation(&node_id, network_path) else {
// If this isn't a nested network input, try candidates directly on this connector.
let mut candidate_types = self.complete_valid_input_types(input_connector, network_path);
if candidate_types.is_empty() {
candidate_types = self.potential_valid_input_types(input_connector, network_path);
}
candidate_types.sort_by_key(|ty| ty.nested_type().identifier_name());

if let Some(candidate) = candidate_types.iter().find_map(|ty| {
let typed = TaggedValue::from_type_or_none(ty);
(!matches!(typed, TaggedValue::None)).then_some(typed)
}) {
return candidate;
}

return tagged_value;
};

let nested_path = [network_path, &[node_id]].concat();
let nested_downstream = self
.outward_wires(&nested_path)
.and_then(|outward_wires| outward_wires.get(&OutputConnector::Import(input_index)))
.cloned()
.unwrap_or_default();

for nested_input_connector in nested_downstream {
let nested_tagged_value = self.tagged_value_from_downstream_connector(&nested_input_connector, &nested_path);
if !matches!(nested_tagged_value, TaggedValue::None) {
return nested_tagged_value;
}
}

// Nested traversal could not infer; try candidates at this connector as a final fallback.
let mut candidate_types = self.complete_valid_input_types(input_connector, network_path);
if candidate_types.is_empty() {
candidate_types = self.potential_valid_input_types(input_connector, network_path);
}
candidate_types.sort_by_key(|ty| ty.nested_type().identifier_name());

candidate_types
.iter()
.find_map(|ty| {
let typed = TaggedValue::from_type_or_none(ty);
(!matches!(typed, TaggedValue::None)).then_some(typed)
})
.unwrap_or(tagged_value)
}

pub fn set_locked(&mut self, node_id: &NodeId, network_path: &[NodeId], locked: bool) {
let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,28 +182,39 @@ impl NodeNetworkInterface {

/// Gets the default tagged value for an input. If its not compiled, then it tries to get a valid type. If there are no valid types, then it picks a random implementation.
pub fn tagged_value_from_input(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> TaggedValue {
let guaranteed_type = match self.input_type(input_connector, network_path) {
TypeSource::Compiled(compiled) => compiled,
TypeSource::TaggedValue(value) => value,
match self.input_type(input_connector, network_path) {
TypeSource::Compiled(compiled) | TypeSource::TaggedValue(compiled) => {
let tagged_value = TaggedValue::from_type_or_none(&compiled);
if matches!(tagged_value, TaggedValue::None) {
log::error!("Failed to construct TaggedValue for {compiled:?} in tagged_value_from_input");
}
tagged_value
}
TypeSource::Unknown | TypeSource::Invalid => {
// Pick a random type from the complete valid types
// TODO: Add a NodeInput::Indeterminate which can be resolved at compile time to be any type that prevents an error. This may require bidirectional typing.
self.complete_valid_input_types(input_connector, network_path)
.into_iter()
.min_by_key(|ty| ty.nested_type().identifier_name())
// Pick a random type from the potential valid types
.or_else(|| {
self.potential_valid_input_types(input_connector, network_path)
.into_iter()
.min_by_key(|ty| ty.nested_type().identifier_name())
}).unwrap_or(concrete!(()))
// Try complete valid types first, then potential valid types
let mut candidate_types = self.complete_valid_input_types(input_connector, network_path);
if candidate_types.is_empty() {
candidate_types = self.potential_valid_input_types(input_connector, network_path);
}
candidate_types.sort_by_key(|ty| ty.nested_type().identifier_name());

// Try each candidate type in order until we find one that works
if let Some(tagged_value) = candidate_types.iter().find_map(|ty| {
let tagged = TaggedValue::from_type_or_none(ty);
(!matches!(tagged, TaggedValue::None)).then_some(tagged)
}) {
return tagged_value;
}

let fallback_type = candidate_types.first().cloned().unwrap_or(concrete!(()));
log::error!("Failed to construct TaggedValue for any valid type on {input_connector:?}; fallback type: {fallback_type:?}");
TaggedValue::from_type_or_none(&fallback_type)
}
TypeSource::Error(e) => {
log::error!("Error getting tagged_value_from_input for {input_connector:?} {e}");
concrete!(())
TaggedValue::None
}
};
TaggedValue::from_type_or_none(&guaranteed_type)
}
}

/// A list of all valid input types for this specific node.
Expand Down
2 changes: 1 addition & 1 deletion node-graph/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub struct DocumentNode {
pub call_argument: Type,
pub implementation: DocumentNodeImplementation,
pub skip_deduplication: bool,
pub visible: bool,
pub visible: Option<Hidden>,
pub original_location: OriginalLocation,
}
```
Expand Down
Loading