Fix Path tool's Path node transform calculation by skipping local transform based on first instance source ID (#2843)

* First instance source id

* Set source node id and migrations
This commit is contained in:
James Lindsay
2025-07-07 22:09:21 +00:00
committed by GitHub
parent 88c059a607
commit 4a83067081
15 changed files with 126 additions and 58 deletions
@@ -182,6 +182,7 @@ pub enum DocumentMessage {
UpdateUpstreamTransforms {
upstream_footprints: HashMap<NodeId, Footprint>,
local_transforms: HashMap<NodeId, DAffine2>,
first_instance_source_id: HashMap<NodeId, Option<NodeId>>,
},
UpdateClickTargets {
click_targets: HashMap<NodeId, Vec<ClickTarget>>,
@@ -1305,8 +1305,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
DocumentMessage::UpdateUpstreamTransforms {
upstream_footprints,
local_transforms,
first_instance_source_id,
} => {
self.network_interface.update_transforms(upstream_footprints, local_transforms);
self.network_interface.update_first_instance_source_id(first_instance_source_id);
}
DocumentMessage::UpdateClickTargets { click_targets } => {
// TODO: Allow non layer nodes to have click targets
@@ -1472,7 +1472,11 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::network(concrete!(graphene_std::vector::VectorModification), 1)],
inputs: vec![
NodeInput::node(NodeId(0), 0),
NodeInput::network(concrete!(graphene_std::vector::VectorModification), 1),
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
],
manual_composition: Some(generic!(T)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::vector_data::modification::PathModifyNode")),
..Default::default()
@@ -119,7 +119,7 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
let transform = document.metadata().transform_to_viewport(layer);
let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
if display_path {
overlay_context.outline_vector(&vector_data, transform);
}
@@ -196,7 +196,7 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &
continue;
};
//let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
let transform = document.metadata().transform_to_viewport(layer);
let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
let selected = shape_editor.selected_shape_state.get(&layer);
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point));
@@ -1,6 +1,8 @@
use super::network_interface::NodeNetworkInterface;
use crate::messages::portfolio::document::graph_operation::transform_utils;
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;
use crate::messages::portfolio::document::utility_types::network_interface::FlowType;
use crate::messages::tool::common_functionality::graph_modification_utils;
use glam::{DAffine2, DVec2};
use graph_craft::document::NodeId;
use graphene_std::math::quad::Quad;
@@ -16,10 +18,11 @@ use std::num::NonZeroU64;
// TODO: To avoid storing a stateful snapshot of some other system's state (which is easily to accidentally get out of sync),
// TODO: it might be better to have a system that can query the state of the node network on demand.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Default)]
pub struct DocumentMetadata {
pub upstream_footprints: HashMap<NodeId, Footprint>,
pub local_transforms: HashMap<NodeId, DAffine2>,
pub first_instance_source_ids: HashMap<NodeId, Option<NodeId>>,
pub structure: HashMap<LayerNodeIdentifier, NodeRelations>,
pub click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
pub clip_targets: HashSet<NodeId>,
@@ -28,20 +31,6 @@ pub struct DocumentMetadata {
pub document_to_viewport: DAffine2,
}
impl Default for DocumentMetadata {
fn default() -> Self {
Self {
upstream_footprints: HashMap::new(),
local_transforms: HashMap::new(),
structure: HashMap::new(),
vector_modify: HashMap::new(),
click_targets: HashMap::new(),
clip_targets: HashSet::new(),
document_to_viewport: DAffine2::IDENTITY,
}
}
}
// =================================
// DocumentMetadata: Layer iterators
// =================================
@@ -91,6 +80,36 @@ impl DocumentMetadata {
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 mut use_local = true;
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface);
if let Some(path_node) = graph_layer.upstream_node_id_from_name("Path") {
if let Some(&source) = self.first_instance_source_ids.get(&layer.to_node()) {
if !network_interface
.upstream_flow_back_from_nodes(vec![path_node], &[], FlowType::HorizontalFlow)
.any(|upstream| Some(upstream) == source)
{
use_local = false;
info!("Local transform is invalid — using the identity for the local transform instead")
}
}
}
let local_transform = use_local.then(|| self.local_transforms.get(&layer.to_node()).copied()).flatten().unwrap_or_default();
footprint * local_transform
}
pub fn transform_to_document_if_feeds(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {
self.document_to_viewport.inverse() * self.transform_to_viewport_if_feeds(layer, network_interface)
}
pub fn transform_to_viewport_with_first_transform_node_if_group(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {
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();
@@ -60,6 +60,27 @@ impl PartialEq for NodeNetworkInterface {
}
}
impl NodeNetworkInterface {
/// Add DocumentNodePath input to the PathModifyNode protonode
pub fn migrate_path_modify_node(&mut self) {
fix_network(&mut self.network);
fn fix_network(network: &mut NodeNetwork) {
for node in network.nodes.values_mut() {
if let Some(network) = node.implementation.get_network_mut() {
fix_network(network);
}
if let DocumentNodeImplementation::ProtoNode(protonode) = &node.implementation {
if protonode.name.contains("PathModifyNode") {
if node.inputs.len() < 3 {
node.inputs.push(NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath));
}
}
}
}
}
}
}
// Public immutable getters for the network interface
impl NodeNetworkInterface {
// TODO: Make private and use .field_name getter methods
@@ -3513,6 +3534,11 @@ impl NodeNetworkInterface {
self.document_metadata.local_transforms = local_transforms;
}
/// Update the cached first instance source id of the layers
pub fn update_first_instance_source_id(&mut self, new: HashMap<NodeId, Option<NodeId>>) {
self.document_metadata.first_instance_source_ids = new;
}
/// Update the cached click targets of the layers
pub fn update_click_targets(&mut self, new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>) {
self.document_metadata.click_targets = new_click_targets;