From 86a3089249244f813d440b210e8e6999f29ce72e Mon Sep 17 00:00:00 2001 From: Sahil Gupta Date: Fri, 9 Jan 2026 20:17:40 +0530 Subject: [PATCH 1/2] Fix Exports having 0 layers --- .../document/document_message_handler.rs | 12 ++----- .../utility_types/network_interface.rs | 32 +++++++++++++++++-- node-graph/nodes/gstd/src/render_node.rs | 2 +- 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 6402f609fd..d3fa3f0f4c 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -1401,17 +1401,11 @@ impl MessageHandler> for DocumentMes self.network_interface.update_first_element_source_id(first_element_source_id); } DocumentMessage::UpdateClickTargets { click_targets } => { - // TODO: Allow non layer nodes to have click targets let layer_click_targets = click_targets .into_iter() - .filter(|(node_id, _)| - // Ensure that the layer is in the document network to prevent logging an error - self.network_interface.document_network().nodes.contains_key(node_id)) - .filter_map(|(node_id, click_targets)| { - self.network_interface.is_layer(&node_id, &[]).then(|| { - let layer = LayerNodeIdentifier::new(node_id, &self.network_interface); - (layer, click_targets) - }) + .map(|(node_id, click_targets)| { + let layer = LayerNodeIdentifier::new_unchecked(node_id); + (layer, click_targets) }) .collect(); self.network_interface.update_click_targets(layer_click_targets); diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index ed485a1f06..89698bfdfa 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -1168,7 +1168,8 @@ impl NodeNetworkInterface { /// Calculates the document bounds in document space pub fn document_bounds_document_space(&self, include_artboards: bool) -> Option<[DVec2; 2]> { - self.document_metadata + let all_layers_bounds = self + .document_metadata .all_layers() .filter(|layer| include_artboards || !self.is_artboard(&layer.to_node(), &[])) .filter_map(|layer| { @@ -1190,7 +1191,34 @@ impl NodeNetworkInterface { } self.document_metadata.bounding_box_document(layer) }) - .reduce(Quad::combine_bounds) + .reduce(Quad::combine_bounds); + + let root_artwork_bounds = self.document_metadata().bounding_box_document(LayerNodeIdentifier::ROOT_PARENT); + + let non_layer_export_bounds = self + .document_network() + .exports + .iter() + .filter_map(|export| { + if let NodeInput::Node { node_id, .. } = export { + if !self.is_layer(node_id, &[]) { + return self.document_metadata().bounding_box_document(LayerNodeIdentifier::new_unchecked(*node_id)); + } + } + None + }) + .reduce(Quad::combine_bounds); + + match (all_layers_bounds, root_artwork_bounds, non_layer_export_bounds) { + (Some(a), Some(b), Some(c)) => Some(Quad::combine_bounds(Quad::combine_bounds(a, b), c)), + (Some(a), Some(b), None) => Some(Quad::combine_bounds(a, b)), + (Some(a), None, Some(c)) => Some(Quad::combine_bounds(a, c)), + (None, Some(b), Some(c)) => Some(Quad::combine_bounds(b, c)), + (Some(a), None, None) => Some(a), + (None, Some(b), None) => Some(b), + (None, None, Some(c)) => Some(c), + (None, None, None) => None, + } } /// Calculates the selected layer bounds in document space diff --git a/node-graph/nodes/gstd/src/render_node.rs b/node-graph/nodes/gstd/src/render_node.rs index f043253a5c..304af97b08 100644 --- a/node-graph/nodes/gstd/src/render_node.rs +++ b/node-graph/nodes/gstd/src/render_node.rs @@ -56,7 +56,7 @@ async fn render_intermediate<'a: 'n, T: 'static + Render + WasmNotSend + Send + let footprint = Footprint::default(); let mut metadata = RenderMetadata::default(); - data.collect_metadata(&mut metadata, footprint, None); + data.collect_metadata(&mut metadata, footprint, Some(graph_craft::document::NodeId(0))); let contains_artboard = data.contains_artboard(); match &render_params.render_output_type { From 8dc1f941bca4ebf54b02d1e87beef7d0d83e2c71 Mon Sep 17 00:00:00 2001 From: Sahil Gupta Date: Sat, 31 Jan 2026 14:38:52 +0530 Subject: [PATCH 2/2] add sm padding --- .../utility_types/document_metadata.rs | 23 ++++----------- .../utility_types/network_interface.rs | 28 ++++--------------- editor/src/node_graph_executor.rs | 12 +++++++- .../libraries/rendering/src/renderer.rs | 10 +++++-- 4 files changed, 29 insertions(+), 44 deletions(-) diff --git a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs index 7ba8eaa7ae..d96af6c2dd 100644 --- a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs +++ b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs @@ -70,24 +70,16 @@ impl DocumentMetadata { } pub fn transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 { - // We're not allowed to convert the root parent to a node id - if layer == LayerNodeIdentifier::ROOT_PARENT { - return self.document_to_viewport; - } - - let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport); - let local_transform = self.local_transforms.get(&layer.to_node()).copied().unwrap_or_default(); + let node_id = if layer == LayerNodeIdentifier::ROOT_PARENT { NodeId(0) } else { layer.to_node() }; + let footprint = self.upstream_footprints.get(&node_id).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport); + let local_transform = self.local_transforms.get(&node_id).copied().unwrap_or_default(); footprint * local_transform } pub fn transform_to_viewport_if_feeds(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 { - // We're not allowed to convert the root parent to a node id - if layer == LayerNodeIdentifier::ROOT_PARENT { - return self.document_to_viewport; - } - - let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport); + let node_id = if layer == LayerNodeIdentifier::ROOT_PARENT { NodeId(0) } else { layer.to_node() }; + let footprint = self.upstream_footprints.get(&node_id).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport); let mut use_local = true; let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface); @@ -264,10 +256,7 @@ impl LayerNodeIdentifier { /// Access the node id of this layer pub fn to_node(self) -> NodeId { - let id = NodeId(u64::from(self.0) - 1); - debug_assert!(id != NodeId(0), "LayerNodeIdentifier::ROOT_PARENT cannot be converted to NodeId"); - - id + NodeId(u64::from(self.0) - 1) } /// Access the parent layer if possible diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 58c415ef0f..6ca6ed2021 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -1195,29 +1195,11 @@ impl NodeNetworkInterface { let root_artwork_bounds = self.document_metadata().bounding_box_document(LayerNodeIdentifier::ROOT_PARENT); - let non_layer_export_bounds = self - .document_network() - .exports - .iter() - .filter_map(|export| { - if let NodeInput::Node { node_id, .. } = export { - if !self.is_layer(node_id, &[]) { - return self.document_metadata().bounding_box_document(LayerNodeIdentifier::new_unchecked(*node_id)); - } - } - None - }) - .reduce(Quad::combine_bounds); - - match (all_layers_bounds, root_artwork_bounds, non_layer_export_bounds) { - (Some(a), Some(b), Some(c)) => Some(Quad::combine_bounds(Quad::combine_bounds(a, b), c)), - (Some(a), Some(b), None) => Some(Quad::combine_bounds(a, b)), - (Some(a), None, Some(c)) => Some(Quad::combine_bounds(a, c)), - (None, Some(b), Some(c)) => Some(Quad::combine_bounds(b, c)), - (Some(a), None, None) => Some(a), - (None, Some(b), None) => Some(b), - (None, None, Some(c)) => Some(c), - (None, None, None) => None, + match (all_layers_bounds, root_artwork_bounds) { + (Some(a), Some(b)) => Some(Quad::combine_bounds(a, b)), + (Some(a), None) => Some(a), + (None, Some(b)) => Some(b), + (None, None) => None, } } diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index cb174e8034..f5cfbd9077 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -195,12 +195,22 @@ impl NodeGraphExecutor { }; // Calculate the bounding box of the region to be exported - let bounds = match export_config.bounds { + let mut bounds = match export_config.bounds { ExportBounds::AllArtwork => document.network_interface.document_bounds_document_space(!export_config.transparent_background), ExportBounds::Selection => document.network_interface.selected_bounds_document_space(!export_config.transparent_background, &[]), ExportBounds::Artboard(id) => document.metadata().bounding_box_document(id), } .ok_or_else(|| "No bounding box".to_string())?; + + // Add a small amount of padding to the bounding box if we are exporting everything or a selection. + // This provides a better framing and ensures we don't clip the outer half of strokes at the edge. + if matches!(export_config.bounds, ExportBounds::AllArtwork | ExportBounds::Selection) { + let size = (bounds[1] - bounds[0]).max(DVec2::splat(1.)); + let padding = size * 0.05; + bounds[0] -= padding; + bounds[1] += padding; + } + let resolution = (bounds[1] - bounds[0]).round().as_uvec2(); let transform = DAffine2::from_translation(bounds[0]).inverse(); diff --git a/node-graph/libraries/rendering/src/renderer.rs b/node-graph/libraries/rendering/src/renderer.rs index fcea51c45c..dfc4f8403a 100644 --- a/node-graph/libraries/rendering/src/renderer.rs +++ b/node-graph/libraries/rendering/src/renderer.rs @@ -307,8 +307,12 @@ impl Render for Graphic { fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option) { if let Some(element_id) = element_id { match self { - Graphic::Graphic(_) => { + Graphic::Graphic(table) => { metadata.upstream_footprints.insert(element_id, footprint); + if let Some(row) = table.iter().next() { + metadata.first_element_source_id.insert(element_id, *row.source_node_id); + metadata.local_transforms.insert(element_id, *row.transform); + } } Graphic::Vector(table) => { metadata.upstream_footprints.insert(element_id, footprint); @@ -505,9 +509,9 @@ impl Render for Table { } } - fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option) { + fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option) { for row in self.iter() { - row.element.collect_metadata(metadata, footprint, *row.source_node_id); + row.element.collect_metadata(metadata, footprint, row.source_node_id.or(element_id)); } }