mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-07 15:55:00 +00:00
Update Imaginate to output bitmap data to the graph via Image Frame node (#1001)
* Multiple node outputs * Add new nodes * gcore use std by default to allow for testing * Allow multiple node outputs * Multiple outputs to frontend * Add ImageFrameNode to node registry * Minor cleanup * Basic transform implementation * Add some logging to image encoding * Fix ImageFrameNode * Add transform input to Imaginate node (#1014) * Add transform input to imaginate node * Force the resolution to be edited with no transform * Add transform to imaginate generation * Fix compilation --------- Co-authored-by: Keavon Chambers <keavon@keavon.com> * Add labels to node outputs * Fix seed; disable mask when transform is disconnected; add Imaginate tooltips * Rename 'Input Multiple' node to 'Input' * Code review * Replicate to Svelte * Show only the primary input chain in the Properties panel --------- Co-authored-by: Dennis Kobert <dennis@kobert.dev> Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
a709a772d5
commit
1b0e1b9bdf
35 changed files with 1172 additions and 553 deletions
|
@ -11,5 +11,6 @@ pub mod application;
|
|||
pub mod consts;
|
||||
pub mod dispatcher;
|
||||
pub mod messages;
|
||||
pub mod node_graph_executor;
|
||||
pub mod test_utils;
|
||||
pub mod utility_traits;
|
||||
|
|
|
@ -107,6 +107,7 @@ pub enum DocumentMessage {
|
|||
},
|
||||
NodeGraphFrameImaginateRandom {
|
||||
imaginate_node: Vec<NodeId>,
|
||||
then_generate: bool,
|
||||
},
|
||||
NodeGraphFrameImaginateTerminate {
|
||||
layer_path: Vec<LayerId>,
|
||||
|
|
|
@ -21,6 +21,7 @@ use crate::messages::portfolio::document::utility_types::vectorize_layer_metadat
|
|||
use crate::messages::portfolio::utility_types::PersistentData;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::utility_types::ToolType;
|
||||
use crate::node_graph_executor::NodeGraphExecutor;
|
||||
|
||||
use document_legacy::boolean_ops::BooleanOperationError;
|
||||
use document_legacy::document::Document as DocumentLegacy;
|
||||
|
@ -104,13 +105,13 @@ impl Default for DocumentMessageHandler {
|
|||
}
|
||||
}
|
||||
|
||||
impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &PersistentData, &PreferencesMessageHandler)> for DocumentMessageHandler {
|
||||
impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &PersistentData, &PreferencesMessageHandler, &mut NodeGraphExecutor)> for DocumentMessageHandler {
|
||||
#[remain::check]
|
||||
fn process_message(
|
||||
&mut self,
|
||||
message: DocumentMessage,
|
||||
responses: &mut VecDeque<Message>,
|
||||
(document_id, ipp, persistent_data, preferences): (u64, &InputPreprocessorMessageHandler, &PersistentData, &PreferencesMessageHandler),
|
||||
(document_id, ipp, persistent_data, preferences, executor): (u64, &InputPreprocessorMessageHandler, &PersistentData, &PreferencesMessageHandler, &mut NodeGraphExecutor),
|
||||
) {
|
||||
use DocumentMessage::*;
|
||||
|
||||
|
@ -203,6 +204,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
|||
artboard_document: &self.artboard_message_handler.artboards_document,
|
||||
selected_layers: &mut self.layer_metadata.iter().filter_map(|(path, data)| data.selected.then_some(path.as_slice())),
|
||||
node_graph_message_handler: &self.node_graph_handler,
|
||||
executor,
|
||||
};
|
||||
self.properties_panel_message_handler
|
||||
.process_message(message, responses, (persistent_data, properties_panel_message_handler_data));
|
||||
|
@ -527,18 +529,22 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
|||
responses.push_back(message);
|
||||
}
|
||||
}
|
||||
NodeGraphFrameImaginateRandom { imaginate_node } => {
|
||||
NodeGraphFrameImaginateRandom { imaginate_node, then_generate } => {
|
||||
// Set a random seed input
|
||||
responses.push_back(
|
||||
NodeGraphMessage::SetInputValue {
|
||||
node_id: *imaginate_node.last().unwrap(),
|
||||
input_index: 1,
|
||||
// Needs to match the index of the seed parameter in `pub const IMAGINATE_NODE: DocumentNodeType` in `document_node_type.rs`
|
||||
input_index: 2,
|
||||
value: graph_craft::document::value::TaggedValue::F64((generate_uuid() >> 1) as f64),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
// Generate the image
|
||||
responses.push_back(DocumentMessage::NodeGraphFrameImaginate { imaginate_node }.into());
|
||||
if then_generate {
|
||||
responses.push_back(DocumentMessage::NodeGraphFrameImaginate { imaginate_node }.into());
|
||||
}
|
||||
}
|
||||
NodeGraphFrameImaginateTerminate { layer_path, node_path } => {
|
||||
responses.push_back(
|
||||
|
@ -599,8 +605,8 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
|||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
|
||||
let path = vec![generate_uuid()];
|
||||
let image_node_id = 2;
|
||||
let mut network = graph_craft::document::NodeNetwork::new_network(32, image_node_id);
|
||||
let image_node_id = 100;
|
||||
let mut network = crate::messages::portfolio::document::node_graph::new_image_network(32, image_node_id);
|
||||
|
||||
let Some(image_node_type) = crate::messages::portfolio::document::node_graph::resolve_document_node_type("Image") else {
|
||||
warn!("Image node should be in registry");
|
||||
|
@ -609,12 +615,10 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
|||
|
||||
network.nodes.insert(
|
||||
image_node_id,
|
||||
graph_craft::document::DocumentNode {
|
||||
name: image_node_type.name.to_string(),
|
||||
inputs: vec![graph_craft::document::NodeInput::value(graph_craft::document::value::TaggedValue::Image(image), false)],
|
||||
implementation: image_node_type.generate_implementation(),
|
||||
metadata: graph_craft::document::DocumentNodeMetadata { position: (20, 4).into() },
|
||||
},
|
||||
image_node_type.to_document_node(
|
||||
[graph_craft::document::NodeInput::value(graph_craft::document::value::TaggedValue::Image(image), false)],
|
||||
graph_craft::document::DocumentNodeMetadata::position((20, 4)),
|
||||
),
|
||||
);
|
||||
|
||||
responses.push_back(
|
||||
|
|
|
@ -12,6 +12,7 @@ pub enum NodeGraphMessage {
|
|||
CloseNodeGraph,
|
||||
ConnectNodesByLink {
|
||||
output_node: u64,
|
||||
output_node_connector_index: usize,
|
||||
input_node: u64,
|
||||
input_node_connector_index: usize,
|
||||
},
|
||||
|
|
|
@ -9,7 +9,7 @@ use document_legacy::layers::layer_info::LayerDataTypeDiscriminant;
|
|||
use document_legacy::layers::nodegraph_layer::NodeGraphFrameLayer;
|
||||
use document_legacy::LayerId;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork};
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, NodeOutput};
|
||||
mod document_node_types;
|
||||
mod node_properties;
|
||||
|
||||
|
@ -39,7 +39,7 @@ impl FrontendGraphDataType {
|
|||
pub const fn with_tagged_value(value: &TaggedValue) -> Self {
|
||||
match value {
|
||||
TaggedValue::String(_) => Self::Text,
|
||||
TaggedValue::F32(_) | TaggedValue::F64(_) | TaggedValue::U32(_) => Self::Number,
|
||||
TaggedValue::F32(_) | TaggedValue::F64(_) | TaggedValue::U32(_) | TaggedValue::DAffine2(_) => Self::Number,
|
||||
TaggedValue::Bool(_) => Self::Boolean,
|
||||
TaggedValue::DVec2(_) => Self::Vector,
|
||||
TaggedValue::Image(_) => Self::Raster,
|
||||
|
@ -57,6 +57,13 @@ pub struct NodeGraphInput {
|
|||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct NodeGraphOutput {
|
||||
#[serde(rename = "dataType")]
|
||||
data_type: FrontendGraphDataType,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct FrontendNode {
|
||||
pub id: graph_craft::document::NodeId,
|
||||
|
@ -66,10 +73,10 @@ pub struct FrontendNode {
|
|||
pub primary_input: Option<FrontendGraphDataType>,
|
||||
#[serde(rename = "exposedInputs")]
|
||||
pub exposed_inputs: Vec<NodeGraphInput>,
|
||||
pub outputs: Vec<FrontendGraphDataType>,
|
||||
pub outputs: Vec<NodeGraphOutput>, // TODO: Break this apart into `primary_output` and `exposed_outputs`
|
||||
pub position: (i32, i32),
|
||||
pub disabled: bool,
|
||||
pub output: bool,
|
||||
pub previewed: bool,
|
||||
}
|
||||
|
||||
// (link_start, link_end, link_end_input_index)
|
||||
|
@ -77,6 +84,8 @@ pub struct FrontendNode {
|
|||
pub struct FrontendNodeLink {
|
||||
#[serde(rename = "linkStart")]
|
||||
pub link_start: u64,
|
||||
#[serde(rename = "linkStartOutputIndex")]
|
||||
pub link_start_output_index: usize,
|
||||
#[serde(rename = "linkEnd")]
|
||||
pub link_end: u64,
|
||||
#[serde(rename = "linkEndInputIndex")]
|
||||
|
@ -176,7 +185,7 @@ impl NodeGraphMessageHandler {
|
|||
let mut widgets = Vec::new();
|
||||
|
||||
// Don't allow disabling input or output nodes
|
||||
let mut selected_nodes = self.selected_nodes.iter().filter(|&&id| !network.inputs.contains(&id) && network.original_output() != id);
|
||||
let mut selected_nodes = self.selected_nodes.iter().filter(|&&id| !network.inputs.contains(&id) && !network.original_outputs_contain(id));
|
||||
|
||||
// If there is at least one other selected node then show the hide or show button
|
||||
if selected_nodes.next().is_some() {
|
||||
|
@ -187,13 +196,11 @@ impl NodeGraphMessageHandler {
|
|||
let multiple_nodes = selected_nodes.next().is_some();
|
||||
|
||||
// Generate the enable or disable button accordingly
|
||||
let hide_button = WidgetHolder::new(Widget::TextButton(TextButton {
|
||||
label: if is_hidden { "Show" } else { "Hide" }.to_string(),
|
||||
tooltip: if is_hidden { "Show node" } else { "Hide node" }.to_string() + if multiple_nodes { "s" } else { "" },
|
||||
tooltip_shortcut: action_keys!(NodeGraphMessageDiscriminant::ToggleHidden),
|
||||
on_update: WidgetCallback::new(move |_| NodeGraphMessage::ToggleHidden.into()),
|
||||
..Default::default()
|
||||
}));
|
||||
let hide_button = TextButton::new(if is_hidden { "Show" } else { "Hide" })
|
||||
.tooltip(if is_hidden { "Show node" } else { "Hide node" }.to_string() + if multiple_nodes { "s" } else { "" })
|
||||
.tooltip_shortcut(action_keys!(NodeGraphMessageDiscriminant::ToggleHidden))
|
||||
.on_update(move |_| NodeGraphMessage::ToggleHidden.into())
|
||||
.widget_holder();
|
||||
widgets.push(hide_button);
|
||||
}
|
||||
|
||||
|
@ -201,13 +208,13 @@ impl NodeGraphMessageHandler {
|
|||
if self.selected_nodes.len() == 1 {
|
||||
let node_id = self.selected_nodes[0];
|
||||
// Is this node the current output
|
||||
let is_output = network.output == node_id;
|
||||
let is_output = network.outputs_contain(node_id);
|
||||
|
||||
// Don't show stop previewing button on the original output node
|
||||
if !(is_output && network.previous_output.filter(|&id| id != self.selected_nodes[0]).is_none()) {
|
||||
if !(is_output && network.previous_outputs_contain(node_id).unwrap_or(true)) {
|
||||
let output_button = WidgetHolder::new(Widget::TextButton(TextButton {
|
||||
label: if is_output { "End Preview" } else { "Preview" }.to_string(),
|
||||
tooltip: if is_output { "Restore preview to Output node" } else { "Preview node" }.to_string() + " (shortcut: Alt+click node)",
|
||||
tooltip: if is_output { "Restore preview to Output node" } else { "Preview node" }.to_string() + " (Shortcut: Alt-click node)",
|
||||
on_update: WidgetCallback::new(move |_| NodeGraphMessage::TogglePreview { node_id }.into()),
|
||||
..Default::default()
|
||||
}));
|
||||
|
@ -220,6 +227,7 @@ impl NodeGraphMessageHandler {
|
|||
self.send_node_bar_layout(responses);
|
||||
}
|
||||
|
||||
/// Collate the properties panel sections for a node graph
|
||||
pub fn collate_properties(&self, node_graph_frame: &NodeGraphFrameLayer, context: &mut NodePropertiesContext, sections: &mut Vec<LayoutGroup>) {
|
||||
let mut network = &node_graph_frame.network;
|
||||
for segment in &self.nested_path {
|
||||
|
@ -228,20 +236,27 @@ impl NodeGraphMessageHandler {
|
|||
|
||||
// If empty, show all nodes in the network starting with the output
|
||||
if self.selected_nodes.is_empty() {
|
||||
let mut stack = vec![network.output];
|
||||
let mut stack = network.outputs.iter().map(|output| output.node_id).collect::<Vec<_>>();
|
||||
let mut nodes = Vec::new();
|
||||
while let Some(node_id) = stack.pop() {
|
||||
let Some(document_node) = network.nodes.get(&node_id) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
stack.extend(document_node.inputs.iter().filter_map(|input| if let NodeInput::Node(ref_id) = input { Some(*ref_id) } else { None }));
|
||||
stack.extend(
|
||||
document_node
|
||||
.inputs
|
||||
.iter()
|
||||
.take(1) // Only show the primary input
|
||||
.filter_map(|input| if let NodeInput::Node { node_id: ref_id, .. } = input { Some(*ref_id) } else { None }),
|
||||
);
|
||||
nodes.push((document_node, node_id));
|
||||
}
|
||||
for &(document_node, node_id) in nodes.iter().rev() {
|
||||
sections.push(node_properties::generate_node_properties(document_node, node_id, context));
|
||||
}
|
||||
}
|
||||
// Show properties for all selected nodes
|
||||
for node_id in &self.selected_nodes {
|
||||
let Some(document_node) = network.nodes.get(node_id) else {
|
||||
continue;
|
||||
|
@ -260,9 +275,14 @@ impl NodeGraphMessageHandler {
|
|||
.iter()
|
||||
.flat_map(|(link_end, node)| node.inputs.iter().filter(|input| input.is_exposed()).enumerate().map(move |(index, input)| (input, link_end, index)))
|
||||
.filter_map(|(input, &link_end, link_end_input_index)| {
|
||||
if let NodeInput::Node(link_start) = *input {
|
||||
if let NodeInput::Node {
|
||||
node_id: link_start,
|
||||
output_index: link_start_index,
|
||||
} = *input
|
||||
{
|
||||
Some(FrontendNodeLink {
|
||||
link_start,
|
||||
link_start_output_index: link_start_index,
|
||||
link_end,
|
||||
link_end_input_index: link_end_input_index as u64,
|
||||
})
|
||||
|
@ -298,9 +318,16 @@ impl NodeGraphMessageHandler {
|
|||
name: input_type.name.to_string(),
|
||||
})
|
||||
.collect(),
|
||||
outputs: node_type.outputs.to_vec(),
|
||||
outputs: node_type
|
||||
.outputs
|
||||
.iter()
|
||||
.map(|output_type| NodeGraphOutput {
|
||||
data_type: output_type.data_type,
|
||||
name: output_type.name.to_string(),
|
||||
})
|
||||
.collect(),
|
||||
position: node.metadata.position.into(),
|
||||
output: network.output == *id,
|
||||
previewed: network.outputs_contain(*id),
|
||||
disabled: network.disabled.contains(id),
|
||||
})
|
||||
}
|
||||
|
@ -318,24 +345,24 @@ impl NodeGraphMessageHandler {
|
|||
);
|
||||
}
|
||||
|
||||
fn remove_references_from_network(network: &mut NodeNetwork, node_id: NodeId) -> bool {
|
||||
if network.inputs.iter().any(|&id| id == node_id) {
|
||||
fn remove_references_from_network(network: &mut NodeNetwork, deleting_node_id: NodeId) -> bool {
|
||||
if network.inputs.contains(&deleting_node_id) {
|
||||
warn!("Deleting input node");
|
||||
return false;
|
||||
}
|
||||
if network.output == node_id {
|
||||
if network.outputs_contain(deleting_node_id) {
|
||||
warn!("Deleting the output node!");
|
||||
return false;
|
||||
}
|
||||
for (id, node) in network.nodes.iter_mut() {
|
||||
if *id == node_id {
|
||||
for (node_id, node) in network.nodes.iter_mut() {
|
||||
if *node_id == deleting_node_id {
|
||||
continue;
|
||||
}
|
||||
for (input_index, input) in node.inputs.iter_mut().enumerate() {
|
||||
let NodeInput::Node(id) = input else {
|
||||
let NodeInput::Node{ node_id, .. } = input else {
|
||||
continue;
|
||||
};
|
||||
if *id != node_id {
|
||||
if *node_id != deleting_node_id {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -344,19 +371,17 @@ impl NodeGraphMessageHandler {
|
|||
return false;
|
||||
};
|
||||
if let NodeInput::Value { tagged_value, .. } = &node_type.inputs[input_index].default {
|
||||
*input = NodeInput::Value {
|
||||
tagged_value: tagged_value.clone(),
|
||||
exposed: true,
|
||||
};
|
||||
*input = NodeInput::value(tagged_value.clone(), true);
|
||||
}
|
||||
}
|
||||
if let DocumentNodeImplementation::Network(network) = &mut node.implementation {
|
||||
Self::remove_references_from_network(network, node_id);
|
||||
Self::remove_references_from_network(network, deleting_node_id);
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Tries to remove a node from the network, returning true on success.
|
||||
fn remove_node(&mut self, network: &mut NodeNetwork, node_id: NodeId) -> bool {
|
||||
if Self::remove_references_from_network(network, node_id) {
|
||||
network.nodes.remove(&node_id);
|
||||
|
@ -378,7 +403,7 @@ impl NodeGraphMessageHandler {
|
|||
fn copy_nodes<'a>(network: &'a NodeNetwork, new_ids: &'a HashMap<NodeId, NodeId>) -> impl Iterator<Item = (NodeId, DocumentNode)> + 'a {
|
||||
new_ids
|
||||
.iter()
|
||||
.filter(|&(&id, _)| id != network.output && !network.inputs.contains(&id))
|
||||
.filter(|&(&id, _)| !network.outputs_contain(id) && !network.inputs.contains(&id))
|
||||
.filter_map(|(&id, &new)| network.nodes.get(&id).map(|node| (new, node.clone())))
|
||||
.map(move |(new, node)| (new, node.map_ids(Self::default_node_input, new_ids)))
|
||||
}
|
||||
|
@ -402,6 +427,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
|
|||
}
|
||||
NodeGraphMessage::ConnectNodesByLink {
|
||||
output_node,
|
||||
output_node_connector_index,
|
||||
input_node,
|
||||
input_node_connector_index,
|
||||
} => {
|
||||
|
@ -422,7 +448,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
|
|||
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
|
||||
let input = NodeInput::Node(output_node);
|
||||
let input = NodeInput::node(output_node, output_node_connector_index);
|
||||
responses.push_back(NodeGraphMessage::SetNodeInput { node_id, input_index, input }.into());
|
||||
|
||||
let should_rerender = network.connected_to_output(node_id);
|
||||
|
@ -454,12 +480,10 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
|
|||
|
||||
responses.push_back(DocumentMessage::StartTransaction.into());
|
||||
|
||||
let document_node = DocumentNode {
|
||||
name: node_type.clone(),
|
||||
inputs: document_node_type.inputs.iter().map(|input| input.default.clone()).collect(),
|
||||
implementation: document_node_type.generate_implementation(),
|
||||
metadata: graph_craft::document::DocumentNodeMetadata { position: (x, y).into() },
|
||||
};
|
||||
let document_node = document_node_type.to_document_node(
|
||||
document_node_type.inputs.iter().map(|input| input.default.clone()),
|
||||
graph_craft::document::DocumentNodeMetadata::position((x, y)),
|
||||
);
|
||||
responses.push_back(NodeGraphMessage::InsertNode { node_id, document_node }.into());
|
||||
|
||||
responses.push_back(NodeGraphMessage::SendGraph { should_rerender: false }.into());
|
||||
|
@ -782,7 +806,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
|
|||
.get(&node_id)
|
||||
.map_or(&Vec::new(), |node| &node.inputs)
|
||||
.iter()
|
||||
.filter_map(|input| if let NodeInput::Node(previous_id) = input { Some(*previous_id) } else { None })
|
||||
.filter_map(|input| if let NodeInput::Node { node_id: previous_id, .. } = input { Some(*previous_id) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for input_node in inputs {
|
||||
|
@ -812,9 +836,11 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
|
|||
// Remove all selected nodes from the disabled list
|
||||
network.disabled.retain(|id| !self.selected_nodes.contains(id));
|
||||
} else {
|
||||
let original_output = network.original_output();
|
||||
let original_outputs = network.original_outputs().iter().map(|output| output.node_id).collect::<Vec<_>>();
|
||||
// Add all selected nodes to the disabled list (excluding input or output nodes)
|
||||
network.disabled.extend(self.selected_nodes.iter().filter(|&id| !network.inputs.contains(id) && original_output != *id));
|
||||
network
|
||||
.disabled
|
||||
.extend(self.selected_nodes.iter().filter(|&id| !network.inputs.contains(id) && !original_outputs.contains(id)));
|
||||
}
|
||||
Self::send_graph(network, responses);
|
||||
|
||||
|
@ -831,12 +857,12 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &mut dyn Iterator<Item = &
|
|||
}
|
||||
NodeGraphMessage::TogglePreviewImpl { node_id } => {
|
||||
if let Some(network) = self.get_active_network_mut(document) {
|
||||
// Check if the node is not already
|
||||
if network.output != node_id {
|
||||
network.previous_output = Some(network.previous_output.unwrap_or(network.output));
|
||||
network.output = node_id;
|
||||
} else if let Some(output) = network.previous_output.take() {
|
||||
network.output = output
|
||||
// Check if the node is not already being previewed
|
||||
if !network.outputs_contain(node_id) {
|
||||
network.previous_outputs = Some(network.previous_outputs.to_owned().unwrap_or_else(|| network.outputs.clone()));
|
||||
network.outputs[0] = NodeOutput::new(node_id, 0);
|
||||
} else if let Some(outputs) = network.previous_outputs.take() {
|
||||
network.outputs = outputs
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use super::{node_properties, FrontendGraphDataType, FrontendNodeType};
|
||||
use crate::messages::layout::utility_types::layout_widget::LayoutGroup;
|
||||
use crate::node_graph_executor::NodeGraphExecutor;
|
||||
|
||||
use graph_craft::document::value::*;
|
||||
use graph_craft::document::*;
|
||||
|
@ -32,12 +33,25 @@ impl DocumentInputType {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct DocumentOutputType {
|
||||
pub name: &'static str,
|
||||
pub data_type: FrontendGraphDataType,
|
||||
}
|
||||
|
||||
impl DocumentOutputType {
|
||||
pub const fn new(name: &'static str, data_type: FrontendGraphDataType) -> Self {
|
||||
Self { name, data_type }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NodePropertiesContext<'a> {
|
||||
pub persistent_data: &'a crate::messages::portfolio::utility_types::PersistentData,
|
||||
pub document: &'a document_legacy::document::Document,
|
||||
pub responses: &'a mut VecDeque<crate::messages::prelude::Message>,
|
||||
pub layer_path: &'a [document_legacy::LayerId],
|
||||
pub nested_path: &'a [NodeId],
|
||||
pub executor: &'a mut NodeGraphExecutor,
|
||||
pub network: &'a NodeNetwork,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -58,25 +72,24 @@ pub struct DocumentNodeType {
|
|||
pub category: &'static str,
|
||||
pub identifier: NodeImplementation,
|
||||
pub inputs: &'static [DocumentInputType],
|
||||
pub outputs: &'static [FrontendGraphDataType],
|
||||
pub outputs: &'static [DocumentOutputType],
|
||||
pub properties: fn(&DocumentNode, NodeId, &mut NodePropertiesContext) -> Vec<LayoutGroup>,
|
||||
}
|
||||
|
||||
fn document_node_types() -> Vec<DocumentNodeType> {
|
||||
let mut vec: Vec<_> = STATIC_NODES.to_vec();
|
||||
|
||||
const INPUTS: &[DocumentInputType] = &[
|
||||
const GAUSSIAN_BLUR_NODE_INPUTS: &[DocumentInputType] = &[
|
||||
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
|
||||
DocumentInputType::new("Radius", TaggedValue::U32(3), false),
|
||||
DocumentInputType::new("Sigma", TaggedValue::F64(1.), false),
|
||||
];
|
||||
|
||||
let blur = DocumentNodeType {
|
||||
name: "Gaussian Blur",
|
||||
category: "Image Filters",
|
||||
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
||||
inputs: vec![0, 1, 1],
|
||||
output: 1,
|
||||
outputs: vec![NodeOutput::new(1, 0)],
|
||||
nodes: vec![
|
||||
(
|
||||
0,
|
||||
|
@ -91,7 +104,7 @@ fn document_node_types() -> Vec<DocumentNodeType> {
|
|||
1,
|
||||
DocumentNode {
|
||||
name: "BlurNode".to_string(),
|
||||
inputs: vec![NodeInput::Node(0), NodeInput::Network, NodeInput::Network, NodeInput::Node(0)],
|
||||
inputs: vec![NodeInput::node(0, 0), NodeInput::Network, NodeInput::Network, NodeInput::node(0, 0)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::raster::BlurNode", &[concrete!("Image")])),
|
||||
metadata: Default::default(),
|
||||
},
|
||||
|
@ -101,11 +114,64 @@ fn document_node_types() -> Vec<DocumentNodeType> {
|
|||
.collect(),
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: INPUTS,
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
inputs: GAUSSIAN_BLUR_NODE_INPUTS,
|
||||
outputs: &[DocumentOutputType {
|
||||
name: "Image",
|
||||
data_type: FrontendGraphDataType::Raster,
|
||||
}],
|
||||
properties: node_properties::blur_image_properties,
|
||||
};
|
||||
vec.push(blur);
|
||||
|
||||
const INPUT_NODE_INPUTS: &[DocumentInputType] = &[
|
||||
DocumentInputType {
|
||||
name: "In",
|
||||
data_type: FrontendGraphDataType::General,
|
||||
default: NodeInput::Network,
|
||||
},
|
||||
DocumentInputType::new("Transform", TaggedValue::DAffine2(DAffine2::IDENTITY), false),
|
||||
];
|
||||
let input = DocumentNodeType {
|
||||
name: "Input",
|
||||
category: "Ignore",
|
||||
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
||||
inputs: vec![0, 1],
|
||||
outputs: vec![NodeOutput::new(0, 0), NodeOutput::new(1, 0)],
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
name: "Identity".to_string(),
|
||||
inputs: vec![NodeInput::Network],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
|
||||
metadata: Default::default(),
|
||||
},
|
||||
DocumentNode {
|
||||
name: "Identity".to_string(),
|
||||
inputs: vec![NodeInput::Network],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
|
||||
metadata: Default::default(),
|
||||
},
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (id as NodeId, node))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: INPUT_NODE_INPUTS,
|
||||
outputs: &[
|
||||
DocumentOutputType {
|
||||
name: "Image",
|
||||
data_type: FrontendGraphDataType::Raster,
|
||||
},
|
||||
DocumentOutputType {
|
||||
name: "Transform",
|
||||
data_type: FrontendGraphDataType::Number,
|
||||
},
|
||||
],
|
||||
properties: node_properties::input_properties,
|
||||
};
|
||||
vec.push(input);
|
||||
|
||||
vec
|
||||
}
|
||||
|
||||
|
@ -122,9 +188,9 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
|||
inputs: &[DocumentInputType {
|
||||
name: "In",
|
||||
data_type: FrontendGraphDataType::General,
|
||||
default: NodeInput::Node(0),
|
||||
default: NodeInput::node(0, 0),
|
||||
}],
|
||||
outputs: &[FrontendGraphDataType::General],
|
||||
outputs: &[DocumentOutputType::new("Out", FrontendGraphDataType::General)],
|
||||
properties: |_document_node, _node_id, _context| node_properties::string_properties("The identity node simply returns the input"),
|
||||
},
|
||||
DocumentNodeType {
|
||||
|
@ -132,21 +198,21 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
|||
category: "Ignore",
|
||||
identifier: NodeImplementation::proto("graphene_core::ops::IdNode", &[generic!("T")]),
|
||||
inputs: &[DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), false)],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: |_document_node, _node_id, _context| node_properties::string_properties("A bitmap image embedded in this node"),
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Input",
|
||||
category: "Ignore",
|
||||
identifier: NodeImplementation::proto("graphene_core::ops::IdNode", &[generic!("T")]),
|
||||
inputs: &[DocumentInputType {
|
||||
name: "In",
|
||||
data_type: FrontendGraphDataType::Raster,
|
||||
default: NodeInput::Network,
|
||||
}],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
properties: node_properties::input_properties,
|
||||
},
|
||||
// DocumentNodeType {
|
||||
// name: "Input",
|
||||
// category: "Ignore",
|
||||
// identifier: NodeImplementation::proto("graphene_core::ops::IdNode", &[generic!("T")]),
|
||||
// inputs: &[DocumentInputType {
|
||||
// name: "In",
|
||||
// data_type: FrontendGraphDataType::Raster,
|
||||
// default: NodeInput::Network,
|
||||
// }],
|
||||
// outputs: &[DocumentOutputType::new("Out", FrontendGraphDataType::Raster)],
|
||||
// properties: node_properties::input_properties,
|
||||
// },
|
||||
DocumentNodeType {
|
||||
name: "Output",
|
||||
category: "Ignore",
|
||||
|
@ -157,7 +223,18 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
|||
default: NodeInput::value(TaggedValue::Image(Image::empty()), true),
|
||||
}],
|
||||
outputs: &[],
|
||||
properties: |_document_node, _node_id, _context| node_properties::string_properties("The graph's output is rendered into the frame".to_string()),
|
||||
properties: |_document_node, _node_id, _context| node_properties::string_properties("The graph's output is rendered into the frame"),
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Image Frame",
|
||||
category: "General",
|
||||
identifier: NodeImplementation::proto("graphene_std::raster::ImageFrameNode<_>", &[concrete!("Image"), concrete!("DAffine2")]),
|
||||
inputs: &[
|
||||
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
|
||||
DocumentInputType::new("Transform", TaggedValue::DAffine2(DAffine2::IDENTITY), true),
|
||||
],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: |_document_node, _node_id, _context| node_properties::string_properties("Creates an embedded image with the given transform"),
|
||||
},
|
||||
DocumentNodeType {
|
||||
name: "Grayscale",
|
||||
|
@ -217,7 +294,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
|||
default: NodeInput::value(TaggedValue::F64(50.), false),
|
||||
},
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::grayscale_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
|
@ -228,7 +305,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
|||
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
|
||||
DocumentInputType::new("Luma Calculation", TaggedValue::LuminanceCalculation(LuminanceCalculation::SRGB), false),
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::luminance_properties,
|
||||
},
|
||||
#[cfg(feature = "gpu")]
|
||||
|
@ -244,7 +321,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
|||
default: NodeInput::value(TaggedValue::String(String::new()), false),
|
||||
},
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::gpu_map_properties,
|
||||
},
|
||||
#[cfg(feature = "quantization")]
|
||||
|
@ -269,7 +346,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
|||
default: NodeInput::value(TaggedValue::U32(0), false),
|
||||
},
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::quantize_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
|
@ -277,7 +354,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
|||
category: "Structural",
|
||||
identifier: NodeImplementation::proto("graphene_std::memo::CacheNode", &[concrete!("Image")]),
|
||||
inputs: &[DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true)],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
|
@ -285,7 +362,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
|||
category: "Image Adjustments",
|
||||
identifier: NodeImplementation::proto("graphene_core::raster::InvertRGBNode", &[concrete!("Image")]),
|
||||
inputs: &[DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true)],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
|
@ -301,7 +378,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
|||
DocumentInputType::new("Saturation Shift", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::new("Lightness Shift", TaggedValue::F64(0.), false),
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::adjust_hsl_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
|
@ -313,7 +390,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
|||
DocumentInputType::new("Brightness", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::new("Contrast", TaggedValue::F64(0.), false),
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::brighten_image_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
|
@ -325,7 +402,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
|||
DocumentInputType::new("Luma Calculation", TaggedValue::LuminanceCalculation(LuminanceCalculation::SRGB), false),
|
||||
DocumentInputType::new("Threshold", TaggedValue::F64(50.), false),
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::adjust_threshold_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
|
@ -336,7 +413,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
|||
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
|
||||
DocumentInputType::new("Vibrance", TaggedValue::F64(0.), false),
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::adjust_vibrance_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
|
@ -347,7 +424,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
|||
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
|
||||
DocumentInputType::new("Factor", TaggedValue::F64(100.), false),
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::multiply_opacity,
|
||||
},
|
||||
DocumentNodeType {
|
||||
|
@ -358,7 +435,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
|||
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
|
||||
DocumentInputType::new("Value", TaggedValue::F64(4.), false),
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::posterize_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
|
@ -374,7 +451,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
|||
DocumentInputType::new("Offset", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::new("Gamma Correction", TaggedValue::F64(1.), false),
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::exposure_properties,
|
||||
},
|
||||
IMAGINATE_NODE,
|
||||
|
@ -386,7 +463,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
|||
DocumentInputType::new("Input", TaggedValue::F64(0.), true),
|
||||
DocumentInputType::new("Addend", TaggedValue::F64(0.), true),
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Number],
|
||||
outputs: &[DocumentOutputType::new("Output", FrontendGraphDataType::Number)],
|
||||
properties: node_properties::add_properties,
|
||||
},
|
||||
/*DocumentNodeType {
|
||||
|
@ -394,7 +471,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
|||
category: "Vector",
|
||||
identifier: NodeImplementation::proto("graphene_std::vector::generator_nodes::UnitCircleGenerator", &[]),
|
||||
inputs: &[DocumentInputType::none()],
|
||||
outputs: &[FrontendGraphDataType::Subpath],
|
||||
outputs: &[DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
|
@ -402,7 +479,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
|||
category: "Vector",
|
||||
identifier: NodeImplementation::proto("graphene_std::vector::generator_nodes::UnitSquareGenerator", &[]),
|
||||
inputs: &[DocumentInputType::none()],
|
||||
outputs: &[FrontendGraphDataType::Subpath],
|
||||
outputs: &[DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
|
@ -414,7 +491,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
|||
data_type: FrontendGraphDataType::Subpath,
|
||||
default: NodeInput::value(TaggedValue::Subpath(Subpath::new()), false),
|
||||
}],
|
||||
outputs: &[FrontendGraphDataType::Subpath],
|
||||
outputs: &[DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::no_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
|
@ -428,7 +505,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
|||
DocumentInputType::new("Scale", TaggedValue::DVec2(DVec2::ONE), false),
|
||||
DocumentInputType::new("Skew", TaggedValue::DVec2(DVec2::ZERO), false),
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Subpath],
|
||||
outputs: &[DocumentOutputType::new("Vector", FrontendGraphDataType::Subpath)],
|
||||
properties: node_properties::transform_properties,
|
||||
},
|
||||
DocumentNodeType {
|
||||
|
@ -439,7 +516,7 @@ static STATIC_NODES: &[DocumentNodeType] = &[
|
|||
DocumentInputType::new("Image", TaggedValue::Image(Image::empty()), true),
|
||||
DocumentInputType::new("Subpath", TaggedValue::Subpath(Subpath::empty()), true),
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Vector", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::no_properties,
|
||||
},*/
|
||||
];
|
||||
|
@ -450,11 +527,12 @@ pub const IMAGINATE_NODE: DocumentNodeType = DocumentNodeType {
|
|||
identifier: NodeImplementation::proto("graphene_std::raster::ImaginateNode<_>", &[concrete!("Image"), concrete!("Option<std::sync::Arc<Image>>")]),
|
||||
inputs: &[
|
||||
DocumentInputType::new("Input Image", TaggedValue::Image(Image::empty()), true),
|
||||
DocumentInputType::new("Seed", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::new("Transform", TaggedValue::DAffine2(DAffine2::IDENTITY), true),
|
||||
DocumentInputType::new("Seed", TaggedValue::F64(0.), false), // Remember to keep index used in `NodeGraphFrameImaginateRandom` updated with this entry's index
|
||||
DocumentInputType::new("Resolution", TaggedValue::OptionalDVec2(None), false),
|
||||
DocumentInputType::new("Samples", TaggedValue::F64(30.), false),
|
||||
DocumentInputType::new("Sampling Method", TaggedValue::ImaginateSamplingMethod(ImaginateSamplingMethod::EulerA), false),
|
||||
DocumentInputType::new("Prompt Guidance", TaggedValue::F64(10.), false),
|
||||
DocumentInputType::new("Prompt Guidance", TaggedValue::F64(7.5), false),
|
||||
DocumentInputType::new("Prompt", TaggedValue::String(String::new()), false),
|
||||
DocumentInputType::new("Negative Prompt", TaggedValue::String(String::new()), false),
|
||||
DocumentInputType::new("Adapt Input Image", TaggedValue::Bool(false), false),
|
||||
|
@ -470,7 +548,7 @@ pub const IMAGINATE_NODE: DocumentNodeType = DocumentNodeType {
|
|||
DocumentInputType::new("Percent Complete", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::new("Status", TaggedValue::ImaginateStatus(ImaginateStatus::Idle), false),
|
||||
],
|
||||
outputs: &[FrontendGraphDataType::Raster],
|
||||
outputs: &[DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::imaginate_properties,
|
||||
};
|
||||
|
||||
|
@ -495,7 +573,7 @@ impl DocumentNodeType {
|
|||
NodeImplementation::ProtoNode(ident) => {
|
||||
NodeNetwork {
|
||||
inputs: (0..num_inputs).map(|_| 0).collect(),
|
||||
output: 0,
|
||||
outputs: vec![NodeOutput::new(0, 0)],
|
||||
nodes: [(
|
||||
0,
|
||||
DocumentNode {
|
||||
|
@ -515,4 +593,37 @@ impl DocumentNodeType {
|
|||
};
|
||||
DocumentNodeImplementation::Network(inner_network)
|
||||
}
|
||||
|
||||
pub fn to_document_node(&self, inputs: impl IntoIterator<Item = NodeInput>, metadata: graph_craft::document::DocumentNodeMetadata) -> DocumentNode {
|
||||
DocumentNode {
|
||||
name: self.name.to_string(),
|
||||
inputs: inputs.into_iter().collect(),
|
||||
implementation: self.generate_implementation(),
|
||||
metadata,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_image_network(output_offset: i32, output_node_id: NodeId) -> NodeNetwork {
|
||||
NodeNetwork {
|
||||
inputs: vec![0],
|
||||
outputs: vec![NodeOutput::new(1, 0)],
|
||||
nodes: [
|
||||
resolve_document_node_type("Input").expect("Input node does not exist").to_document_node(
|
||||
[NodeInput::Network, NodeInput::value(TaggedValue::DAffine2(DAffine2::IDENTITY), false)],
|
||||
DocumentNodeMetadata::position((8, 4)),
|
||||
),
|
||||
resolve_document_node_type("Output")
|
||||
.expect("Output node does not exist")
|
||||
.to_document_node([NodeInput::node(2, 0)], DocumentNodeMetadata::position((output_offset + 8, 4))),
|
||||
resolve_document_node_type("Image Frame")
|
||||
.expect("Image frame node does not exist")
|
||||
.to_document_node([NodeInput::node(output_node_id, 0), NodeInput::node(0, 1)], DocumentNodeMetadata::position((output_offset, 4))),
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(id, node)| (id as NodeId, node))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use document_legacy::layers::layer_info::LayerDataTypeDiscriminant;
|
|||
use document_legacy::Operation;
|
||||
use glam::DVec2;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{generate_uuid, DocumentNode, NodeId, NodeInput};
|
||||
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
|
||||
use graph_craft::imaginate_input::*;
|
||||
use graphene_core::raster::{Color, LuminanceCalculation};
|
||||
|
||||
|
@ -407,10 +407,10 @@ pub fn _transform_properties(document_node: &DocumentNode, node_id: NodeId, _con
|
|||
|
||||
pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
let imaginate_node = [context.nested_path, &[node_id]].concat();
|
||||
let imaginate_node_1 = imaginate_node.clone();
|
||||
let layer_path = context.layer_path.to_vec();
|
||||
|
||||
let resolve_input = |name: &str| IMAGINATE_NODE.inputs.iter().position(|input| input.name == name).unwrap_or_else(|| panic!("Input {name} not found"));
|
||||
let transform_index = resolve_input("Transform");
|
||||
let seed_index = resolve_input("Seed");
|
||||
let resolution_index = resolve_input("Resolution");
|
||||
let samples_index = resolve_input("Samples");
|
||||
|
@ -478,6 +478,9 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
|
|||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
let transform_not_connected = matches!(document_node.inputs[transform_index], NodeInput::Value { .. });
|
||||
|
||||
let progress = {
|
||||
// Since we don't serialize the status, we need to derive from other state whether the Idle state is actually supposed to be the Terminated state
|
||||
let mut interpreted_status = imaginate_status;
|
||||
|
@ -527,12 +530,15 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
|
|||
widgets.push(
|
||||
TextButton::new("Terminate")
|
||||
.tooltip("Cancel the in-progress image generation and keep the latest progress")
|
||||
.on_update(move |_| {
|
||||
DocumentMessage::NodeGraphFrameImaginateTerminate {
|
||||
layer_path: layer_path.clone(),
|
||||
node_path: imaginate_node.clone(),
|
||||
.on_update({
|
||||
let imaginate_node = imaginate_node.clone();
|
||||
move |_| {
|
||||
DocumentMessage::NodeGraphFrameImaginateTerminate {
|
||||
layer_path: layer_path.clone(),
|
||||
node_path: imaginate_node.clone(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder(),
|
||||
);
|
||||
|
@ -549,21 +555,28 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
|
|||
ImaginateStatus::Idle | ImaginateStatus::Terminated => widgets.extend_from_slice(&[
|
||||
IconButton::new("Random", 24)
|
||||
.tooltip("Generate with a new random seed")
|
||||
.on_update(move |_| {
|
||||
DocumentMessage::NodeGraphFrameImaginateRandom {
|
||||
imaginate_node: imaginate_node.clone(),
|
||||
.on_update({
|
||||
let imaginate_node = imaginate_node.clone();
|
||||
move |_| {
|
||||
DocumentMessage::NodeGraphFrameImaginateRandom {
|
||||
imaginate_node: imaginate_node.clone(),
|
||||
then_generate: true,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder(),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
TextButton::new("Generate")
|
||||
.tooltip("Fill layer frame by generating a new image")
|
||||
.on_update(move |_| {
|
||||
DocumentMessage::NodeGraphFrameImaginate {
|
||||
imaginate_node: imaginate_node_1.clone(),
|
||||
.on_update({
|
||||
let imaginate_node = imaginate_node.clone();
|
||||
move |_| {
|
||||
DocumentMessage::NodeGraphFrameImaginate {
|
||||
imaginate_node: imaginate_node.clone(),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder(),
|
||||
WidgetHolder::related_separator(),
|
||||
|
@ -590,7 +603,16 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
|
|||
WidgetHolder::unrelated_separator(),
|
||||
IconButton::new("Regenerate", 24)
|
||||
.tooltip("Set a new random seed")
|
||||
.on_update(update_value(move |_| TaggedValue::F64((generate_uuid() >> 1) as f64), node_id, seed_index))
|
||||
.on_update({
|
||||
let imaginate_node = imaginate_node.clone();
|
||||
move |_| {
|
||||
DocumentMessage::NodeGraphFrameImaginateRandom {
|
||||
imaginate_node: imaginate_node.clone(),
|
||||
then_generate: false,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
})
|
||||
.widget_holder(),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
NumberInput::new(Some(seed))
|
||||
|
@ -604,6 +626,16 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
|
|||
LayoutGroup::Row { widgets }.with_tooltip("Seed determines the random outcome, enabling limitless unique variations")
|
||||
};
|
||||
|
||||
// Get the existing layer transform
|
||||
let transform = context.document.root.transform.inverse() * context.document.multiply_transforms(context.layer_path).unwrap();
|
||||
// Create the input to the graph using an empty image
|
||||
let image_frame = std::borrow::Cow::Owned(graphene_core::raster::ImageFrame {
|
||||
image: graphene_core::raster::Image::empty(),
|
||||
transform,
|
||||
});
|
||||
// Compute the transform input to the node graph frame
|
||||
let transform: glam::DAffine2 = context.executor.compute_input(context.network, &imaginate_node, 1, image_frame).unwrap_or_default();
|
||||
|
||||
let resolution = {
|
||||
use document_legacy::document::pick_safe_imaginate_resolution;
|
||||
|
||||
|
@ -621,7 +653,6 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
|
|||
{
|
||||
let dimensions_is_auto = vec2.is_none();
|
||||
let vec2 = vec2.unwrap_or_else(|| {
|
||||
let transform = context.document.root.transform.inverse() * context.document.multiply_transforms(context.layer_path).unwrap();
|
||||
let w = transform.transform_vector2(DVec2::new(1., 0.)).length();
|
||||
let h = transform.transform_vector2(DVec2::new(0., 1.)).length();
|
||||
|
||||
|
@ -644,9 +675,21 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
|
|||
})
|
||||
.widget_holder(),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
CheckboxInput::new(!dimensions_is_auto)
|
||||
CheckboxInput::new(!dimensions_is_auto || transform_not_connected)
|
||||
.icon("Edit")
|
||||
.tooltip("Set a custom resolution instead of using the frame's rounded dimensions")
|
||||
.tooltip({
|
||||
let message = "Set a custom resolution instead of using the frame's rounded dimensions";
|
||||
let manual_message = "Set a custom resolution instead of using the frame's rounded dimensions.\n\
|
||||
\n\
|
||||
(Resolution must be set manually while the 'Transform' input is disconnected.)";
|
||||
|
||||
if transform_not_connected {
|
||||
manual_message
|
||||
} else {
|
||||
message
|
||||
}
|
||||
})
|
||||
.disabled(transform_not_connected)
|
||||
.on_update(update_value(
|
||||
move |checkbox_input: &CheckboxInput| {
|
||||
if checkbox_input.checked {
|
||||
|
@ -663,7 +706,7 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
|
|||
NumberInput::new(Some(vec2.x))
|
||||
.label("W")
|
||||
.unit(" px")
|
||||
.disabled(dimensions_is_auto)
|
||||
.disabled(dimensions_is_auto && !transform_not_connected)
|
||||
.on_update(update_value(
|
||||
move |number_input: &NumberInput| TaggedValue::OptionalDVec2(round(DVec2::new(number_input.value.unwrap(), vec2.y))),
|
||||
node_id,
|
||||
|
@ -674,7 +717,7 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
|
|||
NumberInput::new(Some(vec2.y))
|
||||
.label("H")
|
||||
.unit(" px")
|
||||
.disabled(dimensions_is_auto)
|
||||
.disabled(dimensions_is_auto && !transform_not_connected)
|
||||
.on_update(update_value(
|
||||
move |number_input: &NumberInput| TaggedValue::OptionalDVec2(round(DVec2::new(vec2.x, number_input.value.unwrap()))),
|
||||
node_id,
|
||||
|
@ -778,18 +821,22 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
|
|||
let layer_reference_input_layer_name = layer_reference_input_layer.as_ref().map(|(layer_name, _)| layer_name);
|
||||
let layer_reference_input_layer_type = layer_reference_input_layer.as_ref().map(|(_, layer_type)| layer_type);
|
||||
|
||||
widgets.extend_from_slice(&[
|
||||
WidgetHolder::unrelated_separator(),
|
||||
LayerReferenceInput::new(layer_path.clone(), layer_reference_input_layer_name.cloned(), layer_reference_input_layer_type.cloned())
|
||||
.disabled(!use_base_image)
|
||||
.on_update(update_value(|input: &LayerReferenceInput| TaggedValue::LayerPath(input.value.clone()), node_id, mask_index))
|
||||
.widget_holder(),
|
||||
]);
|
||||
widgets.push(WidgetHolder::unrelated_separator());
|
||||
if !transform_not_connected {
|
||||
widgets.push(
|
||||
LayerReferenceInput::new(layer_path.clone(), layer_reference_input_layer_name.cloned(), layer_reference_input_layer_type.cloned())
|
||||
.disabled(!use_base_image)
|
||||
.on_update(update_value(|input: &LayerReferenceInput| TaggedValue::LayerPath(input.value.clone()), node_id, mask_index))
|
||||
.widget_holder(),
|
||||
);
|
||||
} else {
|
||||
widgets.push(TextLabel::new("Requires Transform Input").italic(true).widget_holder());
|
||||
}
|
||||
}
|
||||
LayoutGroup::Row { widgets }.with_tooltip(
|
||||
"Reference to a layer or folder which masks parts of the input image. Image generation is constrained to masked areas.\n\
|
||||
\n\
|
||||
Black shapes represent the masked regions. Lighter shades of gray act as a partial mask, and colors become grayscale.",
|
||||
Black shapes represent the masked regions. Lighter shades of gray act as a partial mask, and colors become grayscale. (This is the reverse of traditional masks because it is easier to draw black shapes; this will be changed later when the mask input is a bitmap.)",
|
||||
)
|
||||
};
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
|
|||
artboard_document,
|
||||
selected_layers,
|
||||
node_graph_message_handler,
|
||||
executor,
|
||||
} = data;
|
||||
let get_document = |document_selector: TargetDocument| match document_selector {
|
||||
TargetDocument::Artboard => artboard_document,
|
||||
|
@ -166,7 +167,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
|
|||
let layer = document.layer(&path).unwrap();
|
||||
match target_document {
|
||||
TargetDocument::Artboard => register_artboard_layer_properties(layer, responses, persistent_data),
|
||||
TargetDocument::Artwork => register_artwork_layer_properties(document, path, layer, responses, persistent_data, node_graph_message_handler),
|
||||
TargetDocument::Artwork => register_artwork_layer_properties(document, path, layer, responses, persistent_data, node_graph_message_handler, executor),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ use crate::messages::layout::utility_types::widgets::input_widgets::{CheckboxInp
|
|||
use crate::messages::layout::utility_types::widgets::label_widgets::{IconLabel, TextLabel};
|
||||
use crate::messages::portfolio::utility_types::PersistentData;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::node_graph_executor::NodeGraphExecutor;
|
||||
|
||||
use document_legacy::document::Document;
|
||||
use document_legacy::layers::layer_info::{Layer, LayerDataType, LayerDataTypeDiscriminant};
|
||||
|
@ -246,6 +247,7 @@ pub fn register_artwork_layer_properties(
|
|||
responses: &mut VecDeque<Message>,
|
||||
persistent_data: &PersistentData,
|
||||
node_graph_message_handler: &NodeGraphMessageHandler,
|
||||
executor: &mut NodeGraphExecutor,
|
||||
) {
|
||||
let options_bar = vec![LayoutGroup::Row {
|
||||
widgets: vec![
|
||||
|
@ -323,6 +325,8 @@ pub fn register_artwork_layer_properties(
|
|||
responses,
|
||||
nested_path: &node_graph_message_handler.nested_path,
|
||||
layer_path: &layer_path,
|
||||
executor,
|
||||
network: &node_graph_frame.network,
|
||||
};
|
||||
node_graph_message_handler.collate_properties(node_graph_frame, &mut context, &mut properties_sections);
|
||||
|
||||
|
|
|
@ -3,13 +3,14 @@ use document_legacy::LayerId;
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::messages::prelude::NodeGraphMessageHandler;
|
||||
use crate::{messages::prelude::NodeGraphMessageHandler, node_graph_executor::NodeGraphExecutor};
|
||||
|
||||
pub struct PropertiesPanelMessageHandlerData<'a> {
|
||||
pub artwork_document: &'a DocumentLegacy,
|
||||
pub artboard_document: &'a DocumentLegacy,
|
||||
pub selected_layers: &'a mut dyn Iterator<Item = &'a [LayerId]>,
|
||||
pub node_graph_message_handler: &'a NodeGraphMessageHandler,
|
||||
pub executor: &'a mut NodeGraphExecutor,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize, specta::Type)]
|
||||
|
|
|
@ -2,37 +2,29 @@ use super::utility_types::PersistentData;
|
|||
use crate::application::generate_uuid;
|
||||
use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION};
|
||||
use crate::messages::dialog::simple_dialogs;
|
||||
use crate::messages::frontend::utility_types::{FrontendDocumentDetails, FrontendImageData};
|
||||
use crate::messages::frontend::utility_types::FrontendDocumentDetails;
|
||||
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
|
||||
use crate::messages::layout::utility_types::misc::LayoutTarget;
|
||||
use crate::messages::portfolio::document::node_graph::IMAGINATE_NODE;
|
||||
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
|
||||
use crate::messages::portfolio::document::utility_types::misc::DocumentRenderMode;
|
||||
use crate::messages::portfolio::utility_types::ImaginateServerStatus;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup};
|
||||
use document_legacy::document::pick_safe_imaginate_resolution;
|
||||
use document_legacy::layers::layer_info::{LayerDataType, LayerDataTypeDiscriminant};
|
||||
use crate::node_graph_executor::NodeGraphExecutor;
|
||||
|
||||
use document_legacy::layers::layer_info::LayerDataTypeDiscriminant;
|
||||
use document_legacy::layers::style::RenderData;
|
||||
use document_legacy::layers::text_layer::Font;
|
||||
use document_legacy::{LayerId, Operation as DocumentOperation};
|
||||
use document_legacy::Operation as DocumentOperation;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::NodeId;
|
||||
use graph_craft::document::{NodeInput, NodeNetwork};
|
||||
use graph_craft::executor::Compiler;
|
||||
use graphene_core::raster::Image;
|
||||
|
||||
use glam::DVec2;
|
||||
use interpreted_executor::executor::DynamicExecutor;
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct PortfolioMessageHandler {
|
||||
menu_bar_message_handler: MenuBarMessageHandler,
|
||||
documents: HashMap<u64, DocumentMessageHandler>,
|
||||
document_ids: Vec<u64>,
|
||||
executor: interpreted_executor::executor::DynamicExecutor,
|
||||
executor: NodeGraphExecutor,
|
||||
active_document_id: Option<u64>,
|
||||
copy_buffer: [Vec<CopyBufferEntry>; INTERNAL_CLIPBOARD_COUNT as usize],
|
||||
pub persistent_data: PersistentData,
|
||||
|
@ -50,7 +42,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
|||
PortfolioMessage::Document(message) => {
|
||||
if let Some(document_id) = self.active_document_id {
|
||||
if let Some(document) = self.documents.get_mut(&document_id) {
|
||||
document.process_message(message, responses, (document_id, ipp, &self.persistent_data, preferences))
|
||||
document.process_message(message, responses, (document_id, ipp, &self.persistent_data, preferences, &mut self.executor))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +51,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
|||
#[remain::unsorted]
|
||||
PortfolioMessage::DocumentPassMessage { document_id, message } => {
|
||||
if let Some(document) = self.documents.get_mut(&document_id) {
|
||||
document.process_message(message, responses, (document_id, ipp, &self.persistent_data, preferences))
|
||||
document.process_message(message, responses, (document_id, ipp, &self.persistent_data, preferences, &mut self.executor))
|
||||
}
|
||||
}
|
||||
PortfolioMessage::AutoSaveActiveDocument => {
|
||||
|
@ -489,7 +481,14 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
|||
size,
|
||||
imaginate_node,
|
||||
} => {
|
||||
if let Err(description) = self.evaluate_node_graph(document_id, layer_path, (image_data, size), imaginate_node, preferences, responses) {
|
||||
if let Err(description) = self.executor.evaluate_node_graph(
|
||||
(document_id, &mut self.documents),
|
||||
layer_path,
|
||||
(image_data, size),
|
||||
imaginate_node,
|
||||
(preferences, &self.persistent_data),
|
||||
responses,
|
||||
) {
|
||||
responses.push_back(
|
||||
DialogMessage::DisplayDialogError {
|
||||
title: "Unable to update node graph".to_string(),
|
||||
|
@ -681,227 +680,4 @@ impl PortfolioMessageHandler {
|
|||
fn document_index(&self, document_id: u64) -> usize {
|
||||
self.document_ids.iter().position(|id| id == &document_id).expect("Active document is missing from document ids")
|
||||
}
|
||||
|
||||
/// Execute the network by flattening it and creating a borrow stack. Casts the output to the generic `T`.
|
||||
fn execute_network<T: dyn_any::StaticType>(executor: &mut DynamicExecutor, network: NodeNetwork, image: Image) -> Result<T, String> {
|
||||
let c = Compiler {};
|
||||
let proto_network = c.compile(network, true);
|
||||
|
||||
assert_ne!(proto_network.nodes.len(), 0, "No protonodes exist?");
|
||||
executor.update(proto_network);
|
||||
|
||||
use dyn_any::IntoDynAny;
|
||||
use graph_craft::executor::Executor;
|
||||
|
||||
let boxed = executor.execute(image.into_dyn()).map_err(|e| e.to_string())?;
|
||||
|
||||
dyn_any::downcast::<T>(boxed).map(|v| *v)
|
||||
}
|
||||
|
||||
/// Computes an input for a node in the graph
|
||||
fn compute_input<T: dyn_any::StaticType>(executor: &mut DynamicExecutor, old_network: &NodeNetwork, node_path: &[NodeId], mut input_index: usize, image: Cow<Image>) -> Result<T, String> {
|
||||
let mut network = old_network.clone();
|
||||
// Adjust the output of the graph so we find the relevant output
|
||||
'outer: for end in (0..node_path.len()).rev() {
|
||||
let mut inner_network = &mut network;
|
||||
for &node_id in &node_path[..end] {
|
||||
inner_network.output = node_id;
|
||||
|
||||
let Some(new_inner) = inner_network.nodes.get_mut(&node_id).and_then(|node| node.implementation.get_network_mut()) else {
|
||||
return Err("Failed to find network".to_string());
|
||||
};
|
||||
inner_network = new_inner;
|
||||
}
|
||||
match &inner_network.nodes.get(&node_path[end]).unwrap().inputs[input_index] {
|
||||
// If the input is from a parent network then adjust the input index and continue iteration
|
||||
NodeInput::Network => {
|
||||
input_index = inner_network
|
||||
.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|&(_index, &id)| id == node_path[end])
|
||||
.nth(input_index)
|
||||
.ok_or_else(|| "Invalid network input".to_string())?
|
||||
.0;
|
||||
}
|
||||
// If the input is just a value, return that value
|
||||
NodeInput::Value { tagged_value, .. } => return dyn_any::downcast::<T>(tagged_value.clone().to_any()).map(|v| *v),
|
||||
// If the input is from a node, set the node to be the output (so that is what is evaluated)
|
||||
NodeInput::Node(n) => {
|
||||
inner_network.output = *n;
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self::execute_network(executor, network, image.into_owned())
|
||||
}
|
||||
|
||||
/// Encodes an image into a format using the image crate
|
||||
fn encode_img(image: Image, resize: Option<DVec2>, format: image::ImageOutputFormat) -> Result<(Vec<u8>, (u32, u32)), String> {
|
||||
use image::{ImageBuffer, Rgba};
|
||||
use std::io::Cursor;
|
||||
|
||||
let mut image_data: Vec<u8> = Vec::new();
|
||||
let [image_width, image_height] = [image.width, image.height];
|
||||
let size_estimate = (image_width * image_height * 4) as usize;
|
||||
|
||||
let mut result_bytes = Vec::with_capacity(size_estimate);
|
||||
result_bytes.extend(image.data.into_iter().flat_map(|color| color.to_rgba8()));
|
||||
let mut output: ImageBuffer<Rgba<u8>, _> = image::ImageBuffer::from_raw(image_width, image_height, result_bytes).ok_or_else(|| "Invalid image size".to_string())?;
|
||||
if let Some(size) = resize {
|
||||
let size = size.as_uvec2();
|
||||
if size.x > 0 && size.y > 0 {
|
||||
output = image::imageops::resize(&output, size.x, size.y, image::imageops::Triangle);
|
||||
}
|
||||
}
|
||||
let size = output.dimensions();
|
||||
output.write_to(&mut Cursor::new(&mut image_data), format).map_err(|e| e.to_string())?;
|
||||
Ok::<_, String>((image_data, size))
|
||||
}
|
||||
|
||||
/// Evaluates a node graph, computing either the imaginate node or the entire graph
|
||||
fn evaluate_node_graph(
|
||||
&mut self,
|
||||
document_id: u64,
|
||||
layer_path: Vec<LayerId>,
|
||||
(image_data, (width, height)): (Vec<u8>, (u32, u32)),
|
||||
imaginate_node: Option<Vec<NodeId>>,
|
||||
preferences: &PreferencesMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Result<(), String> {
|
||||
// Reformat the input image data into an f32 image
|
||||
let image = graphene_core::raster::Image::from_image_data(&image_data, width, height);
|
||||
|
||||
// Get the node graph layer
|
||||
let document = self.documents.get_mut(&document_id).ok_or_else(|| "Invalid document".to_string())?;
|
||||
let layer = document.document_legacy.layer(&layer_path).map_err(|e| format!("No layer: {e:?}"))?;
|
||||
let node_graph_frame = match &layer.data {
|
||||
LayerDataType::NodeGraphFrame(frame) => Ok(frame),
|
||||
_ => Err("Invalid layer type".to_string()),
|
||||
}?;
|
||||
let network = node_graph_frame.network.clone();
|
||||
|
||||
// Execute the node graph
|
||||
if let Some(imaginate_node) = imaginate_node {
|
||||
use graph_craft::imaginate_input::*;
|
||||
|
||||
let get = |name: &str| IMAGINATE_NODE.inputs.iter().position(|input| input.name == name).unwrap_or_else(|| panic!("Input {name} not found"));
|
||||
|
||||
let resolution: Option<glam::DVec2> = Self::compute_input(&mut self.executor, &network, &imaginate_node, get("Resolution"), Cow::Borrowed(&image))?;
|
||||
let resolution = resolution.unwrap_or_else(|| {
|
||||
let transform = document.document_legacy.root.transform.inverse() * document.document_legacy.multiply_transforms(&layer_path).unwrap();
|
||||
let (x, y) = pick_safe_imaginate_resolution((transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length()));
|
||||
DVec2::new(x as f64, y as f64)
|
||||
});
|
||||
|
||||
let transform = document.document_legacy.root.transform.inverse() * document.document_legacy.multiply_transforms(&layer_path).unwrap();
|
||||
let parameters = ImaginateGenerationParameters {
|
||||
seed: Self::compute_input::<f64>(&mut self.executor, &network, &imaginate_node, get("Seed"), Cow::Borrowed(&image))? as u64,
|
||||
resolution: resolution.as_uvec2().into(),
|
||||
samples: Self::compute_input::<f64>(&mut self.executor, &network, &imaginate_node, get("Samples"), Cow::Borrowed(&image))? as u32,
|
||||
sampling_method: Self::compute_input::<ImaginateSamplingMethod>(&mut self.executor, &network, &imaginate_node, get("Sampling Method"), Cow::Borrowed(&image))?
|
||||
.api_value()
|
||||
.to_string(),
|
||||
text_guidance: Self::compute_input(&mut self.executor, &network, &imaginate_node, get("Prompt Guidance"), Cow::Borrowed(&image))?,
|
||||
text_prompt: Self::compute_input(&mut self.executor, &network, &imaginate_node, get("Prompt"), Cow::Borrowed(&image))?,
|
||||
negative_prompt: Self::compute_input(&mut self.executor, &network, &imaginate_node, get("Negative Prompt"), Cow::Borrowed(&image))?,
|
||||
image_creativity: Some(Self::compute_input::<f64>(&mut self.executor, &network, &imaginate_node, get("Image Creativity"), Cow::Borrowed(&image))? / 100.),
|
||||
restore_faces: Self::compute_input(&mut self.executor, &network, &imaginate_node, get("Improve Faces"), Cow::Borrowed(&image))?,
|
||||
tiling: Self::compute_input(&mut self.executor, &network, &imaginate_node, get("Tiling"), Cow::Borrowed(&image))?,
|
||||
};
|
||||
let use_base_image = Self::compute_input::<bool>(&mut self.executor, &network, &imaginate_node, get("Adapt Input Image"), Cow::Borrowed(&image))?;
|
||||
|
||||
let base_image = if use_base_image {
|
||||
let image: Image = Self::compute_input(&mut self.executor, &network, &imaginate_node, get("Input Image"), Cow::Borrowed(&image))?;
|
||||
// Only use if has size
|
||||
if image.width > 0 && image.height > 0 {
|
||||
let (image_data, size) = Self::encode_img(image, Some(resolution), image::ImageOutputFormat::Png)?;
|
||||
let size = DVec2::new(size.0 as f64, size.1 as f64);
|
||||
let mime = "image/png".to_string();
|
||||
Some(ImaginateBaseImage { image_data, size, mime })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mask_image =
|
||||
if base_image.is_some() {
|
||||
let mask_path: Option<Vec<LayerId>> = Self::compute_input(&mut self.executor, &network, &imaginate_node, get("Masking Layer"), Cow::Borrowed(&image))?;
|
||||
|
||||
// Calculate the size of the node graph frame
|
||||
let size = DVec2::new(transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length());
|
||||
|
||||
// Render the masking layer within the node graph frame
|
||||
let old_transforms = document.remove_document_transform();
|
||||
let mask_is_some = mask_path.is_some();
|
||||
let mask_image = mask_path.filter(|mask_layer_path| document.document_legacy.layer(mask_layer_path).is_ok()).map(|mask_layer_path| {
|
||||
let render_mode = DocumentRenderMode::LayerCutout(&mask_layer_path, graphene_core::raster::color::Color::WHITE);
|
||||
let svg = document.render_document(size, transform.inverse(), &self.persistent_data, render_mode);
|
||||
|
||||
ImaginateMaskImage { svg, size }
|
||||
});
|
||||
|
||||
if mask_is_some && mask_image.is_none() {
|
||||
return Err("Imagination masking layer is missing.\nIt may have been deleted or moved. Please drag a new layer reference\ninto the 'Masking Layer' parameter input, then generate again.".to_string());
|
||||
}
|
||||
|
||||
document.restore_document_transform(old_transforms);
|
||||
mask_image
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
responses.push_back(
|
||||
FrontendMessage::TriggerImaginateGenerate {
|
||||
parameters: Box::new(parameters),
|
||||
base_image: base_image.map(Box::new),
|
||||
mask_image: mask_image.map(Box::new),
|
||||
mask_paint_mode: if Self::compute_input::<bool>(&mut self.executor, &network, &imaginate_node, get("Inpaint"), Cow::Borrowed(&image))? {
|
||||
ImaginateMaskPaintMode::Inpaint
|
||||
} else {
|
||||
ImaginateMaskPaintMode::Outpaint
|
||||
},
|
||||
mask_blur_px: Self::compute_input::<f64>(&mut self.executor, &network, &imaginate_node, get("Mask Blur"), Cow::Borrowed(&image))? as u32,
|
||||
imaginate_mask_starting_fill: Self::compute_input(&mut self.executor, &network, &imaginate_node, get("Mask Starting Fill"), Cow::Borrowed(&image))?,
|
||||
hostname: preferences.imaginate_server_hostname.clone(),
|
||||
refresh_frequency: preferences.imaginate_refresh_frequency,
|
||||
document_id,
|
||||
layer_path,
|
||||
node_path: imaginate_node,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
} else {
|
||||
let mut image: Image = Self::execute_network(&mut self.executor, network, image)?;
|
||||
|
||||
// If no image was generated, use the input image
|
||||
if image.width == 0 || image.height == 0 {
|
||||
image = graphene_core::raster::Image::from_image_data(&image_data, width, height);
|
||||
}
|
||||
|
||||
let (image_data, _size) = Self::encode_img(image, None, image::ImageOutputFormat::Bmp)?;
|
||||
|
||||
responses.push_back(
|
||||
DocumentOperation::SetNodeGraphFrameImageData {
|
||||
layer_path: layer_path.clone(),
|
||||
image_data: image_data.clone(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
let mime = "image/bmp".to_string();
|
||||
let image_data = std::sync::Arc::new(image_data);
|
||||
responses.push_back(
|
||||
FrontendMessage::UpdateImageData {
|
||||
document_id,
|
||||
image_data: vec![FrontendImageData { path: layer_path, image_data, mime }],
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
|
||||
use crate::messages::portfolio::document::node_graph::IMAGINATE_NODE;
|
||||
use crate::messages::portfolio::document::node_graph::{self, IMAGINATE_NODE};
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
|
@ -123,18 +123,14 @@ impl Fsm for ImaginateToolFsmState {
|
|||
let imaginate_node_type = IMAGINATE_NODE;
|
||||
|
||||
let mut imaginate_inputs: Vec<NodeInput> = imaginate_node_type.inputs.iter().map(|input| input.default.clone()).collect();
|
||||
imaginate_inputs[0] = NodeInput::Node(0);
|
||||
imaginate_inputs[0] = NodeInput::node(0, 0);
|
||||
imaginate_inputs[1] = NodeInput::node(0, 1);
|
||||
|
||||
let imaginate_node_id = 2;
|
||||
let mut network = NodeNetwork::new_network(32, imaginate_node_id);
|
||||
let imaginate_node_id = 100;
|
||||
let mut network = node_graph::new_image_network(32, imaginate_node_id);
|
||||
network.nodes.insert(
|
||||
imaginate_node_id,
|
||||
DocumentNode {
|
||||
name: imaginate_node_type.name.to_string(),
|
||||
inputs: imaginate_inputs,
|
||||
implementation: imaginate_node_type.generate_implementation(),
|
||||
metadata: graph_craft::document::DocumentNodeMetadata { position: (20, 4).into() },
|
||||
},
|
||||
imaginate_node_type.to_document_node(imaginate_inputs, graph_craft::document::DocumentNodeMetadata::position((20, 3))),
|
||||
);
|
||||
|
||||
responses.push_back(
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::messages::frontend::utility_types::MouseCursorIcon;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||
use crate::messages::layout::utility_types::layout_widget::PropertyHolder;
|
||||
use crate::messages::portfolio::document::node_graph;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::resize::Resize;
|
||||
use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
|
||||
|
@ -117,7 +118,7 @@ impl Fsm for NodeGraphToolFsmState {
|
|||
shape_data.path = Some(document.get_path_for_new_layer());
|
||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||
|
||||
let network = graph_craft::document::NodeNetwork::new_network(20, 0);
|
||||
let network = node_graph::new_image_network(20, 0);
|
||||
|
||||
responses.push_back(
|
||||
Operation::AddNodeGraphFrame {
|
||||
|
|
277
editor/src/node_graph_executor.rs
Normal file
277
editor/src/node_graph_executor.rs
Normal file
|
@ -0,0 +1,277 @@
|
|||
use crate::messages::frontend::utility_types::FrontendImageData;
|
||||
use crate::messages::portfolio::document::utility_types::misc::DocumentRenderMode;
|
||||
use crate::messages::portfolio::utility_types::PersistentData;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use document_legacy::{document::pick_safe_imaginate_resolution, layers::layer_info::LayerDataType};
|
||||
use document_legacy::{LayerId, Operation};
|
||||
use graph_craft::document::{generate_uuid, value::TaggedValue, NodeId, NodeInput, NodeNetwork, NodeOutput};
|
||||
use graph_craft::executor::Compiler;
|
||||
use graphene_core::raster::{Image, ImageFrame};
|
||||
use interpreted_executor::executor::DynamicExecutor;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct NodeGraphExecutor {
|
||||
executor: DynamicExecutor,
|
||||
}
|
||||
|
||||
impl NodeGraphExecutor {
|
||||
/// Sets the transform property on the input node
|
||||
fn set_input_transform(network: &mut NodeNetwork, transform: DAffine2) {
|
||||
let Some(input_node) = network.nodes.get_mut(&network.inputs[0]) else {
|
||||
return;
|
||||
};
|
||||
input_node.inputs[1] = NodeInput::value(TaggedValue::DAffine2(transform), false);
|
||||
}
|
||||
|
||||
/// Execute the network by flattening it and creating a borrow stack. Casts the output to the generic `T`.
|
||||
fn execute_network<T: dyn_any::StaticType>(&mut self, mut network: NodeNetwork, image_frame: ImageFrame) -> Result<T, String> {
|
||||
Self::set_input_transform(&mut network, image_frame.transform);
|
||||
network.duplicate_outputs(&mut generate_uuid);
|
||||
network.remove_dead_nodes();
|
||||
|
||||
// We assume only one output
|
||||
assert_eq!(network.outputs.len(), 1, "Graph with multiple outputs not yet handled");
|
||||
let c = Compiler {};
|
||||
let proto_network = c.compile_single(network, true)?;
|
||||
|
||||
assert_ne!(proto_network.nodes.len(), 0, "No protonodes exist?");
|
||||
self.executor.update(proto_network);
|
||||
|
||||
use dyn_any::IntoDynAny;
|
||||
use graph_craft::executor::Executor;
|
||||
|
||||
let boxed = self.executor.execute(image_frame.image.into_dyn()).map_err(|e| e.to_string())?;
|
||||
|
||||
dyn_any::downcast::<T>(boxed).map(|v| *v)
|
||||
}
|
||||
|
||||
/// Computes an input for a node in the graph
|
||||
pub fn compute_input<T: dyn_any::StaticType>(&mut self, old_network: &NodeNetwork, node_path: &[NodeId], mut input_index: usize, image_frame: Cow<ImageFrame>) -> Result<T, String> {
|
||||
let mut network = old_network.clone();
|
||||
// Adjust the output of the graph so we find the relevant output
|
||||
'outer: for end in (0..node_path.len()).rev() {
|
||||
let mut inner_network = &mut network;
|
||||
for &node_id in &node_path[..end] {
|
||||
inner_network.outputs[0] = NodeOutput::new(node_id, 0);
|
||||
|
||||
let Some(new_inner) = inner_network.nodes.get_mut(&node_id).and_then(|node| node.implementation.get_network_mut()) else {
|
||||
return Err("Failed to find network".to_string());
|
||||
};
|
||||
inner_network = new_inner;
|
||||
}
|
||||
match &inner_network.nodes.get(&node_path[end]).unwrap().inputs[input_index] {
|
||||
// If the input is from a parent network then adjust the input index and continue iteration
|
||||
NodeInput::Network => {
|
||||
input_index = inner_network
|
||||
.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|&(_index, &id)| id == node_path[end])
|
||||
.nth(input_index)
|
||||
.ok_or_else(|| "Invalid network input".to_string())?
|
||||
.0;
|
||||
}
|
||||
// If the input is just a value, return that value
|
||||
NodeInput::Value { tagged_value, .. } => return dyn_any::downcast::<T>(tagged_value.clone().to_any()).map(|v| *v),
|
||||
// If the input is from a node, set the node to be the output (so that is what is evaluated)
|
||||
NodeInput::Node { node_id, output_index } => {
|
||||
inner_network.outputs[0] = NodeOutput::new(*node_id, *output_index);
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.execute_network(network, image_frame.into_owned())
|
||||
}
|
||||
|
||||
/// Encodes an image into a format using the image crate
|
||||
fn encode_img(image: Image, resize: Option<DVec2>, format: image::ImageOutputFormat) -> Result<(Vec<u8>, (u32, u32)), String> {
|
||||
use image::{ImageBuffer, Rgba};
|
||||
use std::io::Cursor;
|
||||
|
||||
let (result_bytes, width, height) = image.as_flat_u8();
|
||||
|
||||
let mut output: ImageBuffer<Rgba<u8>, _> = image::ImageBuffer::from_raw(width, height, result_bytes).ok_or_else(|| "Invalid image size".to_string())?;
|
||||
if let Some(size) = resize {
|
||||
let size = size.as_uvec2();
|
||||
if size.x > 0 && size.y > 0 {
|
||||
output = image::imageops::resize(&output, size.x, size.y, image::imageops::Triangle);
|
||||
}
|
||||
}
|
||||
let size = output.dimensions();
|
||||
let mut image_data: Vec<u8> = Vec::new();
|
||||
output.write_to(&mut Cursor::new(&mut image_data), format).map_err(|e| e.to_string())?;
|
||||
Ok::<_, String>((image_data, size))
|
||||
}
|
||||
|
||||
fn generate_imaginate(
|
||||
&mut self,
|
||||
network: NodeNetwork,
|
||||
imaginate_node: Vec<NodeId>,
|
||||
(document, document_id): (&mut DocumentMessageHandler, u64),
|
||||
layer_path: Vec<LayerId>,
|
||||
image_frame: ImageFrame,
|
||||
(preferences, persistent_data): (&PreferencesMessageHandler, &PersistentData),
|
||||
) -> Result<Message, String> {
|
||||
use crate::messages::portfolio::document::node_graph::IMAGINATE_NODE;
|
||||
use graph_craft::imaginate_input::*;
|
||||
|
||||
let get = |name: &str| IMAGINATE_NODE.inputs.iter().position(|input| input.name == name).unwrap_or_else(|| panic!("Input {name} not found"));
|
||||
|
||||
let transform: DAffine2 = self.compute_input(&network, &imaginate_node, get("Transform"), Cow::Borrowed(&image_frame))?;
|
||||
|
||||
let resolution: Option<glam::DVec2> = self.compute_input(&network, &imaginate_node, get("Resolution"), Cow::Borrowed(&image_frame))?;
|
||||
let resolution = resolution.unwrap_or_else(|| {
|
||||
let (x, y) = pick_safe_imaginate_resolution((transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length()));
|
||||
DVec2::new(x as f64, y as f64)
|
||||
});
|
||||
|
||||
let parameters = ImaginateGenerationParameters {
|
||||
seed: self.compute_input::<f64>(&network, &imaginate_node, get("Seed"), Cow::Borrowed(&image_frame))? as u64,
|
||||
resolution: resolution.as_uvec2().into(),
|
||||
samples: self.compute_input::<f64>(&network, &imaginate_node, get("Samples"), Cow::Borrowed(&image_frame))? as u32,
|
||||
sampling_method: self
|
||||
.compute_input::<ImaginateSamplingMethod>(&network, &imaginate_node, get("Sampling Method"), Cow::Borrowed(&image_frame))?
|
||||
.api_value()
|
||||
.to_string(),
|
||||
text_guidance: self.compute_input(&network, &imaginate_node, get("Prompt Guidance"), Cow::Borrowed(&image_frame))?,
|
||||
text_prompt: self.compute_input(&network, &imaginate_node, get("Prompt"), Cow::Borrowed(&image_frame))?,
|
||||
negative_prompt: self.compute_input(&network, &imaginate_node, get("Negative Prompt"), Cow::Borrowed(&image_frame))?,
|
||||
image_creativity: Some(self.compute_input::<f64>(&network, &imaginate_node, get("Image Creativity"), Cow::Borrowed(&image_frame))? / 100.),
|
||||
restore_faces: self.compute_input(&network, &imaginate_node, get("Improve Faces"), Cow::Borrowed(&image_frame))?,
|
||||
tiling: self.compute_input(&network, &imaginate_node, get("Tiling"), Cow::Borrowed(&image_frame))?,
|
||||
};
|
||||
let use_base_image = self.compute_input::<bool>(&network, &imaginate_node, get("Adapt Input Image"), Cow::Borrowed(&image_frame))?;
|
||||
|
||||
let base_image = if use_base_image {
|
||||
let image: Image = self.compute_input(&network, &imaginate_node, get("Input Image"), Cow::Borrowed(&image_frame))?;
|
||||
// Only use if has size
|
||||
if image.width > 0 && image.height > 0 {
|
||||
let (image_data, size) = Self::encode_img(image, Some(resolution), image::ImageOutputFormat::Png)?;
|
||||
let size = DVec2::new(size.0 as f64, size.1 as f64);
|
||||
let mime = "image/png".to_string();
|
||||
Some(ImaginateBaseImage { image_data, size, mime })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mask_image = if base_image.is_some() {
|
||||
let mask_path: Option<Vec<LayerId>> = self.compute_input(&network, &imaginate_node, get("Masking Layer"), Cow::Borrowed(&image_frame))?;
|
||||
|
||||
// Calculate the size of the node graph frame
|
||||
let size = DVec2::new(transform.transform_vector2(DVec2::new(1., 0.)).length(), transform.transform_vector2(DVec2::new(0., 1.)).length());
|
||||
|
||||
// Render the masking layer within the node graph frame
|
||||
let old_transforms = document.remove_document_transform();
|
||||
let mask_is_some = mask_path.is_some();
|
||||
let mask_image = mask_path.filter(|mask_layer_path| document.document_legacy.layer(mask_layer_path).is_ok()).map(|mask_layer_path| {
|
||||
let render_mode = DocumentRenderMode::LayerCutout(&mask_layer_path, graphene_core::raster::color::Color::WHITE);
|
||||
let svg = document.render_document(size, transform.inverse(), persistent_data, render_mode);
|
||||
|
||||
ImaginateMaskImage { svg, size }
|
||||
});
|
||||
|
||||
if mask_is_some && mask_image.is_none() {
|
||||
return Err(
|
||||
"Imagination masking layer is missing.\nIt may have been deleted or moved. Please drag a new layer reference\ninto the 'Masking Layer' parameter input, then generate again."
|
||||
.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
document.restore_document_transform(old_transforms);
|
||||
mask_image
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(FrontendMessage::TriggerImaginateGenerate {
|
||||
parameters: Box::new(parameters),
|
||||
base_image: base_image.map(Box::new),
|
||||
mask_image: mask_image.map(Box::new),
|
||||
mask_paint_mode: if self.compute_input::<bool>(&network, &imaginate_node, get("Inpaint"), Cow::Borrowed(&image_frame))? {
|
||||
ImaginateMaskPaintMode::Inpaint
|
||||
} else {
|
||||
ImaginateMaskPaintMode::Outpaint
|
||||
},
|
||||
mask_blur_px: self.compute_input::<f64>(&network, &imaginate_node, get("Mask Blur"), Cow::Borrowed(&image_frame))? as u32,
|
||||
imaginate_mask_starting_fill: self.compute_input(&network, &imaginate_node, get("Mask Starting Fill"), Cow::Borrowed(&image_frame))?,
|
||||
hostname: preferences.imaginate_server_hostname.clone(),
|
||||
refresh_frequency: preferences.imaginate_refresh_frequency,
|
||||
document_id,
|
||||
layer_path,
|
||||
node_path: imaginate_node,
|
||||
}
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Evaluates a node graph, computing either the imaginate node or the entire graph
|
||||
pub fn evaluate_node_graph(
|
||||
&mut self,
|
||||
(document_id, documents): (u64, &mut HashMap<u64, DocumentMessageHandler>),
|
||||
layer_path: Vec<LayerId>,
|
||||
(image_data, (width, height)): (Vec<u8>, (u32, u32)),
|
||||
imaginate_node: Option<Vec<NodeId>>,
|
||||
persistent_data: (&PreferencesMessageHandler, &PersistentData),
|
||||
responses: &mut VecDeque<Message>,
|
||||
) -> Result<(), String> {
|
||||
// Reformat the input image data into an f32 image
|
||||
let image = graphene_core::raster::Image::from_image_data(&image_data, width, height);
|
||||
|
||||
// Get the node graph layer
|
||||
let document = documents.get_mut(&document_id).ok_or_else(|| "Invalid document".to_string())?;
|
||||
let layer = document.document_legacy.layer(&layer_path).map_err(|e| format!("No layer: {e:?}"))?;
|
||||
|
||||
// Construct the input image frame
|
||||
let transform = layer.transform;
|
||||
let image_frame = ImageFrame { image, transform };
|
||||
|
||||
let node_graph_frame = match &layer.data {
|
||||
LayerDataType::NodeGraphFrame(frame) => Ok(frame),
|
||||
_ => Err("Invalid layer type".to_string()),
|
||||
}?;
|
||||
let network = node_graph_frame.network.clone();
|
||||
|
||||
// Execute the node graph
|
||||
if let Some(imaginate_node) = imaginate_node {
|
||||
responses.push_back(self.generate_imaginate(network, imaginate_node, (document, document_id), layer_path, image_frame, persistent_data)?);
|
||||
} else {
|
||||
let ImageFrame { mut image, transform } = self.execute_network(network, image_frame)?;
|
||||
|
||||
// If no image was generated, use the input image
|
||||
if image.width == 0 || image.height == 0 {
|
||||
image = graphene_core::raster::Image::from_image_data(&image_data, width, height);
|
||||
}
|
||||
|
||||
let (image_data, _size) = Self::encode_img(image, None, image::ImageOutputFormat::Bmp)?;
|
||||
|
||||
responses.push_back(
|
||||
Operation::SetNodeGraphFrameImageData {
|
||||
layer_path: layer_path.clone(),
|
||||
image_data: image_data.clone(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
let mime = "image/bmp".to_string();
|
||||
let image_data = std::sync::Arc::new(image_data);
|
||||
let image_data = vec![FrontendImageData {
|
||||
path: layer_path.clone(),
|
||||
image_data,
|
||||
mime,
|
||||
}];
|
||||
responses.push_back(FrontendMessage::UpdateImageData { document_id, image_data }.into());
|
||||
|
||||
// Update the transform based on the graph output
|
||||
let transform = transform.to_cols_array();
|
||||
responses.push_back(Operation::SetLayerTransform { path: layer_path, transform }.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -92,12 +92,15 @@
|
|||
}
|
||||
|
||||
function resolveLink(link: FrontendNodeLink, containerBounds: HTMLDivElement): { nodePrimaryOutput: HTMLDivElement | undefined; nodePrimaryInput: HTMLDivElement | undefined } {
|
||||
const connectorIndex = Number(link.linkEndInputIndex);
|
||||
const outputIndex = Number(link.linkStartOutputIndex);
|
||||
const inputIndex = Number(link.linkEndInputIndex);
|
||||
|
||||
const nodePrimaryOutput = (containerBounds.querySelector(`[data-node="${String(link.linkStart)}"] [data-port="output"]`) || undefined) as HTMLDivElement | undefined;
|
||||
const nodeOutputConnectors = containerBounds.querySelectorAll(`[data-node="${String(link.linkStart)}"] [data-port="output"]`) || undefined;
|
||||
|
||||
const nodeInputConnectors = containerBounds.querySelectorAll(`[data-node="${String(link.linkEnd)}"] [data-port="input"]`) || undefined;
|
||||
const nodePrimaryInput = nodeInputConnectors?.[connectorIndex] as HTMLDivElement | undefined;
|
||||
|
||||
const nodePrimaryOutput = nodeOutputConnectors?.[outputIndex] as HTMLDivElement | undefined;
|
||||
const nodePrimaryInput = nodeInputConnectors?.[inputIndex] as HTMLDivElement | undefined;
|
||||
return { nodePrimaryOutput, nodePrimaryInput };
|
||||
}
|
||||
|
||||
|
@ -263,8 +266,8 @@
|
|||
const inputIndexInt = BigInt(inputIndex);
|
||||
const links = $nodeGraph.links;
|
||||
const linkIndex = links.findIndex((value) => value.linkEnd === nodeIdInt && value.linkEndInputIndex === inputIndexInt);
|
||||
const queryString = `[data-node="${String(links[linkIndex].linkStart)}"] [data-port="output"]`;
|
||||
linkInProgressFromConnector = (nodesContainer.querySelector(queryString) || undefined) as HTMLDivElement | undefined;
|
||||
const nodeOutputConnectors = nodesContainer.querySelectorAll(`[data-node="${String(links[linkIndex].linkStart)}"] [data-port="output"]`) || undefined;
|
||||
linkInProgressFromConnector = nodeOutputConnectors?.[Number(links[linkIndex].linkEndInputIndex)] as HTMLDivElement | undefined;
|
||||
const nodeInputConnectors = nodesContainer.querySelectorAll(`[data-node="${String(links[linkIndex].linkEnd)}"] [data-port="input"]`) || undefined;
|
||||
linkInProgressToConnector = nodeInputConnectors?.[Number(links[linkIndex].linkEndInputIndex)] as HTMLDivElement | undefined;
|
||||
disconnecting = { nodeId: nodeIdInt, inputIndex, linkIndex };
|
||||
|
@ -359,13 +362,16 @@
|
|||
|
||||
if (outputNode && inputNode && outputConnectedNodeID && inputConnectedNodeID) {
|
||||
const inputNodeInPorts = Array.from(inputNode.querySelectorAll(`[data-port="input"]`));
|
||||
const outputNodeInPorts = Array.from(outputNode.querySelectorAll(`[data-port="output"]`));
|
||||
|
||||
const inputNodeConnectionIndexSearch = inputNodeInPorts.indexOf(linkInProgressToConnector);
|
||||
const outputNodeConnectionIndexSearch = outputNodeInPorts.indexOf(linkInProgressFromConnector);
|
||||
|
||||
const inputNodeConnectionIndex = inputNodeConnectionIndexSearch > -1 ? inputNodeConnectionIndexSearch : undefined;
|
||||
const outputNodeConnectionIndex = outputNodeConnectionIndexSearch > -1 ? outputNodeConnectionIndexSearch : undefined;
|
||||
|
||||
if (inputNodeConnectionIndex !== undefined) {
|
||||
// const oneBasedIndex = inputNodeConnectionIndex + 1;
|
||||
|
||||
editor.instance.connectNodesByLink(BigInt(outputConnectedNodeID), BigInt(inputConnectedNodeID), inputNodeConnectionIndex);
|
||||
if (inputNodeConnectionIndex !== undefined && outputNodeConnectionIndex !== undefined) {
|
||||
editor.instance.connectNodesByLink(BigInt(outputConnectedNodeID), outputNodeConnectionIndex, BigInt(inputConnectedNodeID), inputNodeConnectionIndex);
|
||||
}
|
||||
}
|
||||
} else if (draggingNodes) {
|
||||
|
@ -411,8 +417,8 @@
|
|||
});
|
||||
// If the node has been dragged on top of the link then connect it into the middle.
|
||||
if (link) {
|
||||
editor.instance.connectNodesByLink(link.linkStart, selectedNodeId, 0);
|
||||
editor.instance.connectNodesByLink(selectedNodeId, link.linkEnd, Number(link.linkEndInputIndex));
|
||||
editor.instance.connectNodesByLink(link.linkStart, 0, selectedNodeId, 0);
|
||||
editor.instance.connectNodesByLink(selectedNodeId, 0, link.linkEnd, Number(link.linkEndInputIndex));
|
||||
editor.instance.shiftNode(selectedNodeId);
|
||||
}
|
||||
}
|
||||
|
@ -482,10 +488,11 @@
|
|||
{/if}
|
||||
<div class="nodes" style:transform={`scale(${transform.scale}) translate(${transform.x}px, ${transform.y}px)`} style:transform-origin={`0 0`} bind:this={nodesContainer}>
|
||||
{#each $nodeGraph.nodes as node (String(node.id))}
|
||||
{@const exposedInputsOutputs = [...node.exposedInputs, ...node.outputs.slice(1)]}
|
||||
<div
|
||||
class="node"
|
||||
class:selected={selected.includes(node.id)}
|
||||
class:output={node.output}
|
||||
class:previewed={node.previewed}
|
||||
class:disabled={node.disabled}
|
||||
style:--offset-left={(node.position?.x || 0) + (selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0)}
|
||||
style:--offset-top={(node.position?.y || 0) + (selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0)}
|
||||
|
@ -508,9 +515,9 @@
|
|||
<div
|
||||
class="output port"
|
||||
data-port="output"
|
||||
data-datatype={node.outputs[0]}
|
||||
style:--data-color={`var(--color-data-${node.outputs[0]})`}
|
||||
style:--data-color-dim={`var(--color-data-${node.outputs[0]}-dim)`}
|
||||
data-datatype={node.outputs[0].dataType}
|
||||
style:--data-color={`var(--color-data-${node.outputs[0].dataType})`}
|
||||
style:--data-color-dim={`var(--color-data-${node.outputs[0].dataType}-dim)`}
|
||||
>
|
||||
<div />
|
||||
</div>
|
||||
|
@ -519,22 +526,34 @@
|
|||
<IconLabel icon={nodeIcon(node.displayName)} />
|
||||
<TextLabel>{node.displayName}</TextLabel>
|
||||
</div>
|
||||
{#if node.exposedInputs.length > 0}
|
||||
<div class="arguments">
|
||||
{#each node.exposedInputs as argument, index (index)}
|
||||
<div class="argument">
|
||||
{#if exposedInputsOutputs.length > 0}
|
||||
<div class="parameters">
|
||||
{#each exposedInputsOutputs as parameter, index (index)}
|
||||
<div class="parameter">
|
||||
<div class="ports">
|
||||
<div
|
||||
class="input port"
|
||||
data-port="input"
|
||||
data-datatype={argument.dataType}
|
||||
style:--data-color={`var(--color-data-${argument.dataType})`}
|
||||
style:--data-color-dim={`var(--color-data-${argument.dataType}-dim)`}
|
||||
>
|
||||
<div />
|
||||
</div>
|
||||
{#if index < node.exposedInputs.length}
|
||||
<div
|
||||
class="input port"
|
||||
data-port="input"
|
||||
data-datatype={parameter.dataType}
|
||||
style:--data-color={`var(--color-data-${parameter.dataType})`}
|
||||
style:--data-color-dim={`var(--color-data-${parameter.dataType}-dim)`}
|
||||
>
|
||||
<div />
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="output port"
|
||||
data-port="output"
|
||||
data-datatype={parameter.dataType}
|
||||
style:--data-color={`var(--color-data-${parameter.dataType})`}
|
||||
style:--data-color-dim={`var(--color-data-${parameter.dataType}-dim)`}
|
||||
>
|
||||
<div />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<TextLabel>{argument.name}</TextLabel>
|
||||
<TextLabel class={index < node.exposedInputs.length ? "name" : "output"}>{parameter.name}</TextLabel>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
@ -656,7 +675,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.output {
|
||||
&.previewed {
|
||||
outline: 3px solid var(--color-data-vector);
|
||||
}
|
||||
|
||||
|
@ -679,20 +698,28 @@
|
|||
}
|
||||
}
|
||||
|
||||
.arguments {
|
||||
.parameters {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
.argument {
|
||||
.parameter {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
width: 100%;
|
||||
width: calc(100% - 24px * 2);
|
||||
margin-left: 24px;
|
||||
margin-right: 24px;
|
||||
|
||||
.text-label {
|
||||
width: 100%;
|
||||
|
||||
&.output {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Squares to cover up the rounded corners of the primary area and make them have a straight edge
|
||||
|
|
|
@ -83,6 +83,12 @@ export class NodeGraphInput {
|
|||
readonly name!: string;
|
||||
}
|
||||
|
||||
export class NodeGraphOutput {
|
||||
readonly dataType!: FrontendGraphDataType;
|
||||
|
||||
readonly name!: string;
|
||||
}
|
||||
|
||||
export class FrontendNode {
|
||||
readonly id!: bigint;
|
||||
|
||||
|
@ -92,12 +98,12 @@ export class FrontendNode {
|
|||
|
||||
readonly exposedInputs!: NodeGraphInput[];
|
||||
|
||||
readonly outputs!: FrontendGraphDataType[];
|
||||
readonly outputs!: NodeGraphOutput[];
|
||||
|
||||
@TupleToVec2
|
||||
readonly position!: XY | undefined;
|
||||
|
||||
readonly output!: boolean;
|
||||
readonly previewed!: boolean;
|
||||
|
||||
readonly disabled!: boolean;
|
||||
}
|
||||
|
@ -105,6 +111,8 @@ export class FrontendNode {
|
|||
export class FrontendNodeLink {
|
||||
readonly linkStart!: bigint;
|
||||
|
||||
readonly linkStartOutputIndex!: bigint;
|
||||
|
||||
readonly linkEnd!: bigint;
|
||||
|
||||
readonly linkEndInputIndex!: bigint;
|
||||
|
|
|
@ -578,9 +578,10 @@ impl JsEditorHandle {
|
|||
|
||||
/// Notifies the backend that the user connected a node's primary output to one of another node's inputs
|
||||
#[wasm_bindgen(js_name = connectNodesByLink)]
|
||||
pub fn connect_nodes_by_link(&self, output_node: u64, input_node: u64, input_node_connector_index: usize) {
|
||||
pub fn connect_nodes_by_link(&self, output_node: u64, output_node_connector_index: usize, input_node: u64, input_node_connector_index: usize) {
|
||||
let message = NodeGraphMessage::ConnectNodesByLink {
|
||||
output_node,
|
||||
output_node_connector_index,
|
||||
input_node,
|
||||
input_node_connector_index,
|
||||
};
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
v-for="node in nodes"
|
||||
:key="String(node.id)"
|
||||
class="node"
|
||||
:class="{ selected: selected.includes(node.id), output: node.output, disabled: node.disabled }"
|
||||
:class="{ selected: selected.includes(node.id), previewed: node.previewed, disabled: node.disabled }"
|
||||
:style="{
|
||||
'--offset-left': (node.position?.x || 0) + (selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0),
|
||||
'--offset-top': (node.position?.y || 0) + (selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0),
|
||||
|
@ -58,8 +58,8 @@
|
|||
v-if="node.outputs.length > 0"
|
||||
class="output port"
|
||||
data-port="output"
|
||||
:data-datatype="node.outputs[0]"
|
||||
:style="{ '--data-color': `var(--color-data-${node.outputs[0]})`, '--data-color-dim': `var(--color-data-${node.outputs[0]}-dim)` }"
|
||||
:data-datatype="node.outputs[0].dataType"
|
||||
:style="{ '--data-color': `var(--color-data-${node.outputs[0].dataType})`, '--data-color-dim': `var(--color-data-${node.outputs[0].dataType}-dim)` }"
|
||||
>
|
||||
<div></div>
|
||||
</div>
|
||||
|
@ -67,19 +67,32 @@
|
|||
<IconLabel :icon="nodeIcon(node.displayName)" />
|
||||
<TextLabel>{{ node.displayName }}</TextLabel>
|
||||
</div>
|
||||
<div v-if="node.exposedInputs.length > 0" class="arguments">
|
||||
<div v-for="(argument, index) in node.exposedInputs" :key="index" class="argument">
|
||||
<div v-if="[...node.exposedInputs, ...node.outputs.slice(1)].length > 0" class="parameters">
|
||||
<div v-for="(parameter, index) in [...node.exposedInputs, ...node.outputs.slice(1)]" :key="index" class="parameter">
|
||||
<div class="ports">
|
||||
<div
|
||||
v-if="index < node.exposedInputs.length"
|
||||
class="input port"
|
||||
data-port="input"
|
||||
:data-datatype="argument.dataType"
|
||||
:style="{ '--data-color': `var(--color-data-${argument.dataType})`, '--data-color-dim': `var(--color-data-${argument.dataType}-dim)` }"
|
||||
:data-datatype="parameter.dataType"
|
||||
:style="{
|
||||
'--data-color': `var(--color-data-${parameter.dataType})`,
|
||||
'--data-color-dim': `var(--color-data-${parameter.dataType}-dim)`,
|
||||
}"
|
||||
>
|
||||
<div></div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="output port"
|
||||
data-port="output"
|
||||
:data-datatype="parameter.dataType"
|
||||
:style="{ '--data-color': `var(--color-data-${parameter.dataType})`, '--data-color-dim': `var(--color-data-${parameter.dataType}-dim)` }"
|
||||
>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
<TextLabel>{{ argument.name }}</TextLabel>
|
||||
<TextLabel :class="index < node.exposedInputs.length ? 'name' : 'output'">{{ parameter.name }}</TextLabel>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -208,7 +221,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.output {
|
||||
&.previewed {
|
||||
outline: 3px solid var(--color-data-vector);
|
||||
}
|
||||
|
||||
|
@ -231,20 +244,28 @@
|
|||
}
|
||||
}
|
||||
|
||||
.arguments {
|
||||
.parameters {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
|
||||
.argument {
|
||||
.parameter {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
width: 100%;
|
||||
width: calc(100% - 24px * 2);
|
||||
margin-left: 24px;
|
||||
margin-right: 24px;
|
||||
|
||||
.text-label {
|
||||
width: 100%;
|
||||
|
||||
&.output {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Squares to cover up the rounded corners of the primary area and make them have a straight edge
|
||||
|
@ -315,8 +336,6 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, nextTick } from "vue";
|
||||
|
||||
// import type { FrontendNode } from "@/wasm-communication/messages";
|
||||
|
||||
import type { IconName } from "@/utility-functions/icons";
|
||||
|
||||
import { UpdateNodeGraphSelection, type FrontendNodeLink } from "@/wasm-communication/messages";
|
||||
|
@ -411,12 +430,15 @@ export default defineComponent({
|
|||
},
|
||||
methods: {
|
||||
resolveLink(link: FrontendNodeLink, containerBounds: HTMLDivElement): { nodePrimaryOutput: HTMLDivElement | undefined; nodePrimaryInput: HTMLDivElement | undefined } {
|
||||
const connectorIndex = Number(link.linkEndInputIndex);
|
||||
|
||||
const nodePrimaryOutput = (containerBounds.querySelector(`[data-node="${String(link.linkStart)}"] [data-port="output"]`) || undefined) as HTMLDivElement | undefined;
|
||||
const outputIndex = Number(link.linkStartOutputIndex);
|
||||
const inputIndex = Number(link.linkEndInputIndex);
|
||||
|
||||
const nodeOutputConnectors = containerBounds.querySelectorAll(`[data-node="${String(link.linkStart)}"] [data-port="output"]`) || undefined;
|
||||
const nodeInputConnectors = containerBounds.querySelectorAll(`[data-node="${String(link.linkEnd)}"] [data-port="input"]`) || undefined;
|
||||
const nodePrimaryInput = nodeInputConnectors?.[connectorIndex] as HTMLDivElement | undefined;
|
||||
|
||||
const nodePrimaryOutput = nodeOutputConnectors?.[outputIndex] as HTMLDivElement | undefined;
|
||||
const nodePrimaryInput = nodeInputConnectors?.[inputIndex] as HTMLDivElement | undefined;
|
||||
|
||||
return { nodePrimaryOutput, nodePrimaryInput };
|
||||
},
|
||||
async refreshLinks(): Promise<void> {
|
||||
|
@ -579,8 +601,8 @@ export default defineComponent({
|
|||
const inputIndexInt = BigInt(inputIndex);
|
||||
const links = this.nodeGraph.state.links;
|
||||
const linkIndex = links.findIndex((value) => value.linkEnd === nodeIdInt && value.linkEndInputIndex === inputIndexInt);
|
||||
const queryString = `[data-node="${String(links[linkIndex].linkStart)}"] [data-port="output"]`;
|
||||
this.linkInProgressFromConnector = (containerBounds.querySelector(queryString) || undefined) as HTMLDivElement | undefined;
|
||||
const nodeOutputConnectors = containerBounds.querySelectorAll(`[data-node="${String(links[linkIndex].linkStart)}"] [data-port="output"]`) || undefined;
|
||||
this.linkInProgressFromConnector = nodeOutputConnectors?.[Number(links[linkIndex].linkEndInputIndex)] as HTMLDivElement | undefined;
|
||||
const nodeInputConnectors = containerBounds.querySelectorAll(`[data-node="${String(links[linkIndex].linkEnd)}"] [data-port="input"]`) || undefined;
|
||||
this.linkInProgressToConnector = nodeInputConnectors?.[Number(links[linkIndex].linkEndInputIndex)] as HTMLDivElement | undefined;
|
||||
this.disconnecting = { nodeId: nodeIdInt, inputIndex, linkIndex };
|
||||
|
@ -674,13 +696,16 @@ export default defineComponent({
|
|||
|
||||
if (outputNode && inputNode && outputConnectedNodeID && inputConnectedNodeID) {
|
||||
const inputNodeInPorts = Array.from(inputNode.querySelectorAll(`[data-port="input"]`));
|
||||
const outputNodeInPorts = Array.from(outputNode.querySelectorAll(`[data-port="output"]`));
|
||||
|
||||
const inputNodeConnectionIndexSearch = inputNodeInPorts.indexOf(this.linkInProgressToConnector);
|
||||
const outputNodeConnectionIndexSearch = outputNodeInPorts.indexOf(this.linkInProgressFromConnector);
|
||||
|
||||
const inputNodeConnectionIndex = inputNodeConnectionIndexSearch > -1 ? inputNodeConnectionIndexSearch : undefined;
|
||||
const outputNodeConnectionIndex = outputNodeConnectionIndexSearch > -1 ? outputNodeConnectionIndexSearch : undefined;
|
||||
|
||||
if (inputNodeConnectionIndex !== undefined) {
|
||||
// const oneBasedIndex = inputNodeConnectionIndex + 1;
|
||||
|
||||
this.editor.instance.connectNodesByLink(BigInt(outputConnectedNodeID), BigInt(inputConnectedNodeID), inputNodeConnectionIndex);
|
||||
if (inputNodeConnectionIndex !== undefined && outputNodeConnectionIndex !== undefined) {
|
||||
this.editor.instance.connectNodesByLink(BigInt(outputConnectedNodeID), outputNodeConnectionIndex, BigInt(inputConnectedNodeID), inputNodeConnectionIndex);
|
||||
}
|
||||
}
|
||||
} else if (this.draggingNodes) {
|
||||
|
@ -728,8 +753,8 @@ export default defineComponent({
|
|||
});
|
||||
// If the node has been dragged on top of the link then connect it into the middle.
|
||||
if (link) {
|
||||
this.editor.instance.connectNodesByLink(link.linkStart, selectedNodeId, 0);
|
||||
this.editor.instance.connectNodesByLink(selectedNodeId, link.linkEnd, Number(link.linkEndInputIndex));
|
||||
this.editor.instance.connectNodesByLink(link.linkStart, 0, selectedNodeId, 0);
|
||||
this.editor.instance.connectNodesByLink(selectedNodeId, 0, link.linkEnd, Number(link.linkEndInputIndex));
|
||||
this.editor.instance.shiftNode(selectedNodeId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,6 +83,12 @@ export class NodeGraphInput {
|
|||
readonly name!: string;
|
||||
}
|
||||
|
||||
export class NodeGraphOutput {
|
||||
readonly dataType!: FrontendGraphDataType;
|
||||
|
||||
readonly name!: string;
|
||||
}
|
||||
|
||||
export class FrontendNode {
|
||||
readonly id!: bigint;
|
||||
|
||||
|
@ -92,12 +98,12 @@ export class FrontendNode {
|
|||
|
||||
readonly exposedInputs!: NodeGraphInput[];
|
||||
|
||||
readonly outputs!: FrontendGraphDataType[];
|
||||
readonly outputs!: NodeGraphOutput[];
|
||||
|
||||
@TupleToVec2
|
||||
readonly position!: XY | undefined;
|
||||
|
||||
readonly output!: boolean;
|
||||
readonly previewed!: boolean;
|
||||
|
||||
readonly disabled!: boolean;
|
||||
}
|
||||
|
@ -105,6 +111,8 @@ export class FrontendNode {
|
|||
export class FrontendNodeLink {
|
||||
readonly linkStart!: bigint;
|
||||
|
||||
readonly linkStartOutputIndex!: bigint;
|
||||
|
||||
readonly linkEnd!: bigint;
|
||||
|
||||
readonly linkEndInputIndex!: bigint;
|
||||
|
|
|
@ -578,9 +578,10 @@ impl JsEditorHandle {
|
|||
|
||||
/// Notifies the backend that the user connected a node's primary output to one of another node's inputs
|
||||
#[wasm_bindgen(js_name = connectNodesByLink)]
|
||||
pub fn connect_nodes_by_link(&self, output_node: u64, input_node: u64, input_node_connector_index: usize) {
|
||||
pub fn connect_nodes_by_link(&self, output_node: u64, output_node_connector_index: usize, input_node: u64, input_node_connector_index: usize) {
|
||||
let message = NodeGraphMessage::ConnectNodesByLink {
|
||||
output_node,
|
||||
output_node_connector_index,
|
||||
input_node,
|
||||
input_node_connector_index,
|
||||
};
|
||||
|
|
|
@ -9,9 +9,9 @@ fn main() {
|
|||
|
||||
let network = NodeNetwork {
|
||||
inputs: vec![0],
|
||||
output: 0,
|
||||
outputs: vec![NodeOutput::new(0, 0)],
|
||||
disabled: vec![],
|
||||
previous_output: None,
|
||||
previous_outputs: None,
|
||||
nodes: [(
|
||||
0,
|
||||
DocumentNode {
|
||||
|
@ -39,9 +39,9 @@ fn main() {
|
|||
fn add_network() -> NodeNetwork {
|
||||
NodeNetwork {
|
||||
inputs: vec![0, 0],
|
||||
output: 1,
|
||||
outputs: vec![NodeOutput::new(1, 0)],
|
||||
disabled: vec![],
|
||||
previous_output: None,
|
||||
previous_outputs: None,
|
||||
nodes: [
|
||||
(
|
||||
0,
|
||||
|
@ -56,7 +56,7 @@ fn add_network() -> NodeNetwork {
|
|||
1,
|
||||
DocumentNode {
|
||||
name: "Add".into(),
|
||||
inputs: vec![NodeInput::Node(0)],
|
||||
inputs: vec![NodeInput::node(0, 0)],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])),
|
||||
},
|
||||
|
|
|
@ -10,7 +10,7 @@ license = "MIT OR Apache-2.0"
|
|||
|
||||
[features]
|
||||
std = ["dyn-any", "dyn-any/std"]
|
||||
default = ["async", "serde", "kurbo", "log"]
|
||||
default = ["async", "serde", "kurbo", "log", "std"]
|
||||
log = ["dep:log"]
|
||||
serde = ["dep:serde", "glam/serde"]
|
||||
gpu = ["spirv-std", "bytemuck", "glam/bytemuck", "dyn-any"]
|
||||
|
|
|
@ -275,7 +275,6 @@ mod test {
|
|||
pub fn map_result() {
|
||||
let value: ClonedNode<Result<&u32, ()>> = ClonedNode(Ok(&4u32));
|
||||
assert_eq!(value.eval(()), Ok(&4u32));
|
||||
static clone: &CloneNode<u32> = &CloneNode::new();
|
||||
//let type_erased_clone = clone as &dyn for<'a> Node<'a, &'a u32, Output = u32>;
|
||||
let map_result = MapResultNode::new(ValueNode::new(FnNode::new(|x: &u32| x.clone())));
|
||||
//et type_erased = &map_result as &dyn for<'a> Node<'a, Result<&'a u32, ()>, Output = Result<u32, ()>>;
|
||||
|
|
|
@ -285,13 +285,14 @@ fn dimensions_node(input: ImageSlice<'input>) -> (u32, u32) {
|
|||
}
|
||||
|
||||
#[cfg(feature = "alloc")]
|
||||
pub use image::{CollectNode, Image, ImageRefNode, MapImageSliceNode};
|
||||
pub use image::{CollectNode, Image, ImageFrame, ImageRefNode, MapImageSliceNode};
|
||||
#[cfg(feature = "alloc")]
|
||||
mod image {
|
||||
use super::{Color, ImageSlice};
|
||||
use crate::Node;
|
||||
use alloc::vec::Vec;
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use glam::DAffine2;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DynAny, Default, specta::Type, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
|
@ -321,6 +322,15 @@ mod image {
|
|||
let data = image_data.chunks_exact(4).map(|v| Color::from_rgba8(v[0], v[1], v[2], v[3])).collect();
|
||||
Image { width, height, data }
|
||||
}
|
||||
|
||||
/// Flattens each channel cast to a u8
|
||||
pub fn as_flat_u8(self) -> (Vec<u8>, u32, u32) {
|
||||
let Image { width, height, data } = self;
|
||||
|
||||
let result_bytes = data.into_iter().flat_map(|color| color.to_rgba8()).collect();
|
||||
|
||||
(result_bytes, width, height)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Image {
|
||||
|
@ -363,6 +373,12 @@ mod image {
|
|||
data,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DynAny, Default)]
|
||||
pub struct ImageFrame {
|
||||
pub image: Image,
|
||||
pub transform: DAffine2,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -38,6 +38,12 @@ pub struct DocumentNodeMetadata {
|
|||
pub position: IVec2,
|
||||
}
|
||||
|
||||
impl DocumentNodeMetadata {
|
||||
pub fn position(position: impl Into<IVec2>) -> Self {
|
||||
Self { position: position.into() }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, specta::Type)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct DocumentNode {
|
||||
|
@ -48,7 +54,7 @@ pub struct DocumentNode {
|
|||
}
|
||||
|
||||
impl DocumentNode {
|
||||
pub fn populate_first_network_input(&mut self, node: NodeId, offset: usize) {
|
||||
pub fn populate_first_network_input(&mut self, node_id: NodeId, output_index: usize, offset: usize) {
|
||||
let input = self
|
||||
.inputs
|
||||
.iter()
|
||||
|
@ -58,7 +64,7 @@ impl DocumentNode {
|
|||
.expect("no network input");
|
||||
|
||||
let index = input.0;
|
||||
self.inputs[index] = NodeInput::Node(node);
|
||||
self.inputs[index] = NodeInput::Node { node_id, output_index };
|
||||
}
|
||||
|
||||
fn resolve_proto_node(mut self) -> ProtoNode {
|
||||
|
@ -70,7 +76,10 @@ impl DocumentNode {
|
|||
assert_eq!(self.inputs.len(), 0);
|
||||
(ProtoNodeInput::None, ConstructionArgs::Value(tagged_value))
|
||||
}
|
||||
NodeInput::Node(id) => (ProtoNodeInput::Node(id), ConstructionArgs::Nodes(vec![])),
|
||||
NodeInput::Node { node_id, output_index } => {
|
||||
assert_eq!(output_index, 0, "Outputs should be flattened before converting to protonode.");
|
||||
(ProtoNodeInput::Node(node_id), ConstructionArgs::Nodes(vec![]))
|
||||
}
|
||||
NodeInput::Network => (ProtoNodeInput::Network, ConstructionArgs::Nodes(vec![])),
|
||||
};
|
||||
assert!(!self.inputs.iter().any(|input| matches!(input, NodeInput::Network)), "recieved non resolved parameter");
|
||||
|
@ -83,7 +92,7 @@ impl DocumentNode {
|
|||
|
||||
if let ConstructionArgs::Nodes(nodes) = &mut args {
|
||||
nodes.extend(self.inputs.iter().map(|input| match input {
|
||||
NodeInput::Node(id) => *id,
|
||||
NodeInput::Node { node_id, .. } => *node_id,
|
||||
_ => unreachable!(),
|
||||
}));
|
||||
}
|
||||
|
@ -105,11 +114,11 @@ impl DocumentNode {
|
|||
P: Fn(String, usize) -> Option<NodeInput>,
|
||||
{
|
||||
for (index, input) in self.inputs.iter_mut().enumerate() {
|
||||
let &mut NodeInput::Node(id) = input else {
|
||||
let &mut NodeInput::Node{node_id: id, output_index} = input else {
|
||||
continue;
|
||||
};
|
||||
if let Some(&new_id) = new_ids.get(&id) {
|
||||
*input = NodeInput::Node(new_id);
|
||||
*input = NodeInput::Node { node_id: new_id, output_index };
|
||||
} else if let Some(new_input) = default_input(self.name.clone(), index) {
|
||||
*input = new_input;
|
||||
} else {
|
||||
|
@ -123,23 +132,26 @@ impl DocumentNode {
|
|||
#[derive(Clone, Debug, specta::Type)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum NodeInput {
|
||||
Node(NodeId),
|
||||
Node { node_id: NodeId, output_index: usize },
|
||||
Value { tagged_value: value::TaggedValue, exposed: bool },
|
||||
Network,
|
||||
}
|
||||
|
||||
impl NodeInput {
|
||||
pub const fn node(node_id: NodeId, output_index: usize) -> Self {
|
||||
Self::Node { node_id, output_index }
|
||||
}
|
||||
pub const fn value(tagged_value: value::TaggedValue, exposed: bool) -> Self {
|
||||
Self::Value { tagged_value, exposed }
|
||||
}
|
||||
fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId) {
|
||||
if let NodeInput::Node(id) = self {
|
||||
*self = NodeInput::Node(f(*id))
|
||||
if let &mut NodeInput::Node { node_id, output_index } = self {
|
||||
*self = NodeInput::Node { node_id: f(node_id), output_index }
|
||||
}
|
||||
}
|
||||
pub fn is_exposed(&self) -> bool {
|
||||
match self {
|
||||
NodeInput::Node(_) => true,
|
||||
NodeInput::Node { .. } => true,
|
||||
NodeInput::Value { exposed, .. } => *exposed,
|
||||
NodeInput::Network => false,
|
||||
}
|
||||
|
@ -149,7 +161,7 @@ impl NodeInput {
|
|||
impl PartialEq for NodeInput {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (&self, &other) {
|
||||
(Self::Node(n1), Self::Node(n2)) => n1 == n2,
|
||||
(Self::Node { node_id: n0, output_index: o0 }, Self::Node { node_id: n1, output_index: o1 }) => n0 == n1 && o0 == o1,
|
||||
(Self::Value { tagged_value: v1, .. }, Self::Value { tagged_value: v2, .. }) => v1 == v2,
|
||||
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
|
||||
}
|
||||
|
@ -181,24 +193,38 @@ impl DocumentNodeImplementation {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, DynAny, specta::Type)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct NodeOutput {
|
||||
pub node_id: NodeId,
|
||||
pub node_output_index: usize,
|
||||
}
|
||||
impl NodeOutput {
|
||||
pub fn new(node_id: NodeId, node_output_index: usize) -> Self {
|
||||
Self { node_id, node_output_index }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, DynAny, specta::Type)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct NodeNetwork {
|
||||
pub inputs: Vec<NodeId>,
|
||||
pub output: NodeId,
|
||||
pub outputs: Vec<NodeOutput>,
|
||||
pub nodes: HashMap<NodeId, DocumentNode>,
|
||||
/// These nodes are replaced with identity nodes when flattening
|
||||
pub disabled: Vec<NodeId>,
|
||||
/// In the case where a new node is chosen as output - what was the origional
|
||||
pub previous_output: Option<NodeId>,
|
||||
/// In the case where a new node is chosen as output - what was the original
|
||||
pub previous_outputs: Option<Vec<NodeOutput>>,
|
||||
}
|
||||
|
||||
impl NodeNetwork {
|
||||
pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId + Copy) {
|
||||
self.inputs.iter_mut().for_each(|id| *id = f(*id));
|
||||
self.output = f(self.output);
|
||||
self.outputs.iter_mut().for_each(|output| output.node_id = f(output.node_id));
|
||||
self.disabled.iter_mut().for_each(|id| *id = f(*id));
|
||||
self.previous_output = self.previous_output.map(f);
|
||||
self.previous_outputs
|
||||
.iter_mut()
|
||||
.for_each(|nodes| nodes.iter_mut().for_each(|output| output.node_id = f(output.node_id)));
|
||||
let mut empty = HashMap::new();
|
||||
std::mem::swap(&mut self.nodes, &mut empty);
|
||||
self.nodes = empty
|
||||
|
@ -215,7 +241,7 @@ impl NodeNetwork {
|
|||
let mut outwards_links: HashMap<u64, Vec<u64>> = HashMap::new();
|
||||
for (node_id, node) in &self.nodes {
|
||||
for input in &node.inputs {
|
||||
if let NodeInput::Node(ref_id) = input {
|
||||
if let NodeInput::Node { node_id: ref_id, .. } = input {
|
||||
outwards_links.entry(*ref_id).or_default().push(*node_id)
|
||||
}
|
||||
}
|
||||
|
@ -223,6 +249,102 @@ impl NodeNetwork {
|
|||
outwards_links
|
||||
}
|
||||
|
||||
/// When a node has multiple outputs, we actually just duplicate the node and evaluate each output separately
|
||||
pub fn duplicate_outputs(&mut self, mut gen_id: &mut impl FnMut() -> NodeId) {
|
||||
let mut duplicating_nodes = HashMap::new();
|
||||
// Find the nodes where the inputs require duplicating
|
||||
for node in &mut self.nodes.values_mut() {
|
||||
// Recursivly duplicate children
|
||||
if let DocumentNodeImplementation::Network(network) = &mut node.implementation {
|
||||
network.duplicate_outputs(gen_id);
|
||||
}
|
||||
|
||||
for input in &mut node.inputs {
|
||||
let &mut NodeInput::Node { node_id, output_index} = input else {
|
||||
continue;
|
||||
};
|
||||
// Use the initial node when getting the first output
|
||||
if output_index == 0 {
|
||||
continue;
|
||||
}
|
||||
// Get the existing duplicated node id (or create a new one)
|
||||
let duplicated_node_id = *duplicating_nodes.entry((node_id, output_index)).or_insert_with(&mut gen_id);
|
||||
// Use the first output from the duplicated node
|
||||
*input = NodeInput::node(duplicated_node_id, 0);
|
||||
}
|
||||
}
|
||||
// Find the network outputs that require duplicating
|
||||
for network_output in &mut self.outputs {
|
||||
// Use the initial node when getting the first output
|
||||
if network_output.node_output_index == 0 {
|
||||
continue;
|
||||
}
|
||||
// Get the existing duplicated node id (or create a new one)
|
||||
let duplicated_node_id = *duplicating_nodes.entry((network_output.node_id, network_output.node_output_index)).or_insert_with(&mut gen_id);
|
||||
// Use the first output from the duplicated node
|
||||
*network_output = NodeOutput::new(duplicated_node_id, 0);
|
||||
}
|
||||
// Duplicate the nodes
|
||||
for ((original_node_id, output_index), new_node_id) in duplicating_nodes {
|
||||
let Some(original_node) = self.nodes.get(&original_node_id) else {
|
||||
continue;
|
||||
};
|
||||
let mut new_node = original_node.clone();
|
||||
// Update the required outputs from a nested network to be just the relevant output
|
||||
if let DocumentNodeImplementation::Network(network) = &mut new_node.implementation {
|
||||
if network.outputs.is_empty() {
|
||||
continue;
|
||||
}
|
||||
network.outputs = vec![network.outputs[output_index]];
|
||||
}
|
||||
self.nodes.insert(new_node_id, new_node);
|
||||
}
|
||||
|
||||
// Ensure all nodes only have one output
|
||||
for node in self.nodes.values_mut() {
|
||||
if let DocumentNodeImplementation::Network(network) = &mut node.implementation {
|
||||
if network.outputs.is_empty() {
|
||||
continue;
|
||||
}
|
||||
network.outputs = vec![network.outputs[0]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes unused nodes from the graph. Returns a list of bools which represent if each of the inputs have been retained
|
||||
pub fn remove_dead_nodes(&mut self) -> Vec<bool> {
|
||||
// Take all the nodes out of the nodes list
|
||||
let mut old_nodes = std::mem::take(&mut self.nodes);
|
||||
let mut stack = self.outputs.iter().map(|output| output.node_id).collect::<Vec<_>>();
|
||||
while let Some(node_id) = stack.pop() {
|
||||
let Some((node_id, mut document_node)) = old_nodes.remove_entry(&node_id) else {
|
||||
continue;
|
||||
};
|
||||
// Remove dead nodes from child networks
|
||||
if let DocumentNodeImplementation::Network(network) = &mut document_node.implementation {
|
||||
// Remove inputs to the parent node if they have been removed from the child
|
||||
let mut retain_inputs = network.remove_dead_nodes().into_iter();
|
||||
document_node.inputs.retain(|_| retain_inputs.next().unwrap_or(true))
|
||||
}
|
||||
// Visit all nodes that this node references
|
||||
stack.extend(
|
||||
document_node
|
||||
.inputs
|
||||
.iter()
|
||||
.filter_map(|input| if let NodeInput::Node { node_id, .. } = input { Some(node_id) } else { None }),
|
||||
);
|
||||
// Add the node back to the list of nodes
|
||||
self.nodes.insert(node_id, document_node);
|
||||
}
|
||||
|
||||
// Check if inputs are used and store for return value
|
||||
let are_inputs_used = self.inputs.iter().map(|input| self.nodes.contains_key(input)).collect();
|
||||
// Remove unused inputs from graph
|
||||
self.inputs.retain(|input| self.nodes.contains_key(input));
|
||||
|
||||
are_inputs_used
|
||||
}
|
||||
|
||||
pub fn flatten(&mut self, node: NodeId) {
|
||||
self.flatten_with_fns(node, merge_ids, generate_uuid)
|
||||
}
|
||||
|
@ -254,9 +376,9 @@ impl NodeNetwork {
|
|||
for (document_input, network_input) in node.inputs.into_iter().zip(inner_network.inputs.iter()) {
|
||||
let offset = network_offsets.entry(network_input).or_insert(0);
|
||||
match document_input {
|
||||
NodeInput::Node(node) => {
|
||||
NodeInput::Node { node_id, output_index } => {
|
||||
let network_input = self.nodes.get_mut(network_input).unwrap();
|
||||
network_input.populate_first_network_input(node, *offset);
|
||||
network_input.populate_first_network_input(node_id, output_index, *offset);
|
||||
}
|
||||
NodeInput::Value { tagged_value, exposed } => {
|
||||
// Skip formatting very large values for seconds in performance speedup
|
||||
|
@ -278,7 +400,7 @@ impl NodeNetwork {
|
|||
assert!(!self.nodes.contains_key(&new_id));
|
||||
self.nodes.insert(new_id, value_node);
|
||||
let network_input = self.nodes.get_mut(network_input).unwrap();
|
||||
network_input.populate_first_network_input(new_id, *offset);
|
||||
network_input.populate_first_network_input(new_id, 0, *offset);
|
||||
}
|
||||
NodeInput::Network => {
|
||||
*network_offsets.get_mut(network_input).unwrap() += 1;
|
||||
|
@ -289,7 +411,14 @@ impl NodeNetwork {
|
|||
}
|
||||
}
|
||||
node.implementation = DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")]));
|
||||
node.inputs = vec![NodeInput::Node(inner_network.output)];
|
||||
node.inputs = inner_network
|
||||
.outputs
|
||||
.iter()
|
||||
.map(|&NodeOutput { node_id, node_output_index }| NodeInput::Node {
|
||||
node_id,
|
||||
output_index: node_output_index,
|
||||
})
|
||||
.collect();
|
||||
for node_id in new_nodes {
|
||||
self.flatten_with_fns(node_id, map_ids, gen_id);
|
||||
}
|
||||
|
@ -300,26 +429,28 @@ impl NodeNetwork {
|
|||
self.nodes.insert(id, node);
|
||||
}
|
||||
|
||||
pub fn into_proto_network(self) -> ProtoNetwork {
|
||||
pub fn into_proto_networks(self) -> impl Iterator<Item = ProtoNetwork> {
|
||||
let mut nodes: Vec<_> = self.nodes.into_iter().map(|(id, node)| (id, node.resolve_proto_node())).collect();
|
||||
nodes.sort_unstable_by_key(|(i, _)| *i);
|
||||
ProtoNetwork {
|
||||
inputs: self.inputs,
|
||||
output: self.output,
|
||||
nodes,
|
||||
}
|
||||
|
||||
// Create a network to evaluate each output
|
||||
self.outputs.into_iter().map(move |output| ProtoNetwork {
|
||||
inputs: self.inputs.clone(),
|
||||
output: output.node_id,
|
||||
nodes: nodes.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the original output node of this network, ignoring any preview node
|
||||
pub fn original_output(&self) -> NodeId {
|
||||
self.previous_output.unwrap_or(self.output)
|
||||
/// Get the original output nodes of this network, ignoring any preview node
|
||||
pub fn original_outputs(&self) -> &Vec<NodeOutput> {
|
||||
self.previous_outputs.as_ref().unwrap_or(&self.outputs)
|
||||
}
|
||||
|
||||
/// A graph with just an input and output node
|
||||
pub fn new_network(output_offset: i32, output_node_id: NodeId) -> Self {
|
||||
Self {
|
||||
inputs: vec![0],
|
||||
output: 1,
|
||||
outputs: vec![NodeOutput::new(1, 0)],
|
||||
nodes: [
|
||||
(
|
||||
0,
|
||||
|
@ -334,7 +465,7 @@ impl NodeNetwork {
|
|||
1,
|
||||
DocumentNode {
|
||||
name: "Output".into(),
|
||||
inputs: vec![NodeInput::Node(output_node_id)],
|
||||
inputs: vec![NodeInput::node(output_node_id, 0)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
|
||||
metadata: DocumentNodeMetadata { position: (output_offset, 4).into() },
|
||||
},
|
||||
|
@ -367,28 +498,27 @@ impl NodeNetwork {
|
|||
}
|
||||
|
||||
/// Check if the specified node id is connected to the output
|
||||
pub fn connected_to_output(&self, node_id: NodeId) -> bool {
|
||||
pub fn connected_to_output(&self, target_node_id: NodeId) -> bool {
|
||||
// If the node is the output then return true
|
||||
if self.output == node_id {
|
||||
if self.outputs.iter().any(|&NodeOutput { node_id, .. }| node_id == target_node_id) {
|
||||
return true;
|
||||
}
|
||||
// Get the output
|
||||
let Some(output_node) = self.nodes.get(&self.output) else {
|
||||
// Get the outputs
|
||||
let Some(mut stack) = self.outputs.iter().map(|&output| self.nodes.get(&output.node_id)).collect::<Option<Vec<_>>>() else {
|
||||
return false;
|
||||
};
|
||||
let mut stack = vec![output_node];
|
||||
let mut already_visited = HashSet::new();
|
||||
already_visited.insert(self.output);
|
||||
already_visited.extend(self.outputs.iter().map(|output| output.node_id));
|
||||
|
||||
while let Some(node) = stack.pop() {
|
||||
for input in &node.inputs {
|
||||
if let &NodeInput::Node(ref_id) = input {
|
||||
if let &NodeInput::Node { node_id: ref_id, .. } = input {
|
||||
// Skip if already viewed
|
||||
if already_visited.contains(&ref_id) {
|
||||
continue;
|
||||
}
|
||||
// If the target node is used as input then return true
|
||||
if ref_id == node_id {
|
||||
if ref_id == target_node_id {
|
||||
return true;
|
||||
}
|
||||
// Add the referenced node to the stack
|
||||
|
@ -403,6 +533,21 @@ impl NodeNetwork {
|
|||
|
||||
false
|
||||
}
|
||||
|
||||
/// Is the node being used directly as an output?
|
||||
pub fn outputs_contain(&self, node_id: NodeId) -> bool {
|
||||
self.outputs.iter().any(|output| output.node_id == node_id)
|
||||
}
|
||||
|
||||
/// Is the node being used directly as an original output?
|
||||
pub fn original_outputs_contain(&self, node_id: NodeId) -> bool {
|
||||
self.original_outputs().iter().any(|output| output.node_id == node_id)
|
||||
}
|
||||
|
||||
/// Is the node being used directly as a previous output?
|
||||
pub fn previous_outputs_contain(&self, node_id: NodeId) -> Option<bool> {
|
||||
self.previous_outputs.as_ref().map(|outputs| outputs.iter().any(|output| output.node_id == node_id))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -421,7 +566,7 @@ mod test {
|
|||
fn add_network() -> NodeNetwork {
|
||||
NodeNetwork {
|
||||
inputs: vec![0, 0],
|
||||
output: 1,
|
||||
outputs: vec![NodeOutput::new(1, 0)],
|
||||
nodes: [
|
||||
(
|
||||
0,
|
||||
|
@ -436,7 +581,7 @@ mod test {
|
|||
1,
|
||||
DocumentNode {
|
||||
name: "Add".into(),
|
||||
inputs: vec![NodeInput::Node(0)],
|
||||
inputs: vec![NodeInput::node(0, 0)],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])),
|
||||
},
|
||||
|
@ -454,7 +599,7 @@ mod test {
|
|||
network.map_ids(|id| id + 1);
|
||||
let maped_add = NodeNetwork {
|
||||
inputs: vec![1, 1],
|
||||
output: 2,
|
||||
outputs: vec![NodeOutput::new(2, 0)],
|
||||
nodes: [
|
||||
(
|
||||
1,
|
||||
|
@ -469,7 +614,7 @@ mod test {
|
|||
2,
|
||||
DocumentNode {
|
||||
name: "Add".into(),
|
||||
inputs: vec![NodeInput::Node(1)],
|
||||
inputs: vec![NodeInput::node(1, 0)],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])),
|
||||
},
|
||||
|
@ -486,7 +631,7 @@ mod test {
|
|||
fn flatten_add() {
|
||||
let mut network = NodeNetwork {
|
||||
inputs: vec![1],
|
||||
output: 1,
|
||||
outputs: vec![NodeOutput::new(1, 0)],
|
||||
nodes: [(
|
||||
1,
|
||||
DocumentNode {
|
||||
|
@ -518,7 +663,7 @@ mod test {
|
|||
fn resolve_proto_node_add() {
|
||||
let document_node = DocumentNode {
|
||||
name: "Cons".into(),
|
||||
inputs: vec![NodeInput::Network, NodeInput::Node(0)],
|
||||
inputs: vec![NodeInput::Network, NodeInput::node(0, 0)],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")])),
|
||||
};
|
||||
|
@ -568,23 +713,23 @@ mod test {
|
|||
.collect(),
|
||||
};
|
||||
let network = flat_network();
|
||||
let resolved_network = network.into_proto_network();
|
||||
let resolved_network = network.into_proto_networks().collect::<Vec<_>>();
|
||||
|
||||
println!("{:#?}", resolved_network);
|
||||
println!("{:#?}", resolved_network[0]);
|
||||
println!("{:#?}", construction_network);
|
||||
assert_eq!(resolved_network, construction_network);
|
||||
assert_eq!(resolved_network[0], construction_network);
|
||||
}
|
||||
|
||||
fn flat_network() -> NodeNetwork {
|
||||
NodeNetwork {
|
||||
inputs: vec![10],
|
||||
output: 1,
|
||||
outputs: vec![NodeOutput::new(1, 0)],
|
||||
nodes: [
|
||||
(
|
||||
1,
|
||||
DocumentNode {
|
||||
name: "Inc".into(),
|
||||
inputs: vec![NodeInput::Node(11)],
|
||||
inputs: vec![NodeInput::node(11, 0)],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
|
||||
},
|
||||
|
@ -593,7 +738,7 @@ mod test {
|
|||
10,
|
||||
DocumentNode {
|
||||
name: "Cons".into(),
|
||||
inputs: vec![NodeInput::Network, NodeInput::Node(14)],
|
||||
inputs: vec![NodeInput::Network, NodeInput::node(14, 0)],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::structural::ConsNode", &[generic!("T"), generic!("U")])),
|
||||
},
|
||||
|
@ -614,7 +759,7 @@ mod test {
|
|||
11,
|
||||
DocumentNode {
|
||||
name: "Add".into(),
|
||||
inputs: vec![NodeInput::Node(10)],
|
||||
inputs: vec![NodeInput::node(10, 0)],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[generic!("T"), generic!("U")])),
|
||||
},
|
||||
|
@ -625,4 +770,121 @@ mod test {
|
|||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn two_node_identity() -> NodeNetwork {
|
||||
NodeNetwork {
|
||||
inputs: vec![1, 2],
|
||||
outputs: vec![NodeOutput::new(1, 0), NodeOutput::new(2, 0)],
|
||||
nodes: [
|
||||
(
|
||||
1,
|
||||
DocumentNode {
|
||||
name: "Identity 1".into(),
|
||||
inputs: vec![NodeInput::Network],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
|
||||
},
|
||||
),
|
||||
(
|
||||
2,
|
||||
DocumentNode {
|
||||
name: "Identity 2".into(),
|
||||
inputs: vec![NodeInput::Network],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
|
||||
},
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn output_duplicate(network_outputs: Vec<NodeOutput>, result_node_input: NodeInput) -> NodeNetwork {
|
||||
let mut network = NodeNetwork {
|
||||
inputs: Vec::new(),
|
||||
outputs: network_outputs,
|
||||
nodes: [
|
||||
(
|
||||
10,
|
||||
DocumentNode {
|
||||
name: "Nested network".into(),
|
||||
inputs: vec![NodeInput::value(TaggedValue::F32(1.), false), NodeInput::value(TaggedValue::F32(2.), false)],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Network(two_node_identity()),
|
||||
},
|
||||
),
|
||||
(
|
||||
11,
|
||||
DocumentNode {
|
||||
name: "Result".into(),
|
||||
inputs: vec![result_node_input],
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::IdNode", &[generic!("T")])),
|
||||
},
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
..Default::default()
|
||||
};
|
||||
let mut new_ids = 101..;
|
||||
network.duplicate_outputs(&mut || new_ids.next().unwrap());
|
||||
network.remove_dead_nodes();
|
||||
network
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_duplicate() {
|
||||
let result = output_duplicate(vec![NodeOutput::new(10, 1)], NodeInput::node(10, 0));
|
||||
assert_eq!(result.outputs.len(), 1, "The number of outputs should remain as 1");
|
||||
assert_eq!(result.outputs[0], NodeOutput::new(101, 0), "The outer network output should be from a duplicated inner network");
|
||||
assert_eq!(result.nodes.keys().copied().collect::<Vec<_>>(), vec![101], "Should just call nested network");
|
||||
let nested_network_node = result.nodes.get(&101).unwrap();
|
||||
assert_eq!(nested_network_node.name, "Nested network".to_string(), "Name should not change");
|
||||
assert_eq!(nested_network_node.inputs, vec![NodeInput::value(TaggedValue::F32(2.), false)], "Input should be 2");
|
||||
let inner_network = nested_network_node.implementation.get_network().expect("Implementation should be network");
|
||||
assert_eq!(inner_network.inputs, vec![2], "The input should be sent to the second node");
|
||||
assert_eq!(inner_network.outputs, vec![NodeOutput::new(2, 0)], "The output should be node id 2");
|
||||
assert_eq!(inner_network.nodes.get(&2).unwrap().name, "Identity 2", "The node should be identity 2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn out_of_order_duplicate() {
|
||||
let result = output_duplicate(vec![NodeOutput::new(10, 1), NodeOutput::new(10, 0)], NodeInput::node(10, 0));
|
||||
assert_eq!(result.outputs[0], NodeOutput::new(101, 0), "The first network output should be from a duplicated nested network");
|
||||
assert_eq!(result.outputs[1], NodeOutput::new(10, 0), "The second network output should be from the original nested network");
|
||||
assert!(
|
||||
result.nodes.contains_key(&10) && result.nodes.contains_key(&101) && result.nodes.len() == 2,
|
||||
"Network should contain two duplicated nodes"
|
||||
);
|
||||
for (node_id, input_value, inner_id) in [(10, 1., 1), (101, 2., 2)] {
|
||||
let nested_network_node = result.nodes.get(&node_id).unwrap();
|
||||
assert_eq!(nested_network_node.name, "Nested network".to_string(), "Name should not change");
|
||||
assert_eq!(nested_network_node.inputs, vec![NodeInput::value(TaggedValue::F32(input_value), false)], "Input should be stable");
|
||||
let inner_network = nested_network_node.implementation.get_network().expect("Implementation should be network");
|
||||
assert_eq!(inner_network.inputs, vec![inner_id], "The input should be sent to the second node");
|
||||
assert_eq!(inner_network.outputs, vec![NodeOutput::new(inner_id, 0)], "The output should be node id");
|
||||
assert_eq!(inner_network.nodes.get(&inner_id).unwrap().name, format!("Identity {inner_id}"), "The node should be identity");
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn using_other_node_duplicate() {
|
||||
let result = output_duplicate(vec![NodeOutput::new(11, 0)], NodeInput::node(10, 1));
|
||||
assert_eq!(result.outputs, vec![NodeOutput::new(11, 0)], "The network output should be the result node");
|
||||
assert!(
|
||||
result.nodes.contains_key(&11) && result.nodes.contains_key(&101) && result.nodes.len() == 2,
|
||||
"Network should contain a duplicated node and a result node"
|
||||
);
|
||||
let result_node = result.nodes.get(&11).unwrap();
|
||||
assert_eq!(result_node.inputs, vec![NodeInput::node(101, 0)], "Result node should refer to duplicate node as input");
|
||||
let nested_network_node = result.nodes.get(&101).unwrap();
|
||||
assert_eq!(nested_network_node.name, "Nested network".to_string(), "Name should not change");
|
||||
assert_eq!(nested_network_node.inputs, vec![NodeInput::value(TaggedValue::F32(2.), false)], "Input should be 2");
|
||||
let inner_network = nested_network_node.implementation.get_network().expect("Implementation should be network");
|
||||
assert_eq!(inner_network.inputs, vec![2], "The input should be sent to the second node");
|
||||
assert_eq!(inner_network.outputs, vec![NodeOutput::new(2, 0)], "The output should be node id 2");
|
||||
assert_eq!(inner_network.nodes.get(&2).unwrap().name, "Identity 2", "The node should be identity 2");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
pub use dyn_any::StaticType;
|
||||
use dyn_any::{DynAny, Upcast};
|
||||
use dyn_clone::DynClone;
|
||||
pub use glam::DVec2;
|
||||
pub use glam::{DAffine2, DVec2};
|
||||
use graphene_core::raster::LuminanceCalculation;
|
||||
use graphene_core::Node;
|
||||
use std::hash::Hash;
|
||||
|
@ -22,6 +22,7 @@ pub enum TaggedValue {
|
|||
Bool(bool),
|
||||
DVec2(DVec2),
|
||||
OptionalDVec2(Option<DVec2>),
|
||||
DAffine2(DAffine2),
|
||||
Image(graphene_core::raster::Image),
|
||||
RcImage(Option<Arc<graphene_core::raster::Image>>),
|
||||
Color(graphene_core::raster::color::Color),
|
||||
|
@ -68,44 +69,48 @@ impl Hash for TaggedValue {
|
|||
8.hash(state);
|
||||
Self::DVec2(*v).hash(state)
|
||||
}
|
||||
Self::Image(i) => {
|
||||
Self::DAffine2(m) => {
|
||||
9.hash(state);
|
||||
i.hash(state)
|
||||
m.to_cols_array().iter().for_each(|x| x.to_bits().hash(state))
|
||||
}
|
||||
Self::RcImage(i) => {
|
||||
Self::Image(i) => {
|
||||
10.hash(state);
|
||||
i.hash(state)
|
||||
}
|
||||
Self::Color(c) => {
|
||||
Self::RcImage(i) => {
|
||||
11.hash(state);
|
||||
i.hash(state)
|
||||
}
|
||||
Self::Color(c) => {
|
||||
12.hash(state);
|
||||
c.hash(state)
|
||||
}
|
||||
Self::Subpath(s) => {
|
||||
12.hash(state);
|
||||
s.hash(state)
|
||||
}
|
||||
Self::RcSubpath(s) => {
|
||||
13.hash(state);
|
||||
s.hash(state)
|
||||
}
|
||||
Self::LuminanceCalculation(l) => {
|
||||
Self::RcSubpath(s) => {
|
||||
14.hash(state);
|
||||
s.hash(state)
|
||||
}
|
||||
Self::LuminanceCalculation(l) => {
|
||||
15.hash(state);
|
||||
l.hash(state)
|
||||
}
|
||||
Self::ImaginateSamplingMethod(m) => {
|
||||
15.hash(state);
|
||||
16.hash(state);
|
||||
m.hash(state)
|
||||
}
|
||||
Self::ImaginateMaskStartingFill(f) => {
|
||||
16.hash(state);
|
||||
17.hash(state);
|
||||
f.hash(state)
|
||||
}
|
||||
Self::ImaginateStatus(s) => {
|
||||
17.hash(state);
|
||||
18.hash(state);
|
||||
s.hash(state)
|
||||
}
|
||||
Self::LayerPath(p) => {
|
||||
18.hash(state);
|
||||
19.hash(state);
|
||||
p.hash(state)
|
||||
}
|
||||
}
|
||||
|
@ -124,6 +129,7 @@ impl<'a> TaggedValue {
|
|||
TaggedValue::Bool(x) => Box::new(x),
|
||||
TaggedValue::DVec2(x) => Box::new(x),
|
||||
TaggedValue::OptionalDVec2(x) => Box::new(x),
|
||||
TaggedValue::DAffine2(x) => Box::new(x),
|
||||
TaggedValue::Image(x) => Box::new(x),
|
||||
TaggedValue::RcImage(x) => Box::new(x),
|
||||
TaggedValue::Color(x) => Box::new(x),
|
||||
|
|
|
@ -8,21 +8,29 @@ use crate::proto::ProtoNetwork;
|
|||
pub struct Compiler {}
|
||||
|
||||
impl Compiler {
|
||||
pub fn compile(&self, mut network: NodeNetwork, resolve_inputs: bool) -> ProtoNetwork {
|
||||
pub fn compile(&self, mut network: NodeNetwork, resolve_inputs: bool) -> impl Iterator<Item = ProtoNetwork> {
|
||||
let node_ids = network.nodes.keys().copied().collect::<Vec<_>>();
|
||||
println!("flattening");
|
||||
for id in node_ids {
|
||||
network.flatten(id);
|
||||
}
|
||||
let mut proto_network = network.into_proto_network();
|
||||
if resolve_inputs {
|
||||
println!("resolving inputs");
|
||||
proto_network.resolve_inputs();
|
||||
}
|
||||
println!("reordering ids");
|
||||
proto_network.reorder_ids();
|
||||
proto_network.generate_stable_node_ids();
|
||||
proto_network
|
||||
let proto_networks = network.into_proto_networks();
|
||||
proto_networks.map(move |mut proto_network| {
|
||||
if resolve_inputs {
|
||||
println!("resolving inputs");
|
||||
proto_network.resolve_inputs();
|
||||
}
|
||||
proto_network.reorder_ids();
|
||||
proto_network.generate_stable_node_ids();
|
||||
proto_network
|
||||
})
|
||||
}
|
||||
pub fn compile_single(&self, network: NodeNetwork, resolve_inputs: bool) -> Result<ProtoNetwork, String> {
|
||||
assert_eq!(network.outputs.len(), 1, "Graph with multiple outputs not yet handled");
|
||||
let Some(proto_network) = self.compile(network, resolve_inputs).next() else {
|
||||
return Err("Failed to convert graph into proto graph".to_string());
|
||||
};
|
||||
Ok(proto_network)
|
||||
}
|
||||
}
|
||||
pub type Any<'a> = Box<dyn DynAny<'a> + 'a>;
|
||||
|
|
|
@ -94,7 +94,7 @@ pub struct ProtoNetwork {
|
|||
pub nodes: Vec<(NodeId, ProtoNode)>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ConstructionArgs {
|
||||
Value(value::TaggedValue),
|
||||
Nodes(Vec<NodeId>),
|
||||
|
@ -133,7 +133,7 @@ impl ConstructionArgs {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct ProtoNode {
|
||||
pub construction_args: ConstructionArgs,
|
||||
pub input: ProtoNodeInput,
|
||||
|
|
|
@ -73,7 +73,7 @@ where
|
|||
N: Node<'input, Any<'input>, Output = Any<'input>>,
|
||||
{
|
||||
let node_name = core::any::type_name::<N>();
|
||||
let out = dyn_any::downcast(node.eval(input)).unwrap_or_else(|e| panic!("DynAnyNode Input {e} in:\n{node_name}"));
|
||||
let out = dyn_any::downcast(node.eval(input)).unwrap_or_else(|e| panic!("DowncastNode Input {e} in:\n{node_name}"));
|
||||
*out
|
||||
}
|
||||
|
||||
|
@ -91,7 +91,7 @@ impl<'n: 'input, 'input, O: 'input + StaticType, I: 'input + StaticType> Node<'i
|
|||
fn eval<'node: 'input>(&'node self, input: I) -> Self::Output {
|
||||
{
|
||||
let input = Box::new(input);
|
||||
let out = dyn_any::downcast(self.node.eval(input)).unwrap_or_else(|e| panic!("DynAnyNode Input {e}"));
|
||||
let out = dyn_any::downcast(self.node.eval(input)).unwrap_or_else(|e| panic!("DowncastBothNode Input {e}"));
|
||||
*out
|
||||
}
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ impl<'n: 'input, 'input, O: 'input + StaticType, I: 'input + StaticType> Node<'i
|
|||
fn eval<'node: 'input>(&'node self, input: I) -> Self::Output {
|
||||
{
|
||||
let input = Box::new(input);
|
||||
let out: Box<&_> = dyn_any::downcast::<&O>(self.node.eval(input)).unwrap_or_else(|e| panic!("DynAnyNode Input {e}"));
|
||||
let out: Box<&_> = dyn_any::downcast::<&O>(self.node.eval(input)).unwrap_or_else(|e| panic!("DowncastBothRefNode Input {e}"));
|
||||
*out
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use dyn_any::{DynAny, StaticType};
|
||||
|
||||
use glam::DAffine2;
|
||||
use graphene_core::raster::{Color, Image};
|
||||
use graphene_core::Node;
|
||||
|
||||
|
@ -117,6 +118,14 @@ fn imaginate(image: Image, cached: Option<std::sync::Arc<graphene_core::raster::
|
|||
cached.map(|mut x| std::sync::Arc::make_mut(&mut x).clone()).unwrap_or(image)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ImageFrameNode<Transform> {
|
||||
transform: Transform,
|
||||
}
|
||||
#[node_macro::node_fn(ImageFrameNode)]
|
||||
fn image_frame(image: Image, transform: DAffine2) -> graphene_core::raster::ImageFrame {
|
||||
graphene_core::raster::ImageFrame { image, transform }
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ quantization = ["graphene-std/quantization"]
|
|||
graphene-core = { path = "../gcore", features = ["async", "std" ] }
|
||||
graphene-std = { path = "../gstd" }
|
||||
graph-craft = { path = "../graph-craft" }
|
||||
dyn-any = { path = "../../libraries/dyn-any", features = ["log-bad-types"] }
|
||||
dyn-any = { path = "../../libraries/dyn-any", features = ["log-bad-types", "glam"] }
|
||||
num-traits = "0.2"
|
||||
borrow_stack = { path = "../borrow_stack" }
|
||||
dyn-clone = "1.0"
|
||||
|
|
|
@ -26,7 +26,7 @@ impl DynamicExecutor {
|
|||
|
||||
pub fn update(&mut self, proto_network: ProtoNetwork) {
|
||||
self.output = proto_network.output;
|
||||
info!("setting output to {}", self.output);
|
||||
trace!("setting output to {}", self.output);
|
||||
self.tree.update(proto_network);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ mod tests {
|
|||
fn add_network() -> NodeNetwork {
|
||||
NodeNetwork {
|
||||
inputs: vec![0, 0],
|
||||
output: 1,
|
||||
outputs: vec![NodeOutput::new(1, 0)],
|
||||
nodes: [
|
||||
(
|
||||
0,
|
||||
|
@ -67,7 +67,7 @@ mod tests {
|
|||
1,
|
||||
DocumentNode {
|
||||
name: "Add".into(),
|
||||
inputs: vec![NodeInput::Node(0)],
|
||||
inputs: vec![NodeInput::node(0, 0)],
|
||||
implementation: DocumentNodeImplementation::Unresolved(NodeIdentifier::new("graphene_core::ops::AddNode", &[concrete!("(u32, u32)")])),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
},
|
||||
|
@ -81,7 +81,7 @@ mod tests {
|
|||
|
||||
let network = NodeNetwork {
|
||||
inputs: vec![0],
|
||||
output: 0,
|
||||
outputs: vec![NodeOutput::new(0, 0)],
|
||||
nodes: [(
|
||||
0,
|
||||
DocumentNode {
|
||||
|
@ -106,7 +106,7 @@ mod tests {
|
|||
use graph_craft::executor::{Compiler, Executor};
|
||||
|
||||
let compiler = Compiler {};
|
||||
let protograph = compiler.compile(network, true);
|
||||
let protograph = compiler.compile_single(network, true).expect("Graph should be generated");
|
||||
|
||||
let exec = DynamicExecutor::new(protograph);
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use glam::DAffine2;
|
||||
use graphene_core::ops::{CloneNode, IdNode, TypeNode};
|
||||
use graphene_core::raster::color::Color;
|
||||
use graphene_core::raster::*;
|
||||
|
@ -83,7 +84,7 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[
|
|||
(
|
||||
NodeIdentifier::new("graphene_std::raster::ImaginateNode<_>", &[concrete!("Image"), concrete!("Option<std::sync::Arc<Image>>")]),
|
||||
|args| {
|
||||
let cached = graphene_std::any::input_node::<Option<std::sync::Arc<Image>>>(args[15]);
|
||||
let cached = graphene_std::any::input_node::<Option<std::sync::Arc<Image>>>(args[16]);
|
||||
let node = graphene_std::raster::ImaginateNode::new(cached);
|
||||
let any = DynAnyNode::new(ValueNode::new(node));
|
||||
any.into_type_erased()
|
||||
|
@ -126,6 +127,7 @@ static NODE_REGISTRY: &[(NodeIdentifier, NodeConstructor)] = &[
|
|||
any.into_type_erased()
|
||||
}),
|
||||
register_node!(graphene_core::structural::ConsNode<_, _>, input: Image, params: [&str]),
|
||||
register_node!(graphene_std::raster::ImageFrameNode<_>, input: Image, params: [DAffine2]),
|
||||
/*
|
||||
(NodeIdentifier::new("graphene_std::raster::ImageNode", &[concrete!("&str")]), |_proto_node, stack| {
|
||||
stack.push_fn(|_nodes| {
|
||||
|
|
|
@ -13,7 +13,7 @@ fn has_attribute(attrs: &[Attribute], target: &str) -> bool {
|
|||
|
||||
/// Make setting strings easier by allowing all types that `impl Into<String>`
|
||||
///
|
||||
/// Returns the new input type and a conversion to the origional.
|
||||
/// Returns the new input type and a conversion to the original.
|
||||
fn easier_string_assignment(field_ty: &Type, field_ident: &Ident) -> (TokenStream2, TokenStream2) {
|
||||
if let Type::Path(type_path) = field_ty {
|
||||
if let Some(last_segment) = type_path.path.segments.last() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue