mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 23:08:05 +08:00
Fix Imaginate by porting its JS roundtrip code to graph-based async execution in Rust (#1250)
* Create asynchronous rust imaginate node * Make a first imaginate request via rust * Implement parsing of imaginate API result image * Stop refresh timer from affecting imaginate progress requests * Add cargo-about clarification for rustls-webpki * Delete imaginate.ts and all uses of its functions * Add imaginate img2img feature * Fix imaginate random seed button * Fix imaginate ui inferring non-custom resolutions * Fix the imaginate progress indicator * Remove ImaginatePreferences from being compiled into node graph * Regenerate imaginate only when hitting button * Add ability to terminate imaginate requests * Add imaginate server check feature * Do not compile wasm_bindgen bindings in graphite_editor for tests * Address some review suggestions - move wasm futures dependency in editor to the future-executor crate - guard wasm-bindgen in editor behind a `wasm` feature flag - dont make seed number input a slider - remove poll_server_check from process_message function beginning - guard wasm related code behind `cfg(target_arch = "wasm32")` instead of `cfg(test)` - Call the imaginate idle states "Ready" and "Done" instead of "Nothing to do" - Call the imaginate uploading state "Uploading Image" instead of "Uploading Input Image" - Remove the EvalSyncNode * Fix imaginate host name being restored between graphite instances also change the progress status texts a bit. --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
@@ -9,7 +9,6 @@ use crate::messages::tool::utility_types::HintData;
|
||||
|
||||
use document_legacy::LayerId;
|
||||
use graph_craft::document::NodeId;
|
||||
use graph_craft::imaginate_input::*;
|
||||
use graphene_core::raster::color::Color;
|
||||
use graphene_core::text::Font;
|
||||
|
||||
@@ -75,40 +74,6 @@ pub enum FrontendMessage {
|
||||
#[serde(rename = "isDefault")]
|
||||
is_default: bool,
|
||||
},
|
||||
TriggerImaginateCheckServerStatus {
|
||||
hostname: String,
|
||||
},
|
||||
TriggerImaginateGenerate {
|
||||
parameters: Box<ImaginateGenerationParameters>,
|
||||
#[serde(rename = "baseImage")]
|
||||
base_image: Option<Box<ImaginateBaseImage>>,
|
||||
#[serde(rename = "maskImage")]
|
||||
mask_image: Option<Box<ImaginateMaskImage>>,
|
||||
#[serde(rename = "maskPaintMode")]
|
||||
mask_paint_mode: ImaginateMaskPaintMode,
|
||||
#[serde(rename = "maskBlurPx")]
|
||||
mask_blur_px: u32,
|
||||
#[serde(rename = "maskFillContent")]
|
||||
imaginate_mask_starting_fill: ImaginateMaskStartingFill,
|
||||
hostname: String,
|
||||
#[serde(rename = "refreshFrequency")]
|
||||
refresh_frequency: f64,
|
||||
#[serde(rename = "documentId")]
|
||||
document_id: u64,
|
||||
#[serde(rename = "layerPath")]
|
||||
layer_path: Vec<LayerId>,
|
||||
#[serde(rename = "nodePath")]
|
||||
node_path: Vec<NodeId>,
|
||||
},
|
||||
TriggerImaginateTerminate {
|
||||
#[serde(rename = "documentId")]
|
||||
document_id: u64,
|
||||
#[serde(rename = "layerPath")]
|
||||
layer_path: Vec<LayerId>,
|
||||
#[serde(rename = "nodePath")]
|
||||
node_path: Vec<NodeId>,
|
||||
hostname: String,
|
||||
},
|
||||
TriggerImport,
|
||||
TriggerIndexedDbRemoveDocument {
|
||||
#[serde(rename = "documentId")]
|
||||
|
||||
@@ -93,8 +93,6 @@ pub enum DocumentMessage {
|
||||
GroupSelectedLayers,
|
||||
ImaginateClear {
|
||||
layer_path: Vec<LayerId>,
|
||||
node_id: NodeId,
|
||||
cached_index: usize,
|
||||
},
|
||||
ImaginateGenerate {
|
||||
layer_path: Vec<LayerId>,
|
||||
@@ -105,10 +103,6 @@ pub enum DocumentMessage {
|
||||
imaginate_node: Vec<NodeId>,
|
||||
then_generate: bool,
|
||||
},
|
||||
ImaginateTerminate {
|
||||
layer_path: Vec<LayerId>,
|
||||
node_path: Vec<NodeId>,
|
||||
},
|
||||
InputFrameRasterizeRegionBelowLayer {
|
||||
layer_path: Vec<LayerId>,
|
||||
},
|
||||
|
||||
@@ -465,15 +465,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||
replacement_selected_layers: vec![new_folder_path],
|
||||
});
|
||||
}
|
||||
ImaginateClear {
|
||||
layer_path,
|
||||
node_id,
|
||||
cached_index: input_index,
|
||||
} => {
|
||||
let value = graph_craft::document::value::TaggedValue::RcImage(None);
|
||||
responses.add(NodeGraphMessage::SetInputValue { node_id, input_index, value });
|
||||
responses.add(InputFrameRasterizeRegionBelowLayer { layer_path });
|
||||
}
|
||||
ImaginateClear { layer_path } => responses.add(InputFrameRasterizeRegionBelowLayer { layer_path }),
|
||||
ImaginateGenerate { layer_path, imaginate_node } => {
|
||||
if let Some(message) = self.rasterize_region_below_layer(document_id, layer_path, preferences, persistent_data, Some(imaginate_node)) {
|
||||
responses.add(message);
|
||||
@@ -484,12 +476,16 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||
imaginate_node,
|
||||
then_generate,
|
||||
} => {
|
||||
// Generate a random seed. We only want values between -2^53 and 2^53, because integer values
|
||||
// outside of this range can get rounded in f64
|
||||
let random_bits = generate_uuid();
|
||||
let random_value = ((random_bits >> 11) as f64).copysign(f64::from_bits(random_bits & (1 << 63)));
|
||||
// Set a random seed input
|
||||
responses.add(NodeGraphMessage::SetInputValue {
|
||||
node_id: *imaginate_node.last().unwrap(),
|
||||
// Needs to match the index of the seed parameter in `pub const IMAGINATE_NODE: DocumentNodeType` in `document_node_type.rs`
|
||||
input_index: 1,
|
||||
value: graph_craft::document::value::TaggedValue::F64((generate_uuid() >> 1) as f64),
|
||||
input_index: 3,
|
||||
value: graph_craft::document::value::TaggedValue::F64(random_value),
|
||||
});
|
||||
|
||||
// Generate the image
|
||||
@@ -497,14 +493,6 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
|
||||
responses.add(DocumentMessage::ImaginateGenerate { layer_path, imaginate_node });
|
||||
}
|
||||
}
|
||||
ImaginateTerminate { layer_path, node_path } => {
|
||||
responses.add(FrontendMessage::TriggerImaginateTerminate {
|
||||
document_id,
|
||||
layer_path,
|
||||
node_path,
|
||||
hostname: preferences.imaginate_server_hostname.clone(),
|
||||
});
|
||||
}
|
||||
InputFrameRasterizeRegionBelowLayer { layer_path } => {
|
||||
if layer_path.is_empty() {
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
@@ -996,7 +984,7 @@ impl DocumentMessageHandler {
|
||||
// Check if we use the "Input Frame" node.
|
||||
// TODO: Remove once rasterization is moved into a node.
|
||||
let input_frame_node_id = node_network.nodes.iter().find(|(_, node)| node.name == "Input Frame").map(|(&id, _)| id);
|
||||
let input_frame_connected_to_graph_output = input_frame_node_id.map_or(false, |target_node_id| node_network.connected_to_output(target_node_id, imaginate_node_path.is_none()));
|
||||
let input_frame_connected_to_graph_output = input_frame_node_id.map_or(false, |target_node_id| node_network.connected_to_output(target_node_id));
|
||||
|
||||
// If the Input Frame node is connected upstream, rasterize the artwork below this layer by calling into JS
|
||||
let response = if input_frame_connected_to_graph_output {
|
||||
|
||||
@@ -459,7 +459,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &NodeGraphExecutor, u64)>
|
||||
let input = NodeInput::node(output_node, output_node_connector_index);
|
||||
responses.add(NodeGraphMessage::SetNodeInput { node_id, input_index, input });
|
||||
|
||||
let should_rerender = network.connected_to_output(node_id, true);
|
||||
let should_rerender = network.connected_to_output(node_id);
|
||||
responses.add(NodeGraphMessage::SendGraph { should_rerender });
|
||||
}
|
||||
NodeGraphMessage::Copy => {
|
||||
@@ -517,7 +517,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &NodeGraphExecutor, u64)>
|
||||
|
||||
if let Some(network) = self.get_active_network(document) {
|
||||
// Only generate node graph if one of the selected nodes is connected to the output
|
||||
if self.selected_nodes.iter().any(|&node_id| network.connected_to_output(node_id, true)) {
|
||||
if self.selected_nodes.iter().any(|&node_id| network.connected_to_output(node_id)) {
|
||||
if let Some(layer_path) = self.layer_path.clone() {
|
||||
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path });
|
||||
}
|
||||
@@ -549,7 +549,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &NodeGraphExecutor, u64)>
|
||||
}
|
||||
responses.add(NodeGraphMessage::SetNodeInput { node_id, input_index, input });
|
||||
|
||||
let should_rerender = network.connected_to_output(node_id, true);
|
||||
let should_rerender = network.connected_to_output(node_id);
|
||||
responses.add(NodeGraphMessage::SendGraph { should_rerender });
|
||||
}
|
||||
NodeGraphMessage::DoubleClickNode { node } => {
|
||||
@@ -626,7 +626,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &NodeGraphExecutor, u64)>
|
||||
}
|
||||
responses.add(NodeGraphMessage::SetNodeInput { node_id, input_index, input });
|
||||
|
||||
let should_rerender = network.connected_to_output(node_id, true);
|
||||
let should_rerender = network.connected_to_output(node_id);
|
||||
responses.add(NodeGraphMessage::SendGraph { should_rerender });
|
||||
responses.add(PropertiesPanelMessage::ResendActiveProperties);
|
||||
}
|
||||
@@ -743,7 +743,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &NodeGraphExecutor, u64)>
|
||||
let input = NodeInput::Value { tagged_value: value, exposed: false };
|
||||
responses.add(NodeGraphMessage::SetNodeInput { node_id, input_index, input });
|
||||
responses.add(PropertiesPanelMessage::ResendActiveProperties);
|
||||
if (node.name != "Imaginate" || input_index == 0) && network.connected_to_output(node_id, true) {
|
||||
if (node.name != "Imaginate" || input_index == 0) && network.connected_to_output(node_id) {
|
||||
if let Some(layer_path) = self.layer_path.clone() {
|
||||
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path });
|
||||
} else {
|
||||
@@ -780,7 +780,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &NodeGraphExecutor, u64)>
|
||||
node.inputs.extend(((node.inputs.len() - 1)..input_index).map(|_| NodeInput::Network(generic!(T))));
|
||||
}
|
||||
node.inputs[input_index] = NodeInput::Value { tagged_value: value, exposed: false };
|
||||
if network.connected_to_output(*node_id, true) {
|
||||
if network.connected_to_output(*node_id) {
|
||||
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path });
|
||||
}
|
||||
}
|
||||
@@ -854,7 +854,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &NodeGraphExecutor, u64)>
|
||||
Self::send_graph(network, executor, &self.layer_path, responses);
|
||||
|
||||
// Only generate node graph if one of the selected nodes is connected to the output
|
||||
if self.selected_nodes.iter().any(|&node_id| network.connected_to_output(node_id, true)) {
|
||||
if self.selected_nodes.iter().any(|&node_id| network.connected_to_output(node_id)) {
|
||||
if let Some(layer_path) = self.layer_path.clone() {
|
||||
responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path });
|
||||
}
|
||||
|
||||
@@ -1673,12 +1673,63 @@ fn static_nodes() -> Vec<DocumentNodeType> {
|
||||
pub static IMAGINATE_NODE: Lazy<DocumentNodeType> = Lazy::new(|| DocumentNodeType {
|
||||
name: "Imaginate",
|
||||
category: "Image Synthesis",
|
||||
identifier: NodeImplementation::proto("graphene_std::raster::ImaginateNode<_>"),
|
||||
identifier: NodeImplementation::DocumentNode(NodeNetwork {
|
||||
inputs: vec![0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
||||
outputs: vec![NodeOutput::new(1, 0)],
|
||||
nodes: [
|
||||
(
|
||||
0,
|
||||
DocumentNode {
|
||||
name: "Frame Monitor".into(),
|
||||
inputs: vec![NodeInput::Network(concrete!(ImageFrame<Color>))],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode<_>"),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
(
|
||||
1,
|
||||
DocumentNode {
|
||||
name: "Imaginate".into(),
|
||||
inputs: vec![
|
||||
NodeInput::node(0, 0),
|
||||
NodeInput::Network(concrete!(WasmEditorApi)),
|
||||
NodeInput::Network(concrete!(ImaginateController)),
|
||||
NodeInput::Network(concrete!(f64)),
|
||||
NodeInput::Network(concrete!(Option<DVec2>)),
|
||||
NodeInput::Network(concrete!(u32)),
|
||||
NodeInput::Network(concrete!(ImaginateSamplingMethod)),
|
||||
NodeInput::Network(concrete!(f64)),
|
||||
NodeInput::Network(concrete!(String)),
|
||||
NodeInput::Network(concrete!(String)),
|
||||
NodeInput::Network(concrete!(bool)),
|
||||
NodeInput::Network(concrete!(f64)),
|
||||
NodeInput::Network(concrete!(Option<Vec<u64>>)),
|
||||
NodeInput::Network(concrete!(bool)),
|
||||
NodeInput::Network(concrete!(f64)),
|
||||
NodeInput::Network(concrete!(ImaginateMaskStartingFill)),
|
||||
NodeInput::Network(concrete!(bool)),
|
||||
NodeInput::Network(concrete!(bool)),
|
||||
NodeInput::Network(concrete!(ImaginateCache)),
|
||||
],
|
||||
implementation: DocumentNodeImplementation::proto("graphene_std::raster::ImaginateNode<_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _>"),
|
||||
..Default::default()
|
||||
},
|
||||
),
|
||||
]
|
||||
.into(),
|
||||
..Default::default()
|
||||
}),
|
||||
inputs: vec![
|
||||
DocumentInputType::value("Input Image", TaggedValue::ImageFrame(ImageFrame::empty()), true),
|
||||
DocumentInputType {
|
||||
name: "Editor Api",
|
||||
data_type: FrontendGraphDataType::General,
|
||||
default: NodeInput::Network(concrete!(WasmEditorApi)),
|
||||
},
|
||||
DocumentInputType::value("Controller", TaggedValue::ImaginateController(Default::default()), false),
|
||||
DocumentInputType::value("Seed", TaggedValue::F64(0.), false), // Remember to keep index used in `ImaginateRandom` updated with this entry's index
|
||||
DocumentInputType::value("Resolution", TaggedValue::OptionalDVec2(None), false),
|
||||
DocumentInputType::value("Samples", TaggedValue::F64(30.), false),
|
||||
DocumentInputType::value("Samples", TaggedValue::U32(30), false),
|
||||
DocumentInputType::value("Sampling Method", TaggedValue::ImaginateSamplingMethod(ImaginateSamplingMethod::EulerA), false),
|
||||
DocumentInputType::value("Prompt Guidance", TaggedValue::F64(7.5), false),
|
||||
DocumentInputType::value("Prompt", TaggedValue::String(String::new()), false),
|
||||
@@ -1691,10 +1742,7 @@ pub static IMAGINATE_NODE: Lazy<DocumentNodeType> = Lazy::new(|| DocumentNodeTyp
|
||||
DocumentInputType::value("Mask Starting Fill", TaggedValue::ImaginateMaskStartingFill(ImaginateMaskStartingFill::Fill), false),
|
||||
DocumentInputType::value("Improve Faces", TaggedValue::Bool(false), false),
|
||||
DocumentInputType::value("Tiling", TaggedValue::Bool(false), false),
|
||||
// Non-user status (is document input the right way to do this?)
|
||||
DocumentInputType::value("Cached Data", TaggedValue::RcImage(None), false),
|
||||
DocumentInputType::value("Percent Complete", TaggedValue::F64(0.), false),
|
||||
DocumentInputType::value("Status", TaggedValue::ImaginateStatus(ImaginateStatus::Idle), false),
|
||||
DocumentInputType::value("Cache", TaggedValue::ImaginateCache(Default::default()), false),
|
||||
],
|
||||
outputs: vec![DocumentOutputType::new("Image", FrontendGraphDataType::Raster)],
|
||||
properties: node_properties::imaginate_properties,
|
||||
|
||||
@@ -5,9 +5,11 @@ use super::FrontendGraphDataType;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use document_legacy::{layers::layer_info::LayerDataTypeDiscriminant, Operation};
|
||||
use graph_craft::concrete;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
|
||||
use graph_craft::imaginate_input::{ImaginateMaskStartingFill, ImaginateSamplingMethod, ImaginateServerStatus, ImaginateStatus};
|
||||
use graphene_core::raster::{BlendMode, Color, ImageFrame, LuminanceCalculation, RedGreenBlue, RelativeAbsolute, SelectiveColorChoice};
|
||||
use graphene_core::text::Font;
|
||||
use graphene_core::vector::style::{FillType, GradientType, LineCap, LineJoin};
|
||||
@@ -980,12 +982,17 @@ pub fn node_section_font(document_node: &DocumentNode, node_id: NodeId, _context
|
||||
result
|
||||
}
|
||||
|
||||
pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
/*
|
||||
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 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 resolve_input = |name: &str| {
|
||||
super::IMAGINATE_NODE
|
||||
.inputs
|
||||
.iter()
|
||||
.position(|input| input.name == name)
|
||||
.unwrap_or_else(|| panic!("Input {name} not found"))
|
||||
};
|
||||
let seed_index = resolve_input("Seed");
|
||||
let resolution_index = resolve_input("Resolution");
|
||||
let samples_index = resolve_input("Samples");
|
||||
@@ -1001,22 +1008,12 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co
|
||||
let mask_fill_index = resolve_input("Mask Starting Fill");
|
||||
let faces_index = resolve_input("Improve Faces");
|
||||
let tiling_index = resolve_input("Tiling");
|
||||
let cached_index = resolve_input("Cached Data");
|
||||
|
||||
let cached_value = &document_node.inputs[cached_index];
|
||||
let complete_value = &document_node.inputs[resolve_input("Percent Complete")];
|
||||
let status_value = &document_node.inputs[resolve_input("Status")];
|
||||
let controller = &document_node.inputs[resolve_input("Controller")];
|
||||
|
||||
let server_status = {
|
||||
let status = match &context.persistent_data.imaginate_server_status {
|
||||
ImaginateServerStatus::Unknown => {
|
||||
context.responses.add(PortfolioMessage::ImaginateCheckServerStatus);
|
||||
"Checking..."
|
||||
}
|
||||
ImaginateServerStatus::Checking => "Checking...",
|
||||
ImaginateServerStatus::Unavailable => "Unavailable",
|
||||
ImaginateServerStatus::Connected => "Connected",
|
||||
};
|
||||
let server_status = context.persistent_data.imaginate.server_status();
|
||||
let status_text = server_status.to_text();
|
||||
let mut widgets = vec![
|
||||
WidgetHolder::text_widget("Server"),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
@@ -1025,14 +1022,14 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co
|
||||
.on_update(|_| DialogMessage::RequestPreferencesDialog.into())
|
||||
.widget_holder(),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
WidgetHolder::bold_text(status),
|
||||
WidgetHolder::bold_text(status_text),
|
||||
WidgetHolder::related_separator(),
|
||||
IconButton::new("Reload", 24)
|
||||
.tooltip("Refresh connection status")
|
||||
.on_update(|_| PortfolioMessage::ImaginateCheckServerStatus.into())
|
||||
.widget_holder(),
|
||||
];
|
||||
if context.persistent_data.imaginate_server_status == ImaginateServerStatus::Unavailable {
|
||||
if let ImaginateServerStatus::Unavailable | ImaginateServerStatus::Failed(_) = server_status {
|
||||
widgets.extend([
|
||||
WidgetHolder::unrelated_separator(),
|
||||
TextButton::new("Server Help")
|
||||
@@ -1049,15 +1046,11 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co
|
||||
LayoutGroup::Row { widgets }.with_tooltip("Connection status to the server that computes generated images")
|
||||
};
|
||||
|
||||
let &NodeInput::Value {tagged_value: TaggedValue::ImaginateStatus( imaginate_status),..} = status_value else {
|
||||
panic!("Invalid status input")
|
||||
};
|
||||
let NodeInput::Value {tagged_value: TaggedValue::RcImage( cached_data),..} = cached_value else {
|
||||
panic!("Invalid cached image input, received {:?}, index: {}", cached_value, cached_index)
|
||||
};
|
||||
let &NodeInput::Value {tagged_value: TaggedValue::F64( percent_complete),..} = complete_value else {
|
||||
panic!("Invalid percent complete input")
|
||||
let &NodeInput::Value {tagged_value: TaggedValue::ImaginateController(ref controller),..} = controller else {
|
||||
panic!("Invalid output status input")
|
||||
};
|
||||
let imaginate_status = controller.get_status();
|
||||
|
||||
let use_base_image = if let &NodeInput::Value {
|
||||
tagged_value: TaggedValue::Bool(use_base_image),
|
||||
..
|
||||
@@ -1071,23 +1064,7 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co
|
||||
let transform_not_connected = false;
|
||||
|
||||
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;
|
||||
if imaginate_status == ImaginateStatus::Idle && cached_data.is_some() && percent_complete > 0. && percent_complete < 100. {
|
||||
interpreted_status = ImaginateStatus::Terminated;
|
||||
}
|
||||
|
||||
let status = match interpreted_status {
|
||||
ImaginateStatus::Idle => match cached_data {
|
||||
Some(_) => "Done".into(),
|
||||
None => "Ready".into(),
|
||||
},
|
||||
ImaginateStatus::Beginning => "Beginning...".into(),
|
||||
ImaginateStatus::Uploading(percent) => format!("Uploading Input Image: {percent:.0}%"),
|
||||
ImaginateStatus::Generating => format!("Generating: {percent_complete:.0}%"),
|
||||
ImaginateStatus::Terminating => "Terminating...".into(),
|
||||
ImaginateStatus::Terminated => format!("{percent_complete:.0}% (Terminated)"),
|
||||
};
|
||||
let status = imaginate_status.to_text();
|
||||
let widgets = vec![
|
||||
WidgetHolder::text_widget("Progress"),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
@@ -1095,38 +1072,38 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co
|
||||
WidgetHolder::unrelated_separator(), // TODO: which is the width of the Assist area.
|
||||
WidgetHolder::unrelated_separator(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists.
|
||||
WidgetHolder::unrelated_separator(),
|
||||
WidgetHolder::bold_text(status),
|
||||
WidgetHolder::bold_text(status.as_ref()),
|
||||
];
|
||||
LayoutGroup::Row { widgets }.with_tooltip("When generating, the percentage represents how many sampling steps have so far been processed out of the target number")
|
||||
LayoutGroup::Row { widgets }.with_tooltip(match imaginate_status {
|
||||
ImaginateStatus::Failed(_) => status.as_ref(),
|
||||
_ => "When generating, the percentage represents how many sampling steps have so far been processed out of the target number",
|
||||
})
|
||||
};
|
||||
|
||||
let image_controls = {
|
||||
let image_controls: _ = {
|
||||
let mut widgets = vec![WidgetHolder::text_widget("Image"), WidgetHolder::unrelated_separator()];
|
||||
let assist_separators = vec![
|
||||
let assist_separators = [
|
||||
WidgetHolder::unrelated_separator(), // TODO: These three separators add up to 24px,
|
||||
WidgetHolder::unrelated_separator(), // TODO: which is the width of the Assist area.
|
||||
WidgetHolder::unrelated_separator(), // TODO: Remove these when we have proper entry row formatting that includes room for Assists.
|
||||
WidgetHolder::unrelated_separator(),
|
||||
];
|
||||
|
||||
match imaginate_status {
|
||||
ImaginateStatus::Beginning | ImaginateStatus::Uploading(_) => {
|
||||
match &imaginate_status {
|
||||
ImaginateStatus::Beginning | ImaginateStatus::Uploading => {
|
||||
widgets.extend_from_slice(&assist_separators);
|
||||
widgets.push(TextButton::new("Beginning...").tooltip("Sending image generation request to the server").disabled(true).widget_holder());
|
||||
}
|
||||
ImaginateStatus::Generating => {
|
||||
ImaginateStatus::Generating(_) => {
|
||||
widgets.extend_from_slice(&assist_separators);
|
||||
widgets.push(
|
||||
TextButton::new("Terminate")
|
||||
.tooltip("Cancel the in-progress image generation and keep the latest progress")
|
||||
.on_update({
|
||||
let imaginate_node = imaginate_node.clone();
|
||||
let controller = controller.clone();
|
||||
move |_| {
|
||||
DocumentMessage::ImaginateTerminate {
|
||||
layer_path: layer_path.clone(),
|
||||
node_path: imaginate_node.clone(),
|
||||
}
|
||||
.into()
|
||||
controller.request_termination();
|
||||
Message::NoOp
|
||||
}
|
||||
})
|
||||
.widget_holder(),
|
||||
@@ -1141,13 +1118,15 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co
|
||||
.widget_holder(),
|
||||
);
|
||||
}
|
||||
ImaginateStatus::Idle | ImaginateStatus::Terminated => widgets.extend_from_slice(&[
|
||||
ImaginateStatus::Ready | ImaginateStatus::ReadyDone | ImaginateStatus::Terminated | ImaginateStatus::Failed(_) => widgets.extend_from_slice(&[
|
||||
IconButton::new("Random", 24)
|
||||
.tooltip("Generate with a new random seed")
|
||||
.on_update({
|
||||
let imaginate_node = imaginate_node.clone();
|
||||
let layer_path = context.layer_path.to_vec();
|
||||
let controller = controller.clone();
|
||||
move |_| {
|
||||
controller.trigger_regenerate();
|
||||
DocumentMessage::ImaginateRandom {
|
||||
layer_path: layer_path.clone(),
|
||||
imaginate_node: imaginate_node.clone(),
|
||||
@@ -1163,7 +1142,9 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co
|
||||
.on_update({
|
||||
let imaginate_node = imaginate_node.clone();
|
||||
let layer_path = context.layer_path.to_vec();
|
||||
let controller = controller.clone();
|
||||
move |_| {
|
||||
controller.trigger_regenerate();
|
||||
DocumentMessage::ImaginateGenerate {
|
||||
layer_path: layer_path.clone(),
|
||||
imaginate_node: imaginate_node.clone(),
|
||||
@@ -1175,16 +1156,13 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co
|
||||
WidgetHolder::related_separator(),
|
||||
TextButton::new("Clear")
|
||||
.tooltip("Remove generated image from the layer frame")
|
||||
.disabled(cached_data.is_none())
|
||||
.disabled(!matches!(imaginate_status, ImaginateStatus::ReadyDone))
|
||||
.on_update({
|
||||
let layer_path = context.layer_path.to_vec();
|
||||
let controller = controller.clone();
|
||||
move |_| {
|
||||
DocumentMessage::ImaginateClear {
|
||||
node_id,
|
||||
layer_path: layer_path.clone(),
|
||||
cached_index,
|
||||
}
|
||||
.into()
|
||||
controller.set_status(ImaginateStatus::Ready);
|
||||
DocumentMessage::ImaginateClear { layer_path: layer_path.clone() }.into()
|
||||
}
|
||||
})
|
||||
.widget_holder(),
|
||||
@@ -1221,9 +1199,11 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co
|
||||
.widget_holder(),
|
||||
WidgetHolder::unrelated_separator(),
|
||||
NumberInput::new(Some(seed))
|
||||
.min(0.)
|
||||
.int()
|
||||
.min(-((1u64 << f64::MANTISSA_DIGITS) as f64))
|
||||
.max((1u64 << f64::MANTISSA_DIGITS) as f64)
|
||||
.on_update(update_value(move |input: &NumberInput| TaggedValue::F64(input.value.unwrap()), node_id, seed_index))
|
||||
.mode(NumberInputMode::Increment)
|
||||
.widget_holder(),
|
||||
])
|
||||
}
|
||||
@@ -1231,23 +1211,19 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co
|
||||
LayoutGroup::Row { widgets }.with_tooltip("Seed determines the random outcome, enabling limitless unique variations")
|
||||
};
|
||||
|
||||
// Create the input to the graph using an empty image
|
||||
let editor_api = std::borrow::Cow::Owned(EditorApi {
|
||||
image_frame: None,
|
||||
font_cache: Some(&context.persistent_data.font_cache),
|
||||
});
|
||||
// Compute the transform input to the image frame
|
||||
let image_frame: ImageFrame<Color> = context.executor.compute_input(context.network, &imaginate_node, 0, editor_api).unwrap_or_default();
|
||||
let transform = image_frame.transform;
|
||||
let transform = context
|
||||
.executor
|
||||
.introspect_node_in_network(context.network, &imaginate_node, |network| network.inputs.first().copied(), |frame: &ImageFrame<Color>| frame.transform)
|
||||
.unwrap_or_default();
|
||||
|
||||
let resolution = {
|
||||
use document_legacy::document::pick_safe_imaginate_resolution;
|
||||
use graphene_std::imaginate::pick_safe_imaginate_resolution;
|
||||
|
||||
let mut widgets = start_widgets(document_node, node_id, resolution_index, "Resolution", FrontendGraphDataType::Vector, false);
|
||||
|
||||
let round = |x: DVec2| {
|
||||
let (x, y) = pick_safe_imaginate_resolution(x.into());
|
||||
Some(DVec2::new(x as f64, y as f64))
|
||||
DVec2::new(x as f64, y as f64)
|
||||
};
|
||||
|
||||
if let &NodeInput::Value {
|
||||
@@ -1256,14 +1232,7 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co
|
||||
} = &document_node.inputs[resolution_index]
|
||||
{
|
||||
let dimensions_is_auto = vec2.is_none();
|
||||
let vec2 = vec2.unwrap_or_else(|| {
|
||||
let w = transform.transform_vector2(DVec2::new(1., 0.)).length();
|
||||
let h = transform.transform_vector2(DVec2::new(0., 1.)).length();
|
||||
|
||||
let (x, y) = pick_safe_imaginate_resolution((w, h));
|
||||
|
||||
DVec2::new(x as f64, y as f64)
|
||||
});
|
||||
let vec2 = vec2.unwrap_or_else(|| round([transform.matrix2.x_axis, transform.matrix2.y_axis].map(DVec2::length).into()));
|
||||
|
||||
let layer_path = context.layer_path.to_vec();
|
||||
widgets.extend_from_slice(&[
|
||||
@@ -1308,7 +1277,7 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co
|
||||
.unit(" px")
|
||||
.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))),
|
||||
move |number_input: &NumberInput| TaggedValue::OptionalDVec2(Some(round(DVec2::new(number_input.value.unwrap(), vec2.y)))),
|
||||
node_id,
|
||||
resolution_index,
|
||||
))
|
||||
@@ -1321,7 +1290,7 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co
|
||||
.unit(" px")
|
||||
.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()))),
|
||||
move |number_input: &NumberInput| TaggedValue::OptionalDVec2(Some(round(DVec2::new(vec2.x, number_input.value.unwrap())))),
|
||||
node_id,
|
||||
resolution_index,
|
||||
))
|
||||
@@ -1538,8 +1507,6 @@ pub fn imaginate_properties(_document_node: &DocumentNode, _node_id: NodeId, _co
|
||||
layout.extend_from_slice(&[improve_faces, tiling]);
|
||||
|
||||
layout
|
||||
*/
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn unknown_node_properties(document_node: &DocumentNode) -> Vec<LayoutGroup> {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
use super::utility_types::ImaginateServerStatus;
|
||||
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use document_legacy::LayerId;
|
||||
use graph_craft::document::NodeId;
|
||||
use graph_craft::imaginate_input::ImaginateStatus;
|
||||
use graphene_core::text::Font;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -57,24 +55,9 @@ pub enum PortfolioMessage {
|
||||
is_default: bool,
|
||||
},
|
||||
ImaginateCheckServerStatus,
|
||||
ImaginateSetGeneratingStatus {
|
||||
document_id: u64,
|
||||
layer_path: Vec<LayerId>,
|
||||
node_path: Vec<NodeId>,
|
||||
percent: Option<f64>,
|
||||
status: ImaginateStatus,
|
||||
},
|
||||
ImaginateSetImageData {
|
||||
document_id: u64,
|
||||
layer_path: Vec<LayerId>,
|
||||
node_path: Vec<NodeId>,
|
||||
image_data: Vec<u8>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
},
|
||||
ImaginateSetServerStatus {
|
||||
status: ImaginateServerStatus,
|
||||
},
|
||||
ImaginatePollServerStatus,
|
||||
ImaginatePreferences,
|
||||
ImaginateServerHostname,
|
||||
Import,
|
||||
LoadDocumentResources {
|
||||
document_id: u64,
|
||||
|
||||
@@ -7,9 +7,7 @@ use crate::messages::dialog::simple_dialogs;
|
||||
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::utility_types::ImaginateServerStatus;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup};
|
||||
use crate::node_graph_executor::NodeGraphExecutor;
|
||||
@@ -19,7 +17,6 @@ use document_legacy::layers::style::RenderData;
|
||||
use document_legacy::Operation as DocumentOperation;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_core::raster::Image;
|
||||
use graphene_core::text::Font;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
@@ -218,70 +215,35 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
||||
self.executor.update_font_cache(self.persistent_data.font_cache.clone());
|
||||
}
|
||||
PortfolioMessage::ImaginateCheckServerStatus => {
|
||||
self.persistent_data.imaginate_server_status = ImaginateServerStatus::Checking;
|
||||
responses.add(FrontendMessage::TriggerImaginateCheckServerStatus {
|
||||
hostname: preferences.imaginate_server_hostname.clone(),
|
||||
});
|
||||
responses.add(PropertiesPanelMessage::ResendActiveProperties);
|
||||
}
|
||||
PortfolioMessage::ImaginateSetGeneratingStatus {
|
||||
document_id,
|
||||
layer_path,
|
||||
node_path,
|
||||
percent,
|
||||
status,
|
||||
} => {
|
||||
let get = |name: &str| IMAGINATE_NODE.inputs.iter().position(|input| input.name == name).unwrap_or_else(|| panic!("Input {name} not found"));
|
||||
if let Some(percentage) = percent {
|
||||
responses.add(PortfolioMessage::DocumentPassMessage {
|
||||
document_id,
|
||||
message: NodeGraphMessage::SetQualifiedInputValue {
|
||||
layer_path: layer_path.clone(),
|
||||
node_path: node_path.clone(),
|
||||
input_index: get("Percent Complete"),
|
||||
value: TaggedValue::F64(percentage),
|
||||
let server_status = self.persistent_data.imaginate.server_status().clone();
|
||||
self.persistent_data.imaginate.poll_server_check();
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
if let Some(fut) = self.persistent_data.imaginate.initiate_server_check() {
|
||||
future_executor::spawn(async move {
|
||||
let () = fut.await;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen(module = "/../frontend/src/wasm-communication/editor.ts")]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(js_name = injectImaginatePollServerStatus)]
|
||||
fn inject();
|
||||
}
|
||||
.into(),
|
||||
});
|
||||
inject();
|
||||
})
|
||||
}
|
||||
if &server_status != self.persistent_data.imaginate.server_status() {
|
||||
responses.add(PropertiesPanelMessage::ResendActiveProperties);
|
||||
}
|
||||
|
||||
responses.add(PortfolioMessage::DocumentPassMessage {
|
||||
document_id,
|
||||
message: NodeGraphMessage::SetQualifiedInputValue {
|
||||
layer_path,
|
||||
node_path,
|
||||
input_index: get("Status"),
|
||||
value: TaggedValue::ImaginateStatus(status),
|
||||
}
|
||||
.into(),
|
||||
});
|
||||
}
|
||||
PortfolioMessage::ImaginateSetImageData {
|
||||
document_id,
|
||||
layer_path,
|
||||
node_path,
|
||||
image_data,
|
||||
width,
|
||||
height,
|
||||
} => {
|
||||
let get = |name: &str| IMAGINATE_NODE.inputs.iter().position(|input| input.name == name).unwrap_or_else(|| panic!("Input {name} not found"));
|
||||
|
||||
let image = Image::from_image_data(&image_data, width, height);
|
||||
responses.add(PortfolioMessage::DocumentPassMessage {
|
||||
document_id,
|
||||
message: NodeGraphMessage::SetQualifiedInputValue {
|
||||
layer_path,
|
||||
node_path,
|
||||
input_index: get("Cached Data"),
|
||||
value: TaggedValue::RcImage(Some(std::sync::Arc::new(image))),
|
||||
}
|
||||
.into(),
|
||||
});
|
||||
}
|
||||
PortfolioMessage::ImaginateSetServerStatus { status } => {
|
||||
self.persistent_data.imaginate_server_status = status;
|
||||
PortfolioMessage::ImaginatePollServerStatus => {
|
||||
self.persistent_data.imaginate.poll_server_check();
|
||||
responses.add(PropertiesPanelMessage::ResendActiveProperties);
|
||||
}
|
||||
PortfolioMessage::ImaginatePreferences => self.executor.update_imaginate_preferences(preferences.get_imaginate_preferences()),
|
||||
PortfolioMessage::ImaginateServerHostname => {
|
||||
info!("setting imaginate persistent data");
|
||||
self.persistent_data.imaginate.set_host_name(&preferences.imaginate_server_hostname);
|
||||
}
|
||||
PortfolioMessage::Import => {
|
||||
// This portfolio message wraps the frontend message so it can be listed as an action, which isn't possible for frontend messages
|
||||
if self.active_document().is_some() {
|
||||
@@ -461,7 +423,6 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
|
||||
(document_id, &mut self.documents),
|
||||
layer_path,
|
||||
(input_image_data, size),
|
||||
imaginate_node_path,
|
||||
(preferences, &self.persistent_data),
|
||||
responses,
|
||||
);
|
||||
|
||||
@@ -1,29 +1,11 @@
|
||||
use graphene_std::text::FontCache;
|
||||
use graphene_std::{imaginate::ImaginatePersistentData, text::FontCache};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PersistentData {
|
||||
pub font_cache: FontCache,
|
||||
pub imaginate_server_status: ImaginateServerStatus,
|
||||
}
|
||||
|
||||
impl Default for PersistentData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
font_cache: Default::default(),
|
||||
imaginate_server_status: ImaginateServerStatus::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, Serialize, Deserialize, specta::Type)]
|
||||
pub enum ImaginateServerStatus {
|
||||
#[default]
|
||||
Unknown,
|
||||
Checking,
|
||||
Unavailable,
|
||||
Connected,
|
||||
pub imaginate: ImaginatePersistentData,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, Serialize, Deserialize)]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::messages::input_mapper::key_mapping::MappingVariant;
|
||||
use crate::messages::prelude::*;
|
||||
use graph_craft::imaginate_input::ImaginatePreferences;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -10,10 +11,19 @@ pub struct PreferencesMessageHandler {
|
||||
pub zoom_with_scroll: bool,
|
||||
}
|
||||
|
||||
impl PreferencesMessageHandler {
|
||||
pub fn get_imaginate_preferences(&self) -> ImaginatePreferences {
|
||||
ImaginatePreferences {
|
||||
host_name: self.imaginate_server_hostname.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PreferencesMessageHandler {
|
||||
fn default() -> Self {
|
||||
let ImaginatePreferences { host_name } = Default::default();
|
||||
Self {
|
||||
imaginate_server_hostname: "http://localhost:7860/".into(),
|
||||
imaginate_server_hostname: host_name,
|
||||
imaginate_refresh_frequency: 1.,
|
||||
zoom_with_scroll: matches!(MappingVariant::default(), MappingVariant::ZoomWithScroll),
|
||||
}
|
||||
@@ -28,9 +38,9 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
||||
if let Ok(deserialized_preferences) = serde_json::from_str::<PreferencesMessageHandler>(&preferences) {
|
||||
*self = deserialized_preferences;
|
||||
|
||||
if self.imaginate_server_hostname != Self::default().imaginate_server_hostname {
|
||||
responses.add(PortfolioMessage::ImaginateCheckServerStatus);
|
||||
}
|
||||
responses.add(PortfolioMessage::ImaginateServerHostname);
|
||||
responses.add(PortfolioMessage::ImaginateCheckServerStatus);
|
||||
responses.add(PortfolioMessage::ImaginatePreferences);
|
||||
}
|
||||
}
|
||||
PreferencesMessage::ResetToDefaults => {
|
||||
@@ -43,6 +53,7 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
||||
PreferencesMessage::ImaginateRefreshFrequency { seconds } => {
|
||||
self.imaginate_refresh_frequency = seconds;
|
||||
responses.add(PortfolioMessage::ImaginateCheckServerStatus);
|
||||
responses.add(PortfolioMessage::ImaginatePreferences);
|
||||
}
|
||||
PreferencesMessage::ImaginateServerHostname { hostname } => {
|
||||
let initial = hostname.clone();
|
||||
@@ -55,7 +66,9 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
||||
}
|
||||
|
||||
self.imaginate_server_hostname = hostname;
|
||||
responses.add(PortfolioMessage::ImaginateServerHostname);
|
||||
responses.add(PortfolioMessage::ImaginateCheckServerStatus);
|
||||
responses.add(PortfolioMessage::ImaginatePreferences);
|
||||
}
|
||||
PreferencesMessage::ModifyLayout { zoom_with_scroll } => {
|
||||
self.zoom_with_scroll = zoom_with_scroll;
|
||||
|
||||
@@ -10,16 +10,16 @@ use document_legacy::{LayerId, Operation};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork};
|
||||
use graph_craft::graphene_compiler::Compiler;
|
||||
use graph_craft::imaginate_input::ImaginatePreferences;
|
||||
use graph_craft::{concrete, Type, TypeDescriptor};
|
||||
use graphene_core::application_io::ApplicationIo;
|
||||
use graphene_core::application_io::{ApplicationIo, NodeGraphUpdateMessage, NodeGraphUpdateSender};
|
||||
use graphene_core::raster::{Image, ImageFrame};
|
||||
use graphene_core::renderer::{SvgSegment, SvgSegmentList};
|
||||
use graphene_core::text::FontCache;
|
||||
use graphene_core::vector::style::ViewMode;
|
||||
|
||||
use graphene_core::{Color, SurfaceFrame, SurfaceId};
|
||||
use graphene_std::wasm_application_io::WasmApplicationIo;
|
||||
use graphene_std::wasm_application_io::WasmEditorApi;
|
||||
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
|
||||
use interpreted_executor::dynamic_executor::DynamicExecutor;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
@@ -33,8 +33,9 @@ pub struct NodeRuntime {
|
||||
pub(crate) executor: DynamicExecutor,
|
||||
font_cache: FontCache,
|
||||
receiver: Receiver<NodeRuntimeMessage>,
|
||||
sender: Sender<GenerationResponse>,
|
||||
sender: InternalNodeGraphUpdateSender,
|
||||
wasm_io: Option<WasmApplicationIo>,
|
||||
imaginate_preferences: ImaginatePreferences,
|
||||
pub(crate) thumbnails: HashMap<LayerId, HashMap<NodeId, SvgSegmentList>>,
|
||||
canvas_cache: HashMap<Vec<LayerId>, SurfaceId>,
|
||||
}
|
||||
@@ -42,6 +43,7 @@ pub struct NodeRuntime {
|
||||
enum NodeRuntimeMessage {
|
||||
GenerationRequest(GenerationRequest),
|
||||
FontCacheUpdate(FontCache),
|
||||
ImaginatePreferencesUpdate(ImaginatePreferences),
|
||||
}
|
||||
|
||||
pub(crate) struct GenerationRequest {
|
||||
@@ -50,6 +52,7 @@ pub(crate) struct GenerationRequest {
|
||||
path: Vec<LayerId>,
|
||||
image_frame: Option<ImageFrame<Color>>,
|
||||
}
|
||||
|
||||
pub(crate) struct GenerationResponse {
|
||||
generation_id: u64,
|
||||
result: Result<TaggedValue, String>,
|
||||
@@ -57,18 +60,38 @@ pub(crate) struct GenerationResponse {
|
||||
new_thumbnails: HashMap<LayerId, HashMap<NodeId, SvgSegmentList>>,
|
||||
}
|
||||
|
||||
enum NodeGraphUpdate {
|
||||
GenerationResponse(GenerationResponse),
|
||||
NodeGraphUpdateMessage(NodeGraphUpdateMessage),
|
||||
}
|
||||
|
||||
struct InternalNodeGraphUpdateSender(Sender<NodeGraphUpdate>);
|
||||
|
||||
impl InternalNodeGraphUpdateSender {
|
||||
fn send_generation_response(&self, response: GenerationResponse) {
|
||||
self.0.send(NodeGraphUpdate::GenerationResponse(response)).expect("Failed to send response")
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeGraphUpdateSender for InternalNodeGraphUpdateSender {
|
||||
fn send(&self, message: NodeGraphUpdateMessage) {
|
||||
self.0.send(NodeGraphUpdate::NodeGraphUpdateMessage(message)).expect("Failed to send response")
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub(crate) static NODE_RUNTIME: Rc<RefCell<Option<NodeRuntime>>> = Rc::new(RefCell::new(None));
|
||||
}
|
||||
|
||||
impl NodeRuntime {
|
||||
fn new(receiver: Receiver<NodeRuntimeMessage>, sender: Sender<GenerationResponse>) -> Self {
|
||||
fn new(receiver: Receiver<NodeRuntimeMessage>, sender: Sender<NodeGraphUpdate>) -> Self {
|
||||
let executor = DynamicExecutor::default();
|
||||
Self {
|
||||
executor,
|
||||
receiver,
|
||||
sender,
|
||||
sender: InternalNodeGraphUpdateSender(sender),
|
||||
font_cache: FontCache::default(),
|
||||
imaginate_preferences: Default::default(),
|
||||
thumbnails: Default::default(),
|
||||
wasm_io: None,
|
||||
canvas_cache: Default::default(),
|
||||
@@ -80,13 +103,14 @@ impl NodeRuntime {
|
||||
// This should be avoided in the future.
|
||||
requests.reverse();
|
||||
requests.dedup_by_key(|x| match x {
|
||||
NodeRuntimeMessage::FontCacheUpdate(_) => None,
|
||||
NodeRuntimeMessage::GenerationRequest(x) => Some(x.path.clone()),
|
||||
_ => None,
|
||||
});
|
||||
requests.reverse();
|
||||
for request in requests {
|
||||
match request {
|
||||
NodeRuntimeMessage::FontCacheUpdate(font_cache) => self.font_cache = font_cache,
|
||||
NodeRuntimeMessage::ImaginatePreferencesUpdate(preferences) => self.imaginate_preferences = preferences,
|
||||
NodeRuntimeMessage::GenerationRequest(GenerationRequest {
|
||||
generation_id,
|
||||
graph,
|
||||
@@ -105,7 +129,7 @@ impl NodeRuntime {
|
||||
updates: responses,
|
||||
new_thumbnails: self.thumbnails.clone(),
|
||||
};
|
||||
self.sender.send(response).expect("Failed to send response");
|
||||
self.sender.send_generation_response(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,6 +158,8 @@ impl NodeRuntime {
|
||||
font_cache: &self.font_cache,
|
||||
image_frame,
|
||||
application_io: &self.wasm_io.as_ref().unwrap(),
|
||||
node_graph_message_sender: &self.sender,
|
||||
imaginate_preferences: &self.imaginate_preferences,
|
||||
};
|
||||
|
||||
// We assume only one output
|
||||
@@ -240,7 +266,7 @@ pub async fn run_node_graph() {
|
||||
#[derive(Debug)]
|
||||
pub struct NodeGraphExecutor {
|
||||
sender: Sender<NodeRuntimeMessage>,
|
||||
receiver: Receiver<GenerationResponse>,
|
||||
receiver: Receiver<NodeGraphUpdate>,
|
||||
// TODO: This is a memory leak since layers are never removed
|
||||
pub(crate) last_output_type: HashMap<Vec<LayerId>, Option<Type>>,
|
||||
pub(crate) thumbnails: HashMap<LayerId, HashMap<NodeId, SvgSegmentList>>,
|
||||
@@ -294,10 +320,31 @@ impl NodeGraphExecutor {
|
||||
self.sender.send(NodeRuntimeMessage::FontCacheUpdate(font_cache)).expect("Failed to send font cache update");
|
||||
}
|
||||
|
||||
pub fn update_imaginate_preferences(&self, imaginate_preferences: ImaginatePreferences) {
|
||||
self.sender
|
||||
.send(NodeRuntimeMessage::ImaginatePreferencesUpdate(imaginate_preferences))
|
||||
.expect("Failed to send imaginate preferences");
|
||||
}
|
||||
|
||||
pub fn previous_output_type(&self, path: &[LayerId]) -> Option<Type> {
|
||||
self.last_output_type.get(path).cloned().flatten()
|
||||
}
|
||||
|
||||
pub fn introspect_node_in_network<T: std::any::Any + core::fmt::Debug, U, F1: FnOnce(&NodeNetwork) -> Option<NodeId>, F2: FnOnce(&T) -> U>(
|
||||
&mut self,
|
||||
network: &NodeNetwork,
|
||||
node_path: &[NodeId],
|
||||
find_node: F1,
|
||||
extract_data: F2,
|
||||
) -> Option<U> {
|
||||
let wrapping_document_node = network.nodes.get(node_path.last()?)?;
|
||||
let DocumentNodeImplementation::Network(wrapped_network) = &wrapping_document_node.implementation else { return None; };
|
||||
let introspection_node = find_node(&wrapped_network)?;
|
||||
let introspection = self.introspect_node(&[node_path, &[introspection_node]].concat())?;
|
||||
let downcasted: &T = <dyn std::any::Any>::downcast_ref(introspection.as_ref())?;
|
||||
Some(extract_data(downcasted))
|
||||
}
|
||||
|
||||
/// Encodes an image into a format using the image crate
|
||||
fn encode_img(image: Image<Color>, resize: Option<DVec2>, format: image::ImageOutputFormat) -> Result<(Vec<u8>, (u32, u32)), String> {
|
||||
use image::{ImageBuffer, Rgba};
|
||||
@@ -334,13 +381,12 @@ impl NodeGraphExecutor {
|
||||
})
|
||||
}
|
||||
|
||||
/// Evaluates a node graph, computing either the Imaginate node or the entire graph
|
||||
/// Evaluates a node graph, computing the entire graph
|
||||
pub fn submit_node_graph_evaluation(
|
||||
&mut self,
|
||||
(document_id, documents): (u64, &mut HashMap<u64, DocumentMessageHandler>),
|
||||
layer_path: Vec<LayerId>,
|
||||
(input_image_data, (width, height)): (Vec<u8>, (u32, u32)),
|
||||
_imaginate_node: Option<Vec<NodeId>>,
|
||||
_persistent_data: (&PreferencesMessageHandler, &PersistentData),
|
||||
_responses: &mut VecDeque<Message>,
|
||||
) -> Result<(), String> {
|
||||
@@ -365,11 +411,6 @@ impl NodeGraphExecutor {
|
||||
let transform = DAffine2::IDENTITY;
|
||||
let image_frame = ImageFrame { image, transform };
|
||||
|
||||
// Special execution path for generating Imaginate (as generation requires IO from outside node graph)
|
||||
/*if let Some(imaginate_node) = imaginate_node {
|
||||
responses.add(self.generate_imaginate(network, imaginate_node, (document, document_id), layer_path, editor_api, persistent_data)?);
|
||||
return Ok(());
|
||||
}*/
|
||||
// Execute the node graph
|
||||
let generation_id = self.queue_execution(network, Some(image_frame), layer_path.clone());
|
||||
|
||||
@@ -381,26 +422,32 @@ impl NodeGraphExecutor {
|
||||
pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) -> Result<(), String> {
|
||||
let results = self.receiver.try_iter().collect::<Vec<_>>();
|
||||
for response in results {
|
||||
let GenerationResponse {
|
||||
generation_id,
|
||||
result,
|
||||
updates,
|
||||
new_thumbnails,
|
||||
} = response;
|
||||
self.thumbnails = new_thumbnails;
|
||||
let node_graph_output = result.map_err(|e| format!("Node graph evaluation failed: {:?}", e))?;
|
||||
let execution_context = self.futures.remove(&generation_id).ok_or_else(|| "Invalid generation ID".to_string())?;
|
||||
responses.extend(updates);
|
||||
self.process_node_graph_output(node_graph_output, execution_context.layer_path.clone(), responses, execution_context.document_id)?;
|
||||
responses.add(DocumentMessage::LayerChanged {
|
||||
affected_layer_path: execution_context.layer_path,
|
||||
});
|
||||
responses.add(DocumentMessage::RenderDocument);
|
||||
responses.add(ArtboardMessage::RenderArtboards);
|
||||
responses.add(DocumentMessage::DocumentStructureChanged);
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
responses.add(DocumentMessage::DirtyRenderDocument);
|
||||
responses.add(DocumentMessage::Overlays(OverlaysMessage::Rerender));
|
||||
match response {
|
||||
NodeGraphUpdate::GenerationResponse(GenerationResponse {
|
||||
generation_id,
|
||||
result,
|
||||
updates,
|
||||
new_thumbnails,
|
||||
}) => {
|
||||
self.thumbnails = new_thumbnails;
|
||||
let node_graph_output = result.map_err(|e| format!("Node graph evaluation failed: {:?}", e))?;
|
||||
let execution_context = self.futures.remove(&generation_id).ok_or_else(|| "Invalid generation ID".to_string())?;
|
||||
responses.extend(updates);
|
||||
self.process_node_graph_output(node_graph_output, execution_context.layer_path.clone(), responses, execution_context.document_id)?;
|
||||
responses.add(DocumentMessage::LayerChanged {
|
||||
affected_layer_path: execution_context.layer_path,
|
||||
});
|
||||
responses.add(DocumentMessage::RenderDocument);
|
||||
responses.add(ArtboardMessage::RenderArtboards);
|
||||
responses.add(DocumentMessage::DocumentStructureChanged);
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
responses.add(DocumentMessage::DirtyRenderDocument);
|
||||
responses.add(DocumentMessage::Overlays(OverlaysMessage::Rerender));
|
||||
}
|
||||
NodeGraphUpdate::NodeGraphUpdateMessage(NodeGraphUpdateMessage::ImaginateStatusUpdate) => {
|
||||
responses.add(DocumentMessage::PropertiesPanel(PropertiesPanelMessage::ResendActiveProperties))
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user