Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .branding
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
https://github.com/Keavon/graphite-branded-assets/archive/a40077842627afb0c90858b351112a62b340881d.tar.gz
cd454d750d3f47fb2f41abceeacfc0ae59629ca00229dc56df53e3f4db0f6ee7
https://github.com/Keavon/graphite-branded-assets/archive/ea42bfc35beadaf716bb9efdb036512511dc2c28.tar.gz
1286b27b897d9bad692fc2897eceb25e3dd62476c7be391cf9521917f7fdf0a1
Original file line number Diff line number Diff line change
Expand Up @@ -356,9 +356,9 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
implementation: NodeTemplateImplementation::Network(NodeNetworkTemplate {
exports: vec![NodeInput::node(NodeId(16), 0)],
nodes: [
// 0: Separate Subpaths (split path into individual subpaths)
// 0: Separate Paths (split the vector into its individual paths)
NodeTemplate {
implementation: NodeTemplateImplementation::ProtoNode(vector::separate_subpaths::IDENTIFIER),
implementation: NodeTemplateImplementation::ProtoNode(vector::separate_paths::IDENTIFIER),
inputs: vec![NodeInput::import(generic!(T), 4)],
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(2, 0)),
..Default::default()
Expand Down
102 changes: 98 additions & 4 deletions editor/src/messages/portfolio/document_migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -789,8 +789,8 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
aliases: &["graphene_core::vector::CentroidNode"],
},
NodeReplacement {
node: graphene_std::vector::close_path::IDENTIFIER,
aliases: &["graphene_core::vector::ClosePathNode"],
node: graphene_std::vector::close_paths::IDENTIFIER,
aliases: &["graphene_core::vector::ClosePathNode", "core_types::vector::ClosePathNode"],
},
NodeReplacement {
node: graphene_std::vector::list_length::IDENTIFIER,
Expand Down Expand Up @@ -954,8 +954,8 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
],
},
NodeReplacement {
node: graphene_std::vector::separate_subpaths::IDENTIFIER,
aliases: &["graphene_core::vector::SeparateSubpathsNode"],
node: graphene_std::vector::separate_paths::IDENTIFIER,
aliases: &["graphene_core::vector::SeparateSubpathsNode", "core_types::vector::SeparateSubpathsNode"],
},
NodeReplacement {
node: graphene_std::vector::solidify_stroke::IDENTIFIER,
Expand Down Expand Up @@ -2833,6 +2833,29 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
reset_default_display_name(document, node_id, network_path, "Split Segments");
}

// Separate Subpaths and Close Path became Separate Paths and Close Paths, whose inputs take their current names and descriptions
let pluralized = [
(graphene_std::vector::separate_paths::IDENTIFIER, "Separate Subpaths"),
(graphene_std::vector::close_paths::IDENTIFIER, "Close Path"),
];
if let Some((_, old_default_name)) = pluralized.iter().find(|(identifier, _)| reference == DefinitionIdentifier::ProtoNode(identifier.clone())) {
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
let stored = document
.network_interface
.persistent_input_metadata(node_id, 0, network_path)
.map(|metadata| (metadata.input_name.clone(), metadata.input_description.clone()));
let current = node_template
.input_metadata
.first()
.map(|metadata| (metadata.persistent_metadata.input_name.clone(), metadata.persistent_metadata.input_description.clone()));
if stored != current {
Comment thread
Keavon marked this conversation as resolved.
Resolved
node_template.inputs = node.inputs.clone();
document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
}

reset_default_display_name(document, node_id, network_path, old_default_name);
}

if reference == DefinitionIdentifier::Network("Sample Points".into()) && inputs_count == 5 {
let mut node_template = resolve_network_node_type("Sample Polyline")?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
Expand Down Expand Up @@ -4497,4 +4520,75 @@ mod tests {
let outward_wires = opened.network_interface.outward_wires(&[]).and_then(|wires| wires.get(&OutputConnector::node(upstream, 0)).cloned());
assert_eq!(outward_wires.as_deref(), Some([InputConnector::node_at_index(cut_segments, 0)].as_slice()));
}

// Separate Subpaths and Close Path nodes open under their plural names and current input descriptions, still wired to the node feeding them
#[test]
fn renamed_path_nodes_open_under_their_new_names() {
use crate::messages::portfolio::document_storage_io::load_legacy_document;
use crate::test_utils::capture_logged_errors;
use graph_craft::application_io::resource::HashMapResourceStorage;

let rectangle = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::rectangle::IDENTIFIER)).expect("the Rectangle definition should exist");
let renames = [
(
graphene_std::vector::separate_paths::IDENTIFIER,
"SeparatePathsNode",
"Separate Paths",
["core_types::vector::SeparateSubpathsNode", "graphene_core::vector::SeparateSubpathsNode"],
"Separate Subpaths",
"Content",
),
(
graphene_std::vector::close_paths::IDENTIFIER,
"ClosePathsNode",
"Close Paths",
["core_types::vector::ClosePathNode", "graphene_core::vector::ClosePathNode"],
"Close Path",
"Source",
),
];

for (identifier, identifier_name, title, [current_identifier, oldest_identifier], old_title, input_name) in renames {
let definition = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(identifier.clone())).expect("the renamed node's definition should exist");

// The node under its current identifier and its oldest one, both storing their old title
let legacy_node = |old_identifier: &'static str| NodeTemplate {
implementation: NodeTemplateImplementation::ProtoNode(ProtoNodeIdentifier::new(old_identifier)),
inputs: definition.default_node_template().inputs,
display_name: old_title.to_string(),
input_metadata: vec![(input_name, "").into()],
..Default::default()
};
let (upstream, current, oldest) = (NodeId(1), NodeId(2), NodeId(3));
let mut document = DocumentMessageHandler::default();
document.network_interface.insert_node(upstream, rectangle.default_node_template(), &[]);
document.network_interface.insert_node(current, legacy_node(current_identifier), &[]);
document.network_interface.insert_node(oldest, legacy_node(oldest_identifier), &[]);
document.network_interface.set_input(&InputConnector::node_at_index(current, 0), NodeInput::node(upstream, 0), &[]);

