Skip to content

Commit ab99e0e

Browse files
authored
Record vector pool path structure in Path node edits and chain older edits into paths on load (#4738)
* Chain segments by their connections when weaving the working form * Chain legacy segment soups into paths in the order they used to be drawn * Record where a Path node's paths begin and chain older stored edits into paths on load * Resolve turned segments through network ports and mark path starts after removals by what arrives
1 parent e1bc46a commit ab99e0e

14 files changed

Lines changed: 1380 additions & 190 deletions

File tree

‎editor/src/messages/clipboard/clipboard_message_handler.rs‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,7 @@ impl MessageHandler<ClipboardMessage, ClipboardMessageContext<'_>> for Clipboard
454454

455455
// Create new segment IDs and add the segments into the existing Vector path
456456
let mut segments_map = HashMap::new();
457+
let path_beginnings = pasted_vector.path_beginnings(&pasted_paths);
457458
for (segment_id, segment, start, end) in pasted_vector.segment_iter(&pasted_paths) {
458459
let (Some(&start_point), Some(&end_point)) = (points_map.get(&start), points_map.get(&end)) else {
459460
warn!("Skipping pasted vector segment with an unknown endpoint");
@@ -474,6 +475,15 @@ impl MessageHandler<ClipboardMessage, ClipboardMessageContext<'_>> for Clipboard
474475
let points = [start_point, end_point];
475476
let modification_type = VectorModificationType::InsertSegment { id: new_segment_id, points, handles };
476477
responses.add(GraphOperationMessage::Vector { layer, modification_type });
478+
479+
// Every pasted path stays a path of its own, even where it sets out from the end of another
480+
if path_beginnings.contains(&segment_id) {
481+
let modification_type = VectorModificationType::SetPathBreak {
482+
segment: new_segment_id,
483+
breaks: true,
484+
};
485+
responses.add(GraphOperationMessage::Vector { layer, modification_type });
486+
}
477487
}
478488

479489
// Set G1 continuity

‎editor/src/messages/portfolio/document_migration.rs‎

Lines changed: 243 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use graphene_std::transform::ScaleType;
2121
use graphene_std::uuid::NodeId;
2222
use graphene_std::vector::graphic_types;
2323
use graphene_std::vector::style::{GradientRamp, GradientSpread, PaintOrder, StrokeAlign};
24-
use std::collections::HashMap;
24+
use std::collections::{HashMap, HashSet};
2525
use std::f64::consts::PI;
2626
use std::ops::Range;
2727

@@ -1634,6 +1634,7 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
16341634
migrate_node(node_id, node, network_path, document, reset_node_definitions_on_open);
16351635
}
16361636
create_layer_stacks(document);
1637+
follow_segments_turned_on_load(document);
16371638

16381639
// The old geometry-producing "Text" node was split into the current "Text" (`String[]`) -> converter pair, which reuses the same proto
16391640
// identifier. Runs after `migrate_node` normalizes old text nodes to the legacy 13-input layout, distinguished from the current 12-input
@@ -3347,6 +3348,136 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
33473348
Some(())
33483349
}
33493350

