Skip to content

Commit

Permalink
Make Copy to Points and (Circular) Repeat and nodes output group data…
Browse files Browse the repository at this point in the history
…, and add flattening nodes (#2011)

* Output group from repeat, add flatten vector elements

* Fix tests

* Fix demo artwork

* Output group from copy to points, add repeat for graphic groups, fix editor freeze on render fail

* Restore painted dreams

* WIP: Fix demo artwork

* Fix demo artwork, add ungroup node

* Incorrect scaling

* fix test

* Fix demo art

---------

Co-authored-by: Keavon Chambers <[email protected]>
  • Loading branch information
adamgerhant and Keavon authored Oct 14, 2024
1 parent b028bbb commit e09f5ec
Show file tree
Hide file tree
Showing 14 changed files with 331 additions and 143 deletions.
2 changes: 1 addition & 1 deletion demo-artwork/changing-seasons.graphite

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demo-artwork/painted-dreams.graphite

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demo-artwork/procedural-string-lights.graphite

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion demo-artwork/red-dress.graphite

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -239,18 +239,28 @@ impl<'a> ModifyInputsContext<'a> {
// Gets the node id of a node with a specific reference that is upstream from the layer node, and creates it if it does not exist
pub fn existing_node_id(&mut self, reference: &'static str) -> Option<NodeId> {
// Start from the layer node or export
let node_id = self.get_output_layer().map_or(Vec::new(), |output_layer| vec![output_layer.to_node()]);
let output_layer = self.get_output_layer()?;
let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]).0.nested_type();

let upstream = self.network_interface.upstream_flow_back_from_nodes(node_id, &[], network_interface::FlowType::HorizontalFlow);

let is_traversal_start = |node_id: NodeId| {
self.layer_node.map(|layer| layer.to_node()) == Some(node_id) || self.network_interface.network(&[]).unwrap().exports.iter().any(|export| export.as_node() == Some(node_id))
};
let upstream = self
.network_interface
.upstream_flow_back_from_nodes(vec![output_layer.to_node()], &[], network_interface::FlowType::HorizontalFlow);

// Take until another layer node is found (but not the first layer node)
let existing_node_id = upstream
.take_while(|node_id| is_traversal_start(*node_id) || !self.network_interface.is_layer(node_id, &[]))
.find(|node_id| self.network_interface.reference(node_id, &[]).is_some_and(|node_reference| node_reference == reference));
let mut existing_node_id = None;
for upstream_node in upstream.collect::<Vec<_>>() {
let upstream_node_input_type = self.network_interface.input_type(&InputConnector::node(upstream_node, 0), &[]).0.nested_type();
let is_traversal_start = |node_id: NodeId| {
self.layer_node.map(|layer| layer.to_node()) == Some(node_id) || self.network_interface.network(&[]).unwrap().exports.iter().any(|export| export.as_node() == Some(node_id))
};
if !is_traversal_start(upstream_node) && (self.network_interface.is_layer(&upstream_node, &[]) || upstream_node_input_type != layer_input_type) {
break;
}
if self.network_interface.reference(&upstream_node, &[]).is_some_and(|node_reference| node_reference == reference) {
existing_node_id = Some(upstream_node);
break;
}
}

// Create a new node if the node does not exist and update its inputs
existing_node_id.or_else(|| {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -752,8 +752,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
return;
};

responses.add(DocumentMessage::EndTransaction);

if let Some(preview_node) = self.preview_on_mouse_up {
responses.add(NodeGraphMessage::TogglePreview { node_id: preview_node });
self.preview_on_mouse_up = None;
Expand Down Expand Up @@ -992,6 +990,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
self.box_selection_start = None;
self.wire_in_progress_from_connector = None;
self.wire_in_progress_to_connector = None;
responses.add(DocumentMessage::EndTransaction);
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
responses.add(FrontendMessage::UpdateBox { box_selection: None })
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3603,7 +3603,7 @@ impl NodeNetworkInterface {
};

// Check whether the being-deleted node's first (primary) input is a node
let mut reconnect_to_input = network.nodes.get(node_id).and_then(|node| {
let reconnect_to_input = network.nodes.get(node_id).and_then(|node| {
node.inputs
.iter()
.find(|input| input.is_exposed_to_frontend(network_path.is_empty()))
Expand All @@ -3625,15 +3625,22 @@ impl NodeNetworkInterface {

let mut reconnect_node = None;

// Don't reconnect if the upstream node is a layer and there are multiple downstream inputs to reconnect
let multiple_disconnect_and_upstream_is_layer = downstream_inputs_to_disconnect.len() > 1
&& reconnect_to_input
.as_ref()
.is_some_and(|upstream_input| upstream_input.as_node().map_or(false, |node_id| self.is_layer(&node_id, network_path)));

for downstream_input in &downstream_inputs_to_disconnect {
self.disconnect_input(downstream_input, network_path);
// Prevent reconnecting export to import until https://github.com/GraphiteEditor/Graphite/issues/1762 is solved
if !(matches!(reconnect_to_input, Some(NodeInput::Network { .. })) && matches!(downstream_input, InputConnector::Export(_))) {
if let Some(reconnect_input) = reconnect_to_input.take() {
// Get the reconnect node position only if it is in a stack
if let Some(reconnect_input) = &reconnect_to_input {
reconnect_node = reconnect_input.as_node().and_then(|node_id| if self.is_stack(&node_id, network_path) { Some(node_id) } else { None });
self.disconnect_input(&InputConnector::node(*node_id, 0), network_path);
self.set_input(downstream_input, reconnect_input, network_path);
if !multiple_disconnect_and_upstream_is_layer {
self.set_input(downstream_input, reconnect_input.clone(), network_path);
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions editor/src/messages/portfolio/portfolio_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -963,6 +963,7 @@ impl PortfolioMessageHandler {
/text>"#
// It's a mystery why the `/text>` tag above needs to be missing its `<`, but when it exists it prints the `<` character in the text. However this works with it removed.
.to_string();
responses.add(Message::EndBuffer(graphene_std::renderer::RenderMetadata::default()));
responses.add(FrontendMessage::UpdateDocumentArtwork { svg: error });
}
result
Expand Down
12 changes: 7 additions & 5 deletions editor/src/node_graph_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use graphene_core::renderer::{RenderSvgSegmentList, SvgSegment};
use graphene_core::text::FontCache;
use graphene_core::transform::Footprint;
use graphene_core::vector::style::ViewMode;
use graphene_std::renderer::format_transform_matrix;
use graphene_std::renderer::{format_transform_matrix, RenderMetadata};
use graphene_std::vector::VectorData;
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};
Expand Down Expand Up @@ -630,6 +630,7 @@ impl NodeGraphExecutor {
}

fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, transform: DAffine2, responses: &mut VecDeque<Message>) -> Result<(), String> {
let mut render_output_metadata = RenderMetadata::default();
match node_graph_output {
TaggedValue::RenderOutput(render_output) => {
match render_output.data {
Expand All @@ -651,10 +652,7 @@ impl NodeGraphExecutor {
}
}

responses.add(Message::EndBuffer(render_output.metadata));
responses.add(DocumentMessage::RenderScrollbars);
responses.add(DocumentMessage::RenderRulers);
responses.add(OverlaysMessage::Draw);
render_output_metadata = render_output.metadata;
}
TaggedValue::Bool(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::String(render_object) => Self::debug_render(render_object, transform, responses),
Expand All @@ -669,6 +667,10 @@ impl NodeGraphExecutor {
return Err(format!("Invalid node graph output type: {node_graph_output:#?}"));
}
};
responses.add(Message::EndBuffer(render_output_metadata));
responses.add(DocumentMessage::RenderScrollbars);
responses.add(DocumentMessage::RenderRulers);
responses.add(OverlaysMessage::Draw);
Ok(())
}
}
42 changes: 42 additions & 0 deletions node-graph/gcore/src/graphic_element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,48 @@ async fn to_group<F: 'n + Send, Data: Into<GraphicGroup> + 'n>(
element.eval(footprint).await.into()
}

#[node_macro::node(category("General"))]
async fn flatten_group<F: 'n + Send>(
#[implementations(
(),
Footprint,
)]
footprint: F,
#[implementations(
() -> GraphicGroup,
Footprint -> GraphicGroup,
)]
group: impl Node<F, Output = GraphicGroup>,
fully_flatten: bool,
) -> GraphicGroup {
let nested_group = group.eval(footprint).await;
let mut flat_group = GraphicGroup::EMPTY;
fn flatten_group(result_group: &mut GraphicGroup, current_group: GraphicGroup, fully_flatten: bool) {
let mut collection_group = GraphicGroup::EMPTY;
for (element, reference) in current_group.elements {
if let GraphicElement::GraphicGroup(mut nested_group) = element {
nested_group.transform *= current_group.transform;
let mut sub_group = GraphicGroup::EMPTY;
if fully_flatten {
flatten_group(&mut sub_group, nested_group, fully_flatten);
} else {
for (collection_element, _) in &mut nested_group.elements {
*collection_element.transform_mut() = nested_group.transform * collection_element.transform();
}
sub_group = nested_group;
}
collection_group.append(&mut sub_group.elements);
} else {
collection_group.push((element, reference));
}
}

result_group.append(&mut collection_group.elements);
}
flatten_group(&mut flat_group, nested_group, fully_flatten);
flat_group
}

#[node_macro::node(category(""))]
async fn to_artboard<F: 'n + Send + ApplyTransform, Data: Into<GraphicGroup> + 'n>(
#[implementations(
Expand Down
34 changes: 33 additions & 1 deletion node-graph/gcore/src/graphic_element/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ pub fn to_transform(transform: DAffine2) -> usvg::Transform {

// TODO: Click targets can be removed from the render output, since the vector data is available in the vector modify data from Monitor nodes.
// This will require that the transform for child layers into that layer space be calculated, or it could be returned from the RenderOutput instead of click targets.
#[derive(Debug, Clone, PartialEq, DynAny)]
#[derive(Debug, Default, Clone, PartialEq, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RenderMetadata {
pub footprints: HashMap<NodeId, (Footprint, DAffine2)>,
Expand Down Expand Up @@ -301,6 +301,12 @@ pub trait GraphicElementRendered {
fn contains_artboard(&self) -> bool {
false
}

fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {}

fn to_graphic_element(&self) -> GraphicElement {
GraphicElement::default()
}
}

impl GraphicElementRendered for GraphicGroup {
Expand Down Expand Up @@ -393,6 +399,16 @@ impl GraphicElementRendered for GraphicGroup {
fn contains_artboard(&self) -> bool {
self.iter().any(|(element, _)| element.contains_artboard())
}

fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
for (element, node_id) in self.elements.iter_mut() {
element.new_ids_from_hash(*node_id);
}
}

fn to_graphic_element(&self) -> GraphicElement {
GraphicElement::GraphicGroup(self.clone())
}
}

impl GraphicElementRendered for VectorData {
Expand Down Expand Up @@ -582,6 +598,14 @@ impl GraphicElementRendered for VectorData {
scene.pop_layer();
}
}

fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
self.vector_new_ids_from_hash(reference.map(|id| id.0).unwrap_or_default());
}

fn to_graphic_element(&self) -> GraphicElement {
GraphicElement::VectorData(Box::new(self.clone()))
}
}

impl GraphicElementRendered for Artboard {
Expand Down Expand Up @@ -948,6 +972,14 @@ impl GraphicElementRendered for GraphicElement {
GraphicElement::Raster(raster) => raster.contains_artboard(),
}
}

fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
match self {
GraphicElement::VectorData(vector_data) => vector_data.new_ids_from_hash(reference),
GraphicElement::GraphicGroup(graphic_group) => graphic_group.new_ids_from_hash(reference),
GraphicElement::Raster(_) => (),
}
}
}

/// Used to stop rust complaining about upstream traits adding display implementations to `Option<Color>`. This would not be an issue as we control that crate.
Expand Down
1 change: 0 additions & 1 deletion node-graph/gcore/src/vector/generator_nodes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ fn spline<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, _primar
}

// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node

#[node_macro::node(category(""))]
fn path<F: 'n + Send>(#[implementations((), Footprint)] _footprint: F, path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> super::VectorData {
let mut vector_data = super::VectorData::from_subpaths(path_data, false);
Expand Down
41 changes: 41 additions & 0 deletions node-graph/gcore/src/vector/vector_data/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use dyn_any::DynAny;

use glam::{DAffine2, DVec2};
use std::collections::HashMap;
use std::hash::{Hash, Hasher};

/// A simple macro for creating strongly typed ids (to avoid confusion when passing around ids).
macro_rules! create_ids {
Expand All @@ -22,6 +23,14 @@ macro_rules! create_ids {
Self(crate::uuid::generate_uuid())
}

pub fn generate_from_hash(self, node_id: u64) -> Self {
let mut hasher = std::hash::DefaultHasher::new();
node_id.hash(&mut hasher);
self.hash(&mut hasher);
let hash_value = hasher.finish();
Self(hash_value)
}

/// Gets the inner raw value.
pub fn inner(self) -> u64 {
self.0
Expand Down Expand Up @@ -161,6 +170,10 @@ impl PointDomain {
self.positions.extend(other.positions.iter().map(|&pos| transform.transform_point2(pos)));
}

fn map_ids(&mut self, id_map: &IdMap) {
self.id.iter_mut().for_each(|id| *id = *id_map.point_map.get(id).unwrap_or(id));
}

fn transform(&mut self, transform: DAffine2) {
for pos in &mut self.positions {
*pos = transform.transform_point2(*pos);
Expand Down Expand Up @@ -353,6 +366,10 @@ impl SegmentDomain {
self.stroke.extend(&other.stroke);
}

fn map_ids(&mut self, id_map: &IdMap) {
self.ids.iter_mut().for_each(|id| *id = *id_map.segment_map.get(id).unwrap_or(id));
}

fn transform(&mut self, transform: DAffine2) {
for handles in &mut self.handles {
*handles = handles.apply_transformation(|p| transform.transform_point2(p));
Expand Down Expand Up @@ -460,6 +477,13 @@ impl RegionDomain {
);
self.fill.extend(&other.fill);
}

fn map_ids(&mut self, id_map: &IdMap) {
self.ids.iter_mut().for_each(|id| *id = *id_map.region_map.get(id).unwrap_or(id));
self.segment_range
.iter_mut()
.for_each(|range| *range = *id_map.segment_map.get(range.start()).unwrap_or(range.start())..=*id_map.segment_map.get(range.end()).unwrap_or(range.end()));
}
}

impl super::VectorData {
Expand Down Expand Up @@ -590,6 +614,23 @@ impl super::VectorData {
self.point_domain.transform(transform);
self.segment_domain.transform(transform);
}

pub fn vector_new_ids_from_hash(&mut self, node_id: u64) {
let point_map = self.point_domain.ids().iter().map(|&old| (old, old.generate_from_hash(node_id))).collect::<HashMap<_, _>>();
let segment_map = self.segment_domain.ids().iter().map(|&old| (old, old.generate_from_hash(node_id))).collect::<HashMap<_, _>>();
let region_map = self.region_domain.ids().iter().map(|&old| (old, old.generate_from_hash(node_id))).collect::<HashMap<_, _>>();

let id_map = IdMap {
point_offset: self.point_domain.ids().len(),
point_map,
segment_map,
region_map,
};

self.point_domain.map_ids(&id_map);
self.segment_domain.map_ids(&id_map);
self.region_domain.map_ids(&id_map);
}
}

#[derive(Clone, Copy, PartialEq, Eq, Debug)]
Expand Down
Loading

0 comments on commit e09f5ec

Please sign in to comment.