let serialized = document.serialize_document();
assert!(!serialized.contains(identifier_name), "the saved document should name only the old {title} identifiers");

let (opened, errors) = capture_logged_errors(|| load_legacy_document(serialized, &HashMapResourceStorage::new(), false));
let opened = opened.expect("the document should open");
assert!(errors.is_empty(), "opening the document logged errors: {errors:?}");

for node_id in [current, oldest] {
let interface = &opened.network_interface;
let node = interface.document_node(&node_id, &[]).expect("the node should still exist");
assert_eq!(node.implementation, DocumentNodeImplementation::ProtoNode(identifier.clone()), "{title} node {node_id}");
assert_eq!(interface.display_name(&node_id, &[]), title, "{title} node {node_id}");

let expected = definition
.node_template
.input_metadata
.first()
.map(|metadata| (metadata.persistent_metadata.input_name.clone(), metadata.persistent_metadata.input_description.clone()));
assert_eq!(Some(interface.displayed_input_name_and_description(&node_id, 0, &[])), expected, "{title} node {node_id}");
}

let current_content = opened.network_interface.document_node(&current, &[]).map(|node| node.inputs[0].clone());
assert!(matches!(current_content, Some(NodeInput::Node { node_id, output_index: 0, .. }) if node_id == upstream), "{title}");
}
}
}
10 changes: 6 additions & 4 deletions frontend/src/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ import NodeChannelsToImage from "/../branding/assets/16x16-nodes/node-channels-t
import NodeCircle from "/../branding/assets/16x16-nodes/node-circle.svg";
import NodeClamp from "/../branding/assets/16x16-nodes/node-clamp.svg";
import NodeClippingMask from "/../branding/assets/16x16-nodes/node-clipping-mask.svg";
import NodeClosePath from "/../branding/assets/16x16-nodes/node-close-path.svg";
import NodeClosePaths from "/../branding/assets/16x16-nodes/node-close-paths.svg";
import NodeColorBalance from "/../branding/assets/16x16-nodes/node-color-balance.svg";
import NodeColorLookup from "/../branding/assets/16x16-nodes/node-color-lookup.svg";
import NodeColorOverlay from "/../branding/assets/16x16-nodes/node-color-overlay.svg";
Expand Down Expand Up @@ -320,6 +320,7 @@ import NodeRepeatRadial from "/../branding/assets/16x16-nodes/node-repeat-radial
import NodeRepeat from "/../branding/assets/16x16-nodes/node-repeat.svg";
import NodeReplaceTransform from "/../branding/assets/16x16-nodes/node-replace-transform.svg";
import NodeResetTransform from "/../branding/assets/16x16-nodes/node-reset-transform.svg";
import NodeReversePaths from "/../branding/assets/16x16-nodes/node-reverse-paths.svg";
import NodeReverse from "/../branding/assets/16x16-nodes/node-reverse.svg";
import NodeRgbaToColor from "/../branding/assets/16x16-nodes/node-rgba-to-color.svg";
import NodeRoot from "/../branding/assets/16x16-nodes/node-root.svg";
Expand All @@ -329,7 +330,7 @@ import NodeSampleImage from "/../branding/assets/16x16-nodes/node-sample-image.s
import NodeSamplePolyline from "/../branding/assets/16x16-nodes/node-sample-polyline.svg";
import NodeScatterPoints from "/../branding/assets/16x16-nodes/node-scatter-points.svg";
import NodeSelectiveColor from "/../branding/assets/16x16-nodes/node-selective-color.svg";
import NodeSeparateSubpaths from "/../branding/assets/16x16-nodes/node-separate-subpaths.svg";
import NodeSeparatePaths from "/../branding/assets/16x16-nodes/node-separate-paths.svg";
import NodeSerialize from "/../branding/assets/16x16-nodes/node-serialize.svg";
import NodeShift from "/../branding/assets/16x16-nodes/node-shift.svg";
import NodeShuffle from "/../branding/assets/16x16-nodes/node-shuffle.svg";
Expand Down Expand Up @@ -428,7 +429,7 @@ export const ICON_16X16_NODES = {
NodeCircle: { svg: NodeCircle, size: 16 },
NodeClamp: { svg: NodeClamp, size: 16 },
NodeClippingMask: { svg: NodeClippingMask, size: 16 },
NodeClosePath: { svg: NodeClosePath, size: 16 },
NodeClosePaths: { svg: NodeClosePaths, size: 16 },
NodeColorBalance: { svg: NodeColorBalance, size: 16 },
NodeColorLookup: { svg: NodeColorLookup, size: 16 },
NodeColorOverlay: { svg: NodeColorOverlay, size: 16 },
Expand Down Expand Up @@ -602,6 +603,7 @@ export const ICON_16X16_NODES = {
NodeReplaceTransform: { svg: NodeReplaceTransform, size: 16 },
NodeResetTransform: { svg: NodeResetTransform, size: 16 },
NodeReverse: { svg: NodeReverse, size: 16 },
NodeReversePaths: { svg: NodeReversePaths, size: 16 },
NodeRgbaToColor: { svg: NodeRgbaToColor, size: 16 },
NodeRoot: { svg: NodeRoot, size: 16 },
NodeRound: { svg: NodeRound, size: 16 },
Expand All @@ -610,7 +612,7 @@ export const ICON_16X16_NODES = {
NodeSamplePolyline: { svg: NodeSamplePolyline, size: 16 },
NodeScatterPoints: { svg: NodeScatterPoints, size: 16 },
NodeSelectiveColor: { svg: NodeSelectiveColor, size: 16 },
NodeSeparateSubpaths: { svg: NodeSeparateSubpaths, size: 16 },
NodeSeparatePaths: { svg: NodeSeparatePaths, size: 16 },
NodeSerialize: { svg: NodeSerialize, size: 16 },
NodeShift: { svg: NodeShift, size: 16 },
NodeShuffle: { svg: NodeShuffle, size: 16 },
Expand Down
15 changes: 1 addition & 14 deletions node-graph/libraries/vector-types/src/vector/algorithms/weld.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::vector::flatten::WorkingVector;
use crate::vector::misc::UnweldMethod;
use crate::vector::path::{CurveType, Path, continues_previous_path, curve_types_of, push_paths};
use crate::vector::path::{CurveType, Path, curve_types_of, path_flows, push_paths};
use crate::vector::vector_types::largest_point_id;
use crate::vector::{PointId, SegmentId, Vector};
use core_types::list::{ATTR_ID, List};
Expand Down Expand Up @@ -536,19 +536,6 @@ fn separate_segments(vector: &mut Vector, paths: &mut List<Path>) {
push_paths(paths, segments);
}

/// The path flows, each a range of stored paths: a path, with those after it that only carry it on in another curve type.
fn path_flows(paths: &List<Path>) -> Vec<Range<usize>> {
let mut flows: Vec<Range<usize>> = Vec::new();
for index in 0..paths.len() {
match flows.last_mut() {
Some(flow) if continues_previous_path(paths, index) => flow.end = index + 1,
_ => flows.push(index..index + 1),
}
}

flows
}

/// The pool points a path flow begins and ends at.
fn path_flow_ends(paths: &List<Path>, flow: &Range<usize>) -> Option<(usize, usize)> {
let start = *paths.element(flow.start)?.0.iter_element_values().as_slice().first()?;
Expand Down
51 changes: 51 additions & 0 deletions node-graph/libraries/vector-types/src/vector/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,35 @@ pub fn continues_previous_path(paths: &List<Path>, index: usize) -> bool {
previous_last > 0 && path.0.element(0) == previous.0.element(previous_last) && curve_type_at(paths, index) != curve_type_at(paths, previous_index)
}

/// The path flows, each a range of stored paths: a path, with those after it that only carry it on in another curve type.
pub(crate) fn path_flows(paths: &List<Path>) -> Vec<std::ops::Range<usize>> {
let mut flows: Vec<std::ops::Range<usize>> = Vec::new();
for index in 0..paths.len() {
match flows.last_mut() {
Some(flow) if continues_previous_path(paths, index) => flow.end = index + 1,
_ => flows.push(index..index + 1),
}
}

flows
}

/// Reverses every path flow so every path runs the other way, swapping the order of a flow's stored paths so each still sets out where the one before it ends.
pub fn reverse_path_flows(paths: &mut List<Path>) {
let flows = path_flows(paths);
let curve_types: Vec<CurveType> = curve_types_of(paths).collect();

for (path, &curve_type) in paths.iter_element_values_mut().zip(&curve_types) {
let segment_count = path.segment_count(curve_type);
path.reverse_spans(curve_type, 0..segment_count);
}

if flows.iter().any(|flow| flow.len() > 1) {
let reordered: List<Path> = flows.into_iter().flat_map(|flow| flow.rev()).filter_map(|index| paths.clone_item(index)).collect();
*paths = reordered;
}
}

/// The IDs a weave gives the anchors and spans it adds, each bezpath's continuing on from the latest taken before it.
#[derive(Default)]
struct WovenNames {
Expand Down Expand Up @@ -575,6 +604,28 @@ mod tests {
assert_eq!(ragged.0.iter_element_values().copied().collect::<Vec<_>>(), vec![2, 13, 12, 0, 11, 10, 4, 14]);
}

// Every path turns around with its span IDs, and a flow drawn as a line then a curve swaps its two stored paths so it stays one flow
#[test]
fn reversing_path_flows() {
let mut segment_id = SegmentId::ZERO;
let ids: Vec<SegmentId> = (0..5).map(|_| segment_id.next_id()).collect();
let mut paths = List::new();
push_path(&mut paths, CurveType::Linear, [0, 1], [ids[0]]);
push_path(&mut paths, CurveType::Cubic, [1, 2, 3, 4], [ids[1]]);
push_path(&mut paths, CurveType::Linear, [5, 6, 7, 5], [ids[2], ids[3], ids[4]]);

reverse_path_flows(&mut paths);

let references: Vec<Vec<u32>> = paths.iter_element_values().map(|path| path.references().collect()).collect();
assert_eq!(references, [vec![4, 3, 2, 1], vec![1, 0], vec![5, 7, 6, 5]]);
assert_eq!(curve_types_of(&paths).collect::<Vec<_>>(), [CurveType::Cubic, CurveType::Linear, CurveType::Linear]);
assert!(continues_previous_path(&paths, 1));

let id_at = |path: usize, reference: usize| paths.element(path).and_then(|path| path.0.attribute::<SegmentId>(ATTR_ID, reference).copied());
assert_eq!([id_at(0, 0), id_at(1, 0)], [Some(ids[1]), Some(ids[0])]);
assert_eq!([id_at(2, 0), id_at(2, 1), id_at(2, 2)], [Some(ids[4]), Some(ids[3]), Some(ids[2])]);
}

// A reference naming a point the pool does not have drops its segment instead of panicking
#[test]
fn dangling_reference_skips_its_segment() {
Expand Down
Loading
Loading