3351+
// TODO: Eventually remove this document upgrade code
3352+
/// Older stored edits are turned around where they chain into paths on load. Edits naming those segments by side (a handle,
3353+
/// an endpoint, or a G1 pair) in the Path nodes downstream of the one that turned them are turned with them, so each stays on
3354+
/// the side it was made on. Segment IDs are only unique within one shape, so nodes not downstream are left alone.
3355+
fn follow_segments_turned_on_load(document: &mut DocumentMessageHandler) {
3356+
for (network_path, node_id, index, input) in modifications_following_turned_segments(document.network_interface.document_network()) {
3357+
document.network_interface.set_input(&InputConnector::node_at_index(node_id, index), input, &network_path);
3358+
}
3359+
}
3360+
3361+
// TODO: Eventually remove this document upgrade code
3362+
/// The stored edits to rewrite after loading, each with its network path, node, and input index: those that turned segments,
3363+
/// with their record taken, and those downstream of them, turned to follow. Costs O(nodes + wires) per node that turned any.
3364+
fn modifications_following_turned_segments(network: &graph_craft::document::NodeNetwork) -> Vec<(Vec<NodeId>, NodeId, usize, NodeInput)> {
3365+
type Location = (Vec<NodeId>, NodeId);
3366+
3367+
// The nodes reading each node's data, wired straight to the node producing it through nested networks' imports and exports
3368+
let mut readers: HashMap<Location, Vec<Location>> = HashMap::new();
3369+
let mut turned_by: Vec<(Location, HashSet<graphene_std::vector::SegmentId>)> = Vec::new();
3370+
for (node_id, node, network_path) in network.recursive_nodes() {
3371+
let here = (network_path.clone(), *node_id);
3372+
3373+
for input in &node.inputs {
3374+
if let NodeInput::Value { tagged_value, .. } = input
3375+
&& let TaggedValue::VectorModification(modification) = &**tagged_value
3376+
&& !modification.segments_turned_on_load().is_empty()
3377+
{
3378+
turned_by.push((here.clone(), modification.segments_turned_on_load().clone()));
3379+
}
3380+
}
3381+
3382+
// A network node's inputs only pass through to the nodes inside it, which are wired as readers themselves
3383+
if matches!(node.implementation, DocumentNodeImplementation::Network(_)) {
3384+
continue;
3385+
}
3386+
for input in &node.inputs {
3387+
if let Some(producer) = producing_node(network, &network_path, input, 0) {
3388+
readers.entry(producer).or_default().push(here.clone());
3389+
}
3390+
}
3391+
}
3392+
if turned_by.is_empty() {
3393+
return Vec::new();
3394+
}
3395+
3396+
// Each node's turned segments reach the nodes reading its output, then their readers, and so on
3397+
let mut following: HashMap<Location, HashSet<graphene_std::vector::SegmentId>> = HashMap::new();
3398+
for (origin, turned) in &turned_by {
3399+
let (origin_path, origin_id) = origin;
3400+
let Some(origin_node) = network.nested_network(origin_path).and_then(|nested| nested.nodes.get(origin_id)) else {
3401+
continue;
3402+
};
3403+
3404+
// Where the output comes from: the node itself, or the nodes a network node exports
3405+
let sources: Vec<Location> = match &origin_node.implementation {
3406+
DocumentNodeImplementation::Network(nested) => (0..nested.exports.len())
3407+
.filter_map(|output| producing_node(network, origin_path, &NodeInput::node(*origin_id, output), 0))
3408+
.collect(),
3409+
_ => vec![origin.clone()],
3410+
};
3411+
3412+
let mut reached = HashSet::new();
3413+
let mut pending: Vec<Location> = sources.iter().flat_map(|source| readers.get(source).into_iter().flatten().cloned()).collect();
3414+
while let Some(location) = pending.pop() {
3415+
if reached.insert(location.clone()) {
3416+
pending.extend(readers.get(&location).into_iter().flatten().cloned());
3417+
}
3418+
}
3419+
3420+
// An edit follows when the node holding it, or a node inside it, reads the turned data
3421+
for (path, node_id) in reached {
3422+
let enclosing = (0..path.len()).map(|depth| (path[..depth].to_vec(), path[depth]));
3423+
for holder in enclosing.chain([(path.clone(), node_id)]).filter(|holder| holder != origin) {
3424+
following.entry(holder).or_default().extend(turned.iter().copied());
3425+
}
3426+
}
3427+
}
3428+
3429+
let mut rewritten = Vec::new();
3430+
for (node_id, node, network_path) in network.recursive_nodes() {
3431+
let location = (network_path.clone(), *node_id);
3432+
let follows = following.get(&location);
3433+
3434+
for (index, input) in node.inputs.iter().enumerate() {
3435+
let NodeInput::Value { tagged_value, exposed } = input else { continue };
3436+
let TaggedValue::VectorModification(modification) = &**tagged_value else { continue };
3437+
let had_record = !modification.segments_turned_on_load().is_empty();
3438+
if !had_record && follows.is_none() {
3439+
continue;
3440+
}
3441+
3442+
let mut modification = modification.clone();
3443+
modification.take_segments_turned_on_load();
3444+
let changed = follows.is_some_and(|turned| modification.follow_segments_turned_elsewhere(turned));
3445+
3446+
if had_record || changed {
3447+
let input = NodeInput::value(TaggedValue::VectorModification(modification), *exposed);
3448+
rewritten.push((network_path.clone(), *node_id, index, input));
3449+
}
3450+
}
3451+
}
3452+
3453+
rewritten
3454+
}
3455+
3456+
// TODO: Eventually remove this document upgrade code
3457+
/// The node, not itself a network, producing the data an input reads, looking through the imports and exports of nested networks.
3458+
fn producing_node(network: &graph_craft::document::NodeNetwork, network_path: &[NodeId], input: &NodeInput, depth: usize) -> Option<(Vec<NodeId>, NodeId)> {
3459+
// Nesting is shallow in practice, so this only guards against a malformed document wiring a network back into itself
3460+
if depth > 64 {
3461+
return None;
3462+
}
3463+
3464+
match input {
3465+
NodeInput::Node { node_id, output_index } => {
3466+
let node = network.nested_network(network_path)?.nodes.get(node_id)?;
3467+
match &node.implementation {
3468+
DocumentNodeImplementation::Network(nested) => producing_node(network, &[network_path, &[*node_id]].concat(), nested.exports.get(*output_index)?, depth + 1),
3469+
_ => Some((network_path.to_vec(), *node_id)),
3470+
}
3471+
}
3472+
NodeInput::Import { import_index, .. } => {
3473+
let (parent, parent_path) = network_path.split_last()?;
3474+
let parent_node = network.nested_network(parent_path)?.nodes.get(parent)?;
3475+
producing_node(network, parent_path, parent_node.inputs.get(*import_index)?, depth + 1)
3476+
}
3477+
_ => None,
3478+
}
3479+
}
3480+
33503481
/// Ensure layers are positioned as stacks if they are upstream siblings of another layer
33513482
fn create_layer_stacks(document: &mut DocumentMessageHandler) {
33523483
document.network_interface.load_structure();
@@ -4081,4 +4212,115 @@ mod tests {
40814212
panic!("Duplicate entries in `NODE_REPLACEMENTS`: {duplicates:?}");
40824213
}
40834214
}
4215+
4216+
// Segment IDs repeat across shapes, so a segment turned on load is followed only by the stored edits downstream of the node that turned it
4217+
#[test]
4218+
fn turned_segments_are_followed_only_downstream() {
4219+
use graph_craft::document::NodeNetwork;
4220+
use graphene_std::vector::misc::HandleId;
4221+
use graphene_std::vector::{SegmentId, VectorModification, VectorModificationType};
4222+
4223+
let segment = SegmentId::generate();
4224+
let turned = VectorModification::default().with_segments_turned_on_load(HashSet::from([segment]));
4225+
let edited_on = |handle: HandleId| {
4226+
let mut modification = VectorModification::default();
4227+
modification.modify(&VectorModificationType::SetHandlePosition { handle, relative_position: DVec2::X });
4228+
modification
4229+
};
4230+
let value = |modification: VectorModification| NodeInput::value(TaggedValue::VectorModification(Box::new(modification)), false);
4231+
let node = |inputs: Vec<NodeInput>| DocumentNode { inputs, ..Default::default() };
4232+
4233+
// One turned Path node feeds another directly, and a second sits inside a nested network whose export feeds a third
4234+
let [turning, following, unrelated, group, following_group] = [1, 2, 3, 4, 5].map(NodeId);
4235+
let nested = NodeNetwork {
4236+
exports: vec![NodeInput::node(turning, 0)],
4237+
nodes: [(turning, node(vec![value(turned.clone())]))].into_iter().collect(),
4238+
..Default::default()
4239+
};
4240+
let network = NodeNetwork {
4241+
nodes: [
4242+
(turning, node(vec![value(turned)])),
4243+
(following, node(vec![NodeInput::node(turning, 0), value(edited_on(HandleId::primary(segment)))])),
4244+
(unrelated, node(vec![value(edited_on(HandleId::primary(segment)))])),
4245+
(
4246+
group,
4247+
DocumentNode {
4248+
implementation: DocumentNodeImplementation::Network(nested),
4249+
..Default::default()
4250+
},
4251+
),
4252+
(following_group, node(vec![NodeInput::node(group, 0), value(edited_on(HandleId::primary(segment)))])),
4253+
]
4254+
.into_iter()
4255+
.collect(),
4256+
..Default::default()
4257+
};
4258+
4259+
let rewritten = modifications_following_turned_segments(&network);
4260+
let rewritten_at = |location: (&[NodeId], NodeId)| rewritten.iter().find(|(path, node_id, ..)| (path.as_slice(), *node_id) == location);
4261+
4262+
assert!(rewritten_at((&[], turning)).is_some(), "the record of what turned is taken");
4263+
assert!(rewritten_at((&[group], turning)).is_some(), "the nested record of what turned is taken");
4264+
assert!(rewritten_at((&[], unrelated)).is_none(), "an edit not downstream stays as it was");
4265+
4266+
let expected = value(edited_on(HandleId::end(segment)));
4267+
for downstream in [following, following_group] {
4268+
let (.., index, input) = rewritten_at((&[], downstream)).expect("an edit downstream follows the turn");
4269+
assert_eq!(*index, 1);
4270+
assert_eq!(*input, expected);
4271+
}
4272+
}
4273+
4274+
// Inside a network fed by a turned node on one input and something else on another, only the edit reading the turned input follows
4275+
#[test]
4276+
fn turned_segments_follow_only_the_network_input_they_enter() {
4277+
use graph_craft::document::NodeNetwork;
4278+
use graphene_std::vector::misc::HandleId;
4279+
use graphene_std::vector::{SegmentId, VectorModification, VectorModificationType};
4280+
4281+
let segment = SegmentId::generate();
4282+
let turned = VectorModification::default().with_segments_turned_on_load(HashSet::from([segment]));
4283+
let mut edited = VectorModification::default();
4284+
edited.modify(&VectorModificationType::SetHandlePosition {
4285+
handle: HandleId::primary(segment),
4286+
relative_position: DVec2::X,
4287+
});
4288+
let value = |modification: VectorModification| NodeInput::value(TaggedValue::VectorModification(Box::new(modification)), false);
4289+
let import = |import_index: usize| NodeInput::Import {
4290+
import_type: concrete!(()),
4291+
import_index,
4292+
};
4293+
let node = |inputs: Vec<NodeInput>| DocumentNode { inputs, ..Default::default() };
4294+
4295+
let [turning, other, group, reads_turned, reads_other] = [1, 2, 3, 4, 5].map(NodeId);
4296+
let nested = NodeNetwork {
4297+
exports: vec![NodeInput::node(reads_turned, 0)],
4298+
nodes: [(reads_turned, node(vec![import(0), value(edited.clone())])), (reads_other, node(vec![import(1), value(edited)]))]
4299+
.into_iter()
4300+
.collect(),
4301+
..Default::default()
4302+
};
4303+
let network = NodeNetwork {
4304+
nodes: [
4305+
(turning, node(vec![value(turned)])),
4306+
(other, node(Vec::new())),
4307+
(
4308+
group,
4309+
DocumentNode {
4310+
inputs: vec![NodeInput::node(turning, 0), NodeInput::node(other, 0)],
4311+
implementation: DocumentNodeImplementation::Network(nested),
4312+
..Default::default()
4313+
},
4314+
),
4315+
]
4316+
.into_iter()
4317+
.collect(),
4318+
..Default::default()
4319+
};
4320+
4321+
let rewritten = modifications_following_turned_segments(&network);
4322+
let rewritten_at = |node_id: NodeId| rewritten.iter().any(|(path, rewritten_id, ..)| path.as_slice() == [group] && *rewritten_id == node_id);
4323+
assert!(rewritten_at(reads_turned));
4324+
assert!(!rewritten_at(reads_other));
4325+
}
40844326
}

