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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ use graph_craft::application_io::wgpu_available;
use graph_craft::descriptor;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork};
use graphene_std::core_types::animation::AnimationCurveMap;
use graphene_std::math::quad::Quad;
use graphene_std::path_bool_nodes::boolean_intersect;
use graphene_std::raster::BlendMode;
Expand Down Expand Up @@ -3547,6 +3548,10 @@ impl DocumentMessageHandler {
}
resources.into_iter().collect::<Vec<_>>().into_boxed_slice()
}

pub fn timeline_curves(&self) -> &AnimationCurveMap {
self.network_interface.timeline_curves()
}
}

/// Create a network interface with a single export
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork};
use graphene_std::ContextDependencies;
use graphene_std::Graphic;
use graphene_std::core_types::animation::AnimationCurveMap;
use graphene_std::list::List;
use graphene_std::math::quad::Quad;
use graphene_std::subpath::Subpath;
Expand All @@ -47,6 +48,9 @@ pub struct NodeNetworkInterface {
network: MemoNetwork,
/// Stores all editor information for a NodeNetwork. Should automatically kept in sync by the setter methods when changes to the document network are made.
network_metadata: NodeNetworkMetadata,
/// Stores animation curves on the timeline
#[serde(default)]
timeline_curves: AnimationCurveMap,
// TODO: Wrap in TransientMetadata Option
/// Stores the document network's structural topology. Should automatically kept in sync by the setter methods when changes to the document network are made.
#[serde(skip)]
Expand All @@ -63,6 +67,7 @@ impl Clone for NodeNetworkInterface {
Self {
network: self.network.clone(),
network_metadata: self.network_metadata.clone(),
timeline_curves: self.timeline_curves.clone(),
document_metadata: Default::default(),
resolved_types: Default::default(),
transaction_status: TransactionStatus::Finished,
Expand All @@ -72,7 +77,7 @@ impl Clone for NodeNetworkInterface {

impl PartialEq for NodeNetworkInterface {
fn eq(&self, other: &Self) -> bool {
self.network == other.network && self.network_metadata == other.network_metadata
self.network == other.network && self.network_metadata == other.network_metadata && self.timeline_curves == other.timeline_curves
}
}

Expand Down Expand Up @@ -163,6 +168,10 @@ impl NodeNetworkInterface {
self.selected_nodes_in_nested_network(&[]).unwrap_or_default()
}

pub fn timeline_curves(&self) -> &AnimationCurveMap {
&self.timeline_curves
}

/// Get the selected nodes for the network at the network_path
pub fn selected_nodes_in_nested_network(&self, network_path: &[NodeId]) -> Option<SelectedNodes> {
let Some(network_metadata) = self.network_metadata(network_path) else {
Expand Down Expand Up @@ -1458,6 +1467,7 @@ impl NodeNetworkInterface {
Self {
network: MemoNetwork::new(node_network),
network_metadata,
timeline_curves: HashMap::new(),
document_metadata: DocumentMetadata::default(),
resolved_types: ResolvedDocumentNodeTypes::default(),
transaction_status: TransactionStatus::Finished,
Expand Down Expand Up @@ -5657,7 +5667,7 @@ impl NodeNetworkInterface {
let layer_output = NodeInput::node(layer.to_node(), 0);

match post_node_input {
NodeInput::Value { .. } | NodeInput::Scope(_) | NodeInput::Inline(_) | NodeInput::Reflection(_) => {
NodeInput::Value { .. } | NodeInput::Timeline { .. } | NodeInput::Scope(_) | NodeInput::Inline(_) | NodeInput::Reflection(_) => {
// First child in the stack: wire layer output to the post_node input
self.set_input_for_import(&post_node, layer_output, network_path);
}
Expand Down Expand Up @@ -5831,7 +5841,7 @@ impl NodeNetworkInterface {
if !inserting_into_stack {
match post_node_input {
// Create a new stack
NodeInput::Value { .. } | NodeInput::Scope(_) | NodeInput::Inline(_) | NodeInput::Reflection(_) => {
NodeInput::Value { .. } | NodeInput::Timeline { .. } | NodeInput::Scope(_) | NodeInput::Inline(_) | NodeInput::Reflection(_) => {
self.create_wire(&OutputConnector::node(layer.to_node(), 0), &post_node, network_path);

let final_layer_position = after_move_post_layer_position + IVec2::new(-LAYER_INDENT_OFFSET, STACK_VERTICAL_GAP);
Expand All @@ -5857,7 +5867,7 @@ impl NodeNetworkInterface {
} else {
match post_node_input {
// Move to the bottom of the stack
NodeInput::Value { .. } | NodeInput::Scope(_) | NodeInput::Inline(_) | NodeInput::Reflection(_) => {
NodeInput::Value { .. } | NodeInput::Timeline { .. } | NodeInput::Scope(_) | NodeInput::Inline(_) | NodeInput::Reflection(_) => {
let offset = after_move_post_layer_position - previous_layer_position + IVec2::new(0, STACK_VERTICAL_GAP + height_above_layer);
self.shift_absolute_node_position(&layer.to_node(), offset, network_path);
self.create_wire(&OutputConnector::node(layer.to_node(), 0), &post_node, network_path);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ impl NodeNetworkInterface {
}

NodeInput::Value { tagged_value, .. } => TypeSource::TaggedValue(tagged_value.ty()),
NodeInput::Timeline { .. } => TypeSource::Compiled(concrete!(f32)),
NodeInput::Import { import_index, .. } => {
// Get the input type of the encapsulating node input
let Some((encapsulating_node, encapsulating_path)) = network_path.split_last() else {
Expand Down
4 changes: 4 additions & 0 deletions editor/src/node_graph_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ impl NodeGraphExecutor {
viewport,
scale: viewport_scale,
time,
timeline_curves: Arc::new(document.timeline_curves().clone()),
pointer,
export_format: graphene_std::application_io::ExportFormat::Raster,
render_mode: document.render_mode,
Expand Down Expand Up @@ -255,6 +256,7 @@ impl NodeGraphExecutor {
viewport,
scale: viewport_scale,
time,
timeline_curves: Arc::new(document.timeline_curves().clone()),
pointer,
export_format: graphene_std::application_io::ExportFormat::Raster,
render_mode,
Expand Down Expand Up @@ -312,6 +314,7 @@ impl NodeGraphExecutor {
viewport,
scale: export_config.scale_factor,
time: Default::default(),
timeline_curves: Arc::new(document.timeline_curves().clone()),
pointer: DVec2::ZERO,
export_format,
render_mode: document.render_mode,
Expand Down Expand Up @@ -544,6 +547,7 @@ impl NodeGraphExecutor {
viewport,
scale,
time: Default::default(),
timeline_curves: Arc::new(document.timeline_curves().clone()),
pointer: DVec2::ZERO,
export_format: ExportFormat::Svg,
render_mode: document.render_mode,
Expand Down
2 changes: 1 addition & 1 deletion editor/src/node_graph_executor/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ impl NodeRuntime {
render_config.export_format = ExportFormat::Svg;
}

let result = self.execute_network(render_config).await;
let result = self.execute_network(render_config.clone()).await;
let mut responses = VecDeque::new();
// TODO: Only process monitor nodes if the graph has changed, not when only the Footprint changes
if !render_config.for_eyedropper {
Expand Down
84 changes: 83 additions & 1 deletion node-graph/graph-craft/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ pub mod value;
use crate::document::value::TaggedValue;
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode};
use core_types::memo::MemoHashGuard;
use core_types::uuid::CurveId;
pub use core_types::uuid::NodeId;
pub use core_types::uuid::generate_uuid;
use core_types::{Context, ContextDependencies, Cow, MemoHash, ProtoNodeIdentifier, Type};
use core_types::{Context, ContextDependencies, ContextFeatures, Cow, MemoHash, ProtoNodeIdentifier, Type};
use dyn_any::DynAny;
use glam::IVec2;
use log::Metadata;
Expand Down Expand Up @@ -184,6 +185,9 @@ pub enum NodeInput {
exposed: bool,
},

/// An animation curve input from the timeline
Timeline { curve_id: CurveId },

// TODO: Remove import_type and get type from parent node input
/// Input that is provided by the import from the parent network to this document node network.
#[serde(alias = "Network")]
Expand Down Expand Up @@ -259,6 +263,7 @@ impl NodeInput {
match self {
NodeInput::Node { .. } => true,
NodeInput::Value { exposed, .. } => *exposed,
NodeInput::Timeline { .. } => false,
NodeInput::Import { .. } => true,
NodeInput::Inline(_) => false,
NodeInput::Scope(_) => false,
Expand All @@ -271,6 +276,7 @@ impl NodeInput {
NodeInput::Node { .. } => unreachable!("ty() called on NodeInput::Node"),
NodeInput::Value { tagged_value, .. } => tagged_value.ty(),
NodeInput::Import { import_type, .. } => import_type.clone(),
NodeInput::Timeline { .. } => concrete!(f32),
NodeInput::Inline(_) => panic!("ty() called on NodeInput::Inline"),
NodeInput::Scope(_) => panic!("ty() called on NodeInput::Scope"),
NodeInput::Reflection(_) => concrete!(Metadata),
Expand Down Expand Up @@ -880,6 +886,7 @@ impl NodeNetwork {
// Replace value inputs with dedicated value nodes
if node.implementation != DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("core_types::value::ClonedNode")) {
Self::replace_value_inputs_with_nodes(&mut node.inputs, &mut self.nodes, &path, gen_id, map_ids, id);
Self::replace_timeline_inputs_with_nodes(&mut node.inputs, &mut self.nodes, &path, gen_id, map_ids, id);
}

let DocumentNodeImplementation::Network(mut inner_network) = node.implementation else {
Expand All @@ -899,6 +906,14 @@ impl NodeNetwork {
map_ids,
id,
);
Self::replace_timeline_inputs_with_nodes(
&mut inner_network.exports,
&mut inner_network.nodes,
node.original_location.path.as_ref().unwrap_or(&vec![]),
gen_id,
map_ids,
id,
);

// Connect all network inputs to either the parent network nodes, or newly created value nodes for the parent node.
inner_network.map_ids(|inner_id| map_ids(id, inner_id));
Expand Down Expand Up @@ -940,6 +955,7 @@ impl NodeNetwork {
*import_index = parent_input_index;
}
NodeInput::Value { .. } => unreachable!("Value inputs should have been replaced with value nodes"),
NodeInput::Timeline { .. } => unreachable!("Timeline inputs should have been replaced with timeline value nodes"),
NodeInput::Inline(_) => (),
NodeInput::Scope(_) => unreachable!("Scope inputs should have been resolved by resolve_scope_inputs_recursive before flattening"),
NodeInput::Reflection(_) => unreachable!("Reflection inputs should have been replaced with value nodes"),
Expand Down Expand Up @@ -1029,6 +1045,72 @@ impl NodeNetwork {
}
}

#[inline(never)]
fn replace_timeline_inputs_with_nodes(
inputs: &mut [NodeInput],
collection: &mut FxHashMap<NodeId, DocumentNode>,
path: &[NodeId],
gen_id: impl Fn() -> NodeId + Copy,
map_ids: impl Fn(NodeId, NodeId) -> NodeId + Copy,
id: NodeId,
) {
for input in inputs {
let curve_id = match input {
NodeInput::Timeline { curve_id } => *curve_id,
_ => continue,
};

let make_value_node = |collection: &mut FxHashMap<NodeId, DocumentNode>, value: TaggedValue, dependant: NodeId| {
let node_id = gen_id();
let merged_id = map_ids(id, node_id);
let mut node_path = path.to_vec();
node_path.push(node_id);
collection.insert(
merged_id,
DocumentNode {
inputs: vec![NodeInput::value(value, false)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("core_types::value::ClonedNode")),
original_location: OriginalLocation {
path: Some(node_path),
dependants: vec![vec![dependant]],
..Default::default()
},
..Default::default()
},
);
merged_id
};

let eval_node_id = gen_id();
let eval_merged_id = map_ids(id, eval_node_id);

let primary_node_id = make_value_node(collection, TaggedValue::None, eval_merged_id);
let curve_id_node_id = make_value_node(collection, TaggedValue::U64(curve_id.0), eval_merged_id);

let mut eval_path = path.to_vec();
eval_path.push(eval_node_id);
collection.insert(
eval_merged_id,
DocumentNode {
inputs: vec![NodeInput::node(primary_node_id, 0), NodeInput::node(curve_id_node_id, 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::animation::TimelineEvalNode")),
context_features: ContextDependencies {
extract: ContextFeatures::ANIMATION_TIME | ContextFeatures::TIMELINE_CURVES,
inject: ContextFeatures::empty(),
},
original_location: OriginalLocation {
path: Some(eval_path),
dependants: vec![vec![id]],
..Default::default()
},
..Default::default()
},
);

*input = NodeInput::node(eval_merged_id, 0);
}
}

fn remove_passthrough_node(&mut self, id: NodeId) -> Result<(), String> {
let node = self.nodes.get(&id).ok_or_else(|| format!("Node with id {id} does not exist"))?.clone();
if let DocumentNodeImplementation::ProtoNode(ident) = &node.implementation
Expand Down
2 changes: 2 additions & 0 deletions node-graph/graph-craft/src/document/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::application_io::PlatformEditorApi;
use crate::application_io::resource::Resource;
use crate::proto::{Any as DAny, FutureAny};
use brush_nodes::brush_stroke::BrushStroke;
use core_types::animation::AnimationCurve;
use core_types::color::SRGBA8;
use core_types::list::List;
use core_types::transform::Footprint;
Expand Down Expand Up @@ -408,6 +409,7 @@ tagged_value! {
VectorModification(Box<VectorModification>),
ImageData(Image<Color>),
Resource(graphene_application_io::resource::ResourceId),
AnimationCurve(AnimationCurve),
// ==========
// ENUM TYPES
// ==========
Expand Down
4 changes: 4 additions & 0 deletions node-graph/interpreted-executor/src/node_registry.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use core_types::animation::AnimationCurve;
use dyn_any::StaticType;
use glam::{DAffine2, DVec2, IVec2};
use graph_craft::application_io::PlatformEditorApi;
Expand Down Expand Up @@ -171,6 +172,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::RedGreenBlue]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::RedGreenBlueAlpha]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::animation::RealTimeMode]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => AnimationCurve]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::NoiseType]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::FractalType]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::raster::adjustments::CellularDistanceFunction]),
Expand Down Expand Up @@ -199,6 +201,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => AttributeDyn, Context => graphene_std::ContextFeatures]),
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => AttributeValueDyn, Context => graphene_std::ContextFeatures]),
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => ListDyn, Context => graphene_std::ContextFeatures]),
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => AnimationCurve, Context => graphene_std::ContextFeatures]),
#[cfg(target_family = "wasm")]
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => CanvasHandle, Context => graphene_std::ContextFeatures]),
async_node!(graphene_core::context_modification::ContextModificationNode<_, _>, input: Context, fn_params: [Context => &PlatformEditorApi, Context => graphene_std::ContextFeatures]),
Expand Down Expand Up @@ -241,6 +244,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Footprint]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => RenderOutput]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => &PlatformEditorApi]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => AnimationCurve]),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => List<Raster<GPU>>]),
async_node!(graphene_core::memo::MemoizeNode<_, _>, input: Context, fn_params: [Context => Option<f64>]),
Expand Down
4 changes: 3 additions & 1 deletion node-graph/libraries/application-io/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use core_types::animation::AnimationCurveMap;
use core_types::transform::Footprint;
use dyn_any::{DynAny, StaticType, StaticTypeSized};
use glam::DVec2;
Expand Down Expand Up @@ -94,12 +95,13 @@ pub struct TimingInformation {
pub animation_time: Duration,
}

#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny)]
#[derive(Debug, Default, Clone, PartialEq, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RenderConfig {
pub viewport: Footprint,
pub scale: f64,
pub time: TimingInformation,
pub timeline_curves: Arc<AnimationCurveMap>,
pub pointer: DVec2,
#[cfg_attr(feature = "serde", serde(alias = "view_mode"))]
pub render_mode: RenderMode,
Expand Down
Loading