From 164e81e3438a52c1113a7539b801032fbecaebf9 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 11 Oct 2024 23:02:59 -0700 Subject: [PATCH] Output group from copy to points, add repeat for graphic groups, fix editor freeze on render fail --- .../portfolio/portfolio_message_handler.rs | 1 + editor/src/node_graph_executor.rs | 12 +- .../gcore/src/graphic_element/renderer.rs | 34 ++- .../src/vector/vector_data/attributes.rs | 11 +- node-graph/gcore/src/vector/vector_nodes.rs | 204 ++++++++++-------- 5 files changed, 157 insertions(+), 105 deletions(-) diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 86140428ab..8eede2d1c6 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -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 diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index a5a1ff7a8b..9dc90c37a2 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -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}; @@ -630,6 +630,7 @@ impl NodeGraphExecutor { } fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, transform: DAffine2, responses: &mut VecDeque) -> Result<(), String> { + let mut render_output_metadata = RenderMetadata::default(); match node_graph_output { TaggedValue::RenderOutput(render_output) => { match render_output.data { @@ -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), @@ -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(()) } } diff --git a/node-graph/gcore/src/graphic_element/renderer.rs b/node-graph/gcore/src/graphic_element/renderer.rs index de459e1834..4fcbdf0daa 100644 --- a/node-graph/gcore/src/graphic_element/renderer.rs +++ b/node-graph/gcore/src/graphic_element/renderer.rs @@ -272,7 +272,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, @@ -306,6 +306,12 @@ pub trait GraphicElementRendered { fn contains_artboard(&self) -> bool { false } + + fn new_ids_from_hash(&mut self, _reference: Option) {} + + fn to_graphic_element(&self) -> GraphicElement { + GraphicElement::default() + } } impl GraphicElementRendered for GraphicGroup { @@ -398,6 +404,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) { + 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 { @@ -587,6 +603,14 @@ impl GraphicElementRendered for VectorData { scene.pop_layer(); } } + + fn new_ids_from_hash(&mut self, reference: Option) { + 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 { @@ -968,6 +992,14 @@ impl GraphicElementRendered for GraphicElement { GraphicElement::Raster(raster) => raster.contains_artboard(), } } + + fn new_ids_from_hash(&mut self, reference: Option) { + 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`. This would not be an issue as we control that crate. diff --git a/node-graph/gcore/src/vector/vector_data/attributes.rs b/node-graph/gcore/src/vector/vector_data/attributes.rs index ca9778c49d..95139050fb 100644 --- a/node-graph/gcore/src/vector/vector_data/attributes.rs +++ b/node-graph/gcore/src/vector/vector_data/attributes.rs @@ -23,8 +23,9 @@ macro_rules! create_ids { Self(crate::uuid::generate_uuid()) } - pub fn generate_from_hash(self) -> Self { + 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) @@ -614,10 +615,10 @@ impl super::VectorData { self.segment_domain.transform(transform); } - pub fn new_ids_from_hash(&mut self) { - let point_map = self.point_domain.ids().iter().map(|&old| (old, old.generate_from_hash())).collect::>(); - let segment_map = self.segment_domain.ids().iter().map(|&old| (old, old.generate_from_hash())).collect::>(); - let region_map = self.region_domain.ids().iter().map(|&old| (old, old.generate_from_hash())).collect::>(); + 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::>(); + let segment_map = self.segment_domain.ids().iter().map(|&old| (old, old.generate_from_hash(node_id))).collect::>(); + let region_map = self.region_domain.ids().iter().map(|&old| (old, old.generate_from_hash(node_id))).collect::>(); let id_map = IdMap { point_offset: self.point_domain.ids().len(), diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index 0a2bb44d9e..0bf00e2c25 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -164,17 +164,22 @@ async fn stroke> + 'n + Send>( } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn repeat( +async fn repeat( #[implementations( (), + (), + Footprint, Footprint, )] footprint: F, + // TODO: Implement other GraphicElementRendered types. #[implementations( () -> VectorData, + () -> GraphicGroup, Footprint -> VectorData, + Footprint -> GraphicGroup, )] - instance: impl Node, + instance: impl Node, #[default(100., 100.)] // TODO: When using a custom Properties panel layout in document_node_definitions.rs and this default is set, the widget weirdly doesn't show up in the Properties panel. Investigation is needed. direction: DVec2, @@ -182,7 +187,7 @@ async fn repeat( #[default(4)] instances: IntegerCount, ) -> GraphicGroup { let instance = instance.eval(footprint).await; - let first_vector_transform = instance.transform; + let first_vector_transform = instance.transform(); let angle = angle.to_radians(); let instances = instances.max(1); @@ -190,7 +195,7 @@ async fn repeat( let mut result = GraphicGroup::EMPTY; - let Some(bounding_box) = instance.bounding_box_with_transform(instance.transform) else { + let Some(bounding_box) = instance.bounding_box(first_vector_transform) else { return result; }; @@ -199,41 +204,46 @@ async fn repeat( for i in 0..instances { let translation = i as f64 * direction / total; let angle = i as f64 * angle / total; - let mut new_vector_data = result.last().and_then(|(element, _)| element.as_vector_data()).unwrap_or(&instance).clone(); - new_vector_data.new_ids_from_hash(); + let mut new_instance = result.last().map(|(element, _)| element.clone()).unwrap_or(instance.to_graphic_element()); + new_instance.new_ids_from_hash(None); let modification = DAffine2::from_translation(center) * DAffine2::from_angle(angle) * DAffine2::from_translation(translation) * DAffine2::from_translation(-center); - let data_transform = new_vector_data.transform_mut(); + let data_transform = new_instance.transform_mut(); *data_transform = modification * first_vector_transform; - result.push((new_vector_data.into(), None)); + result.push((new_instance, None)); } result } #[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn circular_repeat( +async fn circular_repeat( #[implementations( (), + (), + Footprint, Footprint, )] footprint: F, + // TODO: Implement other GraphicElementRendered types. #[implementations( () -> VectorData, + () -> GraphicGroup, Footprint -> VectorData, + Footprint -> GraphicGroup, )] - instance: impl Node, + instance: impl Node, angle_offset: Angle, #[default(5)] radius: Length, #[default(5)] instances: IntegerCount, ) -> GraphicGroup { let instance = instance.eval(footprint).await; - let first_vector_transform = instance.transform; + let first_vector_transform = instance.transform(); let instances = instances.max(1); let mut result = GraphicGroup::EMPTY; - let Some(bounding_box) = instance.bounding_box_with_transform(instance.transform) else { + let Some(bounding_box) = instance.bounding_box(first_vector_transform) else { return result; }; @@ -245,12 +255,96 @@ async fn circular_repeat( let angle = (std::f64::consts::TAU / instances as f64) * i as f64 + angle_offset.to_radians(); let rotation = DAffine2::from_angle(angle); let modification = DAffine2::from_translation(center) * rotation * DAffine2::from_translation(base_transform); - let mut new_vector_data = result.last().and_then(|(element, _)| element.as_vector_data()).unwrap_or(&instance).clone(); - new_vector_data.new_ids_from_hash(); + let mut new_instance = result.last().map(|(element, _)| element.clone()).unwrap_or(instance.to_graphic_element()); + new_instance.new_ids_from_hash(None); - let data_transform = new_vector_data.transform_mut(); + let data_transform = new_instance.transform_mut(); *data_transform = modification * first_vector_transform; - result.push((new_vector_data.into(), None)); + result.push((new_instance, None)); + } + + result +} + +#[node_macro::node(category("Vector"), path(graphene_core::vector))] +async fn copy_to_points( + #[implementations( + (), + (), + Footprint, + )] + footprint: F, + #[implementations( + () -> VectorData, + () -> VectorData, + Footprint -> VectorData, + )] + points: impl Node, + #[expose] + #[implementations( + () -> VectorData, + () -> GraphicGroup, + Footprint -> VectorData, + Footprint -> GraphicGroup, + )] + instance: impl Node, + #[default(1)] random_scale_min: f64, + #[default(1)] random_scale_max: f64, + random_scale_bias: f64, + random_scale_seed: SeedValue, + random_rotation: Angle, + random_rotation_seed: SeedValue, +) -> GraphicGroup { + let points = points.eval(footprint).await; + let instance = instance.eval(footprint).await; + let instance_transform = instance.transform(); + + let random_scale_difference = random_scale_max - random_scale_min; + + let points_list = points.point_domain.positions(); + + let instance_bounding_box = instance.bounding_box(DAffine2::IDENTITY).unwrap_or_default(); + let instance_center = -0.5 * (instance_bounding_box[0] + instance_bounding_box[1]); + + let mut scale_rng = rand::rngs::StdRng::seed_from_u64(random_scale_seed.into()); + let mut rotation_rng = rand::rngs::StdRng::seed_from_u64(random_rotation_seed.into()); + + let do_scale = random_scale_difference.abs() > 1e-6; + let do_rotation = random_rotation.abs() > 1e-6; + + let mut result = GraphicGroup::default(); + for &point in points_list { + let center_transform = DAffine2::from_translation(instance_center); + + let translation = points.transform.transform_point2(point); + + let rotation = if do_rotation { + let degrees = (rotation_rng.gen::() - 0.5) * random_rotation; + degrees / 360. * std::f64::consts::TAU + } else { + 0. + }; + + let scale = if do_scale { + if random_scale_bias.abs() < 1e-6 { + // Linear + random_scale_min + scale_rng.gen::() * random_scale_difference + } else { + // Weighted (see ) + let horizontal_scale_factor = 1. - 2_f64.powf(random_scale_bias); + let scale_factor = (1. - scale_rng.gen::() * horizontal_scale_factor).log2() / random_scale_bias; + random_scale_min + scale_factor * random_scale_difference + } + } else { + random_scale_min + }; + + let mut new_instance = result.last().map(|(element, _)| element.clone()).unwrap_or(instance.to_graphic_element()); + new_instance.new_ids_from_hash(None); + + let data_transform = new_instance.transform_mut(); + *data_transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation) * center_transform * instance_transform; + result.push((new_instance, None)); } result @@ -432,84 +526,6 @@ impl ConcatElement for GraphicGroup { } } -#[node_macro::node(category("Vector"), path(graphene_core::vector))] -async fn copy_to_points( - #[implementations( - (), - (), - Footprint, - )] - footprint: F, - #[implementations( - () -> VectorData, - () -> VectorData, - Footprint -> VectorData, - )] - points: impl Node, - #[expose] - #[implementations( - () -> VectorData, - () -> GraphicGroup, - Footprint -> VectorData, - Footprint -> GraphicGroup, - )] - instance: impl Node, - #[default(1)] random_scale_min: f64, - #[default(1)] random_scale_max: f64, - random_scale_bias: f64, - random_scale_seed: SeedValue, - random_rotation: Angle, - random_rotation_seed: SeedValue, -) -> I { - let points = points.eval(footprint).await; - let instance = instance.eval(footprint).await; - - let random_scale_difference = random_scale_max - random_scale_min; - - let points_list = points.point_domain.positions(); - - let instance_bounding_box = instance.bounding_box(DAffine2::IDENTITY).unwrap_or_default(); - let instance_center = -0.5 * (instance_bounding_box[0] + instance_bounding_box[1]); - - let mut scale_rng = rand::rngs::StdRng::seed_from_u64(random_scale_seed.into()); - let mut rotation_rng = rand::rngs::StdRng::seed_from_u64(random_rotation_seed.into()); - - let do_scale = random_scale_difference.abs() > 1e-6; - let do_rotation = random_rotation.abs() > 1e-6; - - let mut result = I::default(); - for &point in points_list { - let center_transform = DAffine2::from_translation(instance_center); - - let translation = points.transform.transform_point2(point); - - let rotation = if do_rotation { - let degrees = (rotation_rng.gen::() - 0.5) * random_rotation; - degrees / 360. * std::f64::consts::TAU - } else { - 0. - }; - - let scale = if do_scale { - if random_scale_bias.abs() < 1e-6 { - // Linear - random_scale_min + scale_rng.gen::() * random_scale_difference - } else { - // Weighted (see ) - let horizontal_scale_factor = 1. - 2_f64.powf(random_scale_bias); - let scale_factor = (1. - scale_rng.gen::() * horizontal_scale_factor).log2() / random_scale_bias; - random_scale_min + scale_factor * random_scale_difference - } - } else { - random_scale_min - }; - - result.concat(&instance, DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation) * center_transform); - } - - result -} - #[node_macro::node(category(""))] async fn sample_points( #[implementations(