‎editor/src/messages/tool/common_functionality/graph_modification_utils.rs‎

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,11 @@ pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIde
164164
}
165165

166166
/// Merge the `first_endpoint` with `second_endpoint`.
167-
pub fn merge_points(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, first_endpoint: PointId, second_endpont: PointId, responses: &mut VecDeque<Message>) {
167+
pub fn merge_points(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, first_endpoint: PointId, second_endpoint: PointId, responses: &mut VecDeque<Message>) {
168168
let transform = document.metadata().transform_to_document(layer);
169169
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return };
170170

171-
let segment = vector.segment_iter().find(|(_, _, start, end)| *end == second_endpont || *start == second_endpont);
171+
let segment = vector.segment_iter().find(|(_, _, start, end)| *end == second_endpoint || *start == second_endpoint);
172172
let Some((segment, _, mut segment_start_point, mut segment_end_point)) = segment else {
173173
log::error!("Could not get the segment for second_endpoint.");
174174
return;
@@ -190,20 +190,28 @@ pub fn merge_points(document: &DocumentMessageHandler, layer: LayerNodeIdentifie
190190
handles[1] = Some(anchor_to_handle);
191191
}
192192

193-
if segment_start_point == second_endpont {
194-
core::mem::swap(&mut segment_start_point, &mut segment_end_point);
195-
handles.reverse();
193+
// The segment keeps its direction, with whichever end sat on the merged-away point moving to the point it merges into
194+
if segment_start_point == second_endpoint {
195+
segment_start_point = first_endpoint;
196+
} else {
197+
segment_end_point = first_endpoint;
196198
}
197199

198-
let modification_type = VectorModificationType::RemovePoint { id: second_endpont };
200+
let modification_type = VectorModificationType::RemovePoint { id: second_endpoint };
199201
responses.add(GraphOperationMessage::Vector { layer, modification_type });
200202
let modification_type = VectorModificationType::RemoveSegment { id: segment };
201203
responses.add(GraphOperationMessage::Vector { layer, modification_type });
202204

203-
let points = [segment_start_point, first_endpoint];
205+
let points = [segment_start_point, segment_end_point];
204206
let id = SegmentId::generate();
205207
let modification_type = VectorModificationType::InsertSegment { id, points, handles };
206208
responses.add(GraphOperationMessage::Vector { layer, modification_type });
209+
210+
// The replacement takes over as the beginning of the path that the removed segment began
211+
if vector.path_beginnings().contains(&segment) {
212+
let modification_type = VectorModificationType::SetPathBreak { segment: id, breaks: true };
213+
responses.add(GraphOperationMessage::Vector { layer, modification_type });
214+
}
207215
}
208216

209217
/// Create a new bitmap layer.

0 commit comments

Comments
 (0)