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/ca7dd793caba8999ec33fde99bb721a69c3d221f.tar.gz
77e8a6b68d67a8d5fcfd6268a6b7938ac9322d877378c6563e933a57df6da821
https://github.com/Keavon/graphite-branded-assets/archive/a40077842627afb0c90858b351112a62b340881d.tar.gz
cd454d750d3f47fb2f41abceeacfc0ae59629ca00229dc56df53e3f4db0f6ee7
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use graphene_std::text_nodes::{StringCapitalization, TextDenomination};
use graphene_std::transfer_curve::TransferCurve;
use graphene_std::transform::{ReferencePoint, ScaleType};
use graphene_std::vector::misc::{
ArcType, BooleanOperation, BoxCorners, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, SpiralType,
ArcType, BooleanOperation, BoxCorners, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, PointSpacingType, RowsOrColumns, SpiralType, UnweldMethod, WeldMethod,
};
use graphene_std::vector::style::{
DashPattern, FillChoice, GradientForm, GradientHueDirection, GradientInterpolation, GradientRamp, GradientSettings, GradientSpace, GradientSpread, StrokeAlign, StrokeCap, StrokeJoin,
Expand Down Expand Up @@ -209,7 +209,8 @@ fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'st
List<StrokeJoin>,
List<StrokeAlign>,
List<StrokeCap>,
List<MergeByDistanceAlgorithm>,
List<WeldMethod>,
List<UnweldMethod>,
List<ExtrudeJoiningAlgorithm>,
List<PointSpacingType>,
List<StringCapitalization>,
Expand Down Expand Up @@ -263,7 +264,8 @@ fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'st
Item<StrokeJoin>,
Item<StrokeAlign>,
Item<StrokeCap>,
Item<MergeByDistanceAlgorithm>,
Item<WeldMethod>,
Item<UnweldMethod>,
Item<ExtrudeJoiningAlgorithm>,
Item<PointSpacingType>,
Item<StringCapitalization>,
Expand Down Expand Up @@ -981,7 +983,8 @@ impl_table_item_layout_for_choice_enum!(
StrokeJoin,
StrokeAlign,
StrokeCap,
MergeByDistanceAlgorithm,
WeldMethod,
UnweldMethod,
ExtrudeJoiningAlgorithm,
PointSpacingType,
StringCapitalization,
Expand Down Expand Up @@ -1153,7 +1156,8 @@ macro_rules! known_item_types {
StrokeJoin,
StrokeAlign,
StrokeCap,
MergeByDistanceAlgorithm,
WeldMethod,
UnweldMethod,
ExtrudeJoiningAlgorithm,
PointSpacingType,
StringCapitalization,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use graphene_std::transfer_curve::TransferCurve;
use graphene_std::transform::{Footprint, ReferencePoint, ScaleType, Transform};
use graphene_std::vector::misc::BooleanOperation;
use graphene_std::vector::misc::{
ArcType, BoxCorners, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, MergeByDistanceAlgorithm, PointSpacingType, RowsOrColumns, SpiralType,
ArcType, BoxCorners, CentroidType, ExtrudeJoiningAlgorithm, GridType, InterpolationDistribution, PointSpacingType, RowsOrColumns, SpiralType, UnweldMethod, WeldMethod,
};
use graphene_std::vector::style::{
FillChoice, Gradient, GradientForm, GradientHueDirection, GradientInterpolation, GradientRamp, GradientSettings, GradientSpace, GradientSpread, GradientStops, StrokeAlign, StrokeCap, StrokeJoin,
Expand Down Expand Up @@ -430,7 +430,8 @@ pub(crate) fn inspector_row_from_type(node_id: NodeId, index: usize, ty: &Type,
Some(x) if id_is::<ArcType>(x) => enum_choice::<ArcType>().for_socket(default_info).inspector_row(),
Some(x) if id_is::<RowsOrColumns>(x) => enum_choice::<RowsOrColumns>().for_socket(default_info).inspector_row(),
Some(x) if id_is::<TextAlign>(x) => enum_choice::<TextAlign>().for_socket(default_info).inspector_row(),
Some(x) if id_is::<MergeByDistanceAlgorithm>(x) => enum_choice::<MergeByDistanceAlgorithm>().for_socket(default_info).inspector_row(),
Some(x) if id_is::<WeldMethod>(x) => enum_choice::<WeldMethod>().for_socket(default_info).inspector_row(),
Some(x) if id_is::<UnweldMethod>(x) => enum_choice::<UnweldMethod>().for_socket(default_info).inspector_row(),
Some(x) if id_is::<ExtrudeJoiningAlgorithm>(x) => enum_choice::<ExtrudeJoiningAlgorithm>().for_socket(default_info).inspector_row(),
Some(x) if id_is::<PointSpacingType>(x) => enum_choice::<PointSpacingType>().for_socket(default_info).inspector_row(),
Some(x) if id_is::<BooleanOperation>(x) => enum_choice::<BooleanOperation>().for_socket(default_info).inspector_row(),
Expand Down
197 changes: 190 additions & 7 deletions editor/src/messages/portfolio/document_migration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -801,11 +801,12 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
aliases: &["graphene_core::vector::vector_nodes::SplitPathNode", "graphene_core::vector::SplitPathNode"],
},
NodeReplacement {
node: graphene_std::vector::cut_segments::IDENTIFIER,
node: graphene_std::vector::unweld::IDENTIFIER,
aliases: &[
"graphene_core::vector::vector_nodes::SplitSegmentsNode",
"graphene_core::vector::SplitSegmentsNode",
"graphene_core::vector::CutSegmentsNode",
"core_types::vector::CutSegmentsNode",
],
},
NodeReplacement {
Expand Down Expand Up @@ -901,8 +902,8 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
aliases: &["graphene_core::vector::JitterPointsNode"],
},
NodeReplacement {
node: graphene_std::vector::merge_by_distance::IDENTIFIER,
aliases: &["graphene_core::vector::MergeByDistanceNode"],
node: graphene_std::vector::weld::IDENTIFIER,
aliases: &["graphene_core::vector::MergeByDistanceNode", "core_types::vector::MergeByDistanceNode"],
},
NodeReplacement {
node: graphene_std::graphic::mirror::IDENTIFIER,
Expand Down Expand Up @@ -2774,7 +2775,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
.set_input(&InputConnector::node_at_index(*node_id, 2), NodeInput::value(TaggedValue::Bool(true), false), network_path);
}

if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::merge_by_distance::IDENTIFIER) && inputs_count == 2 {
if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::weld::IDENTIFIER) && inputs_count == 2 {
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);

Expand All @@ -2784,13 +2785,28 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[1].clone(), network_path);
document.network_interface.set_input(
&InputConnector::node_at_index(*node_id, 2),
NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Topological), false),
NodeInput::value(TaggedValue::WeldMethod(graphene_std::vector::misc::WeldMethod::Connected), false),
network_path,
);
reset_default_display_name(document, node_id, network_path, "Merge by Distance");
}

// A Merge by Distance node saved with its "Algorithm" input takes Weld's input names and descriptions, keeping its inputs
if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::weld::IDENTIFIER)
&& inputs_count == 3
&& document
.network_interface
.persistent_input_metadata(node_id, 2, network_path)
.is_some_and(|metadata| metadata.input_name == "Algorithm")
{
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
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, "Merge by Distance");
}

if reference == DefinitionIdentifier::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SpatialMergeByDistanceNode")) {
let mut node_template = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::vector::merge_by_distance::IDENTIFIER))?.default_node_template();
let mut node_template = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::vector::weld::IDENTIFIER))?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);

let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
Expand All @@ -2799,9 +2815,22 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
document.network_interface.set_input(&InputConnector::node_at_index(*node_id, 1), old_inputs[1].clone(), network_path);
document.network_interface.set_input(
&InputConnector::node_at_index(*node_id, 2),
NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Spatial), false),
NodeInput::value(TaggedValue::WeldMethod(graphene_std::vector::misc::WeldMethod::All), false),
network_path,
);
reset_default_display_name(document, node_id, network_path, "Spatial Merge by Distance");
}

// Cut Segments became Unweld, keeping its content and taking the All method that splits every anchor as it did
if reference == DefinitionIdentifier::ProtoNode(graphene_std::vector::unweld::IDENTIFIER) && inputs_count == 1 {
let mut node_template = resolve_document_node_type(&reference)?.default_node_template();
node_template.inputs = vec![
node.inputs[0].clone(),
NodeInput::value(TaggedValue::UnweldMethod(graphene_std::vector::misc::UnweldMethod::All), false),
];
document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
reset_default_display_name(document, node_id, network_path, "Cut Segments");
reset_default_display_name(document, node_id, network_path, "Split Segments");
}

if reference == DefinitionIdentifier::Network("Sample Points".into()) && inputs_count == 5 {
Expand Down Expand Up @@ -4314,4 +4343,158 @@ mod tests {
assert!(rewritten_at(reads_turned));
assert!(!rewritten_at(reads_other));
}

// Values saved for Merge by Distance load as the methods of the Weld node it became
#[test]
fn merge_by_distance_values_load_as_weld_methods() {
use graphene_std::vector::misc::WeldMethod;

let load = |json: &str| serde_json::from_str::<TaggedValue>(json).ok();
assert_eq!(load(r#"{"MergeByDistanceAlgorithm":"Spatial"}"#), Some(TaggedValue::WeldMethod(WeldMethod::All)));
assert_eq!(load(r#"{"MergeByDistanceAlgorithm":"Topological"}"#), Some(TaggedValue::WeldMethod(WeldMethod::Connected)));
assert_eq!(load(r#"{"WeldMethod":"FlowEnds"}"#), Some(TaggedValue::WeldMethod(WeldMethod::FlowEnds)));
}

// Every kind of Merge by Distance node older editors saved opens as a Weld node, with the method it had and Weld's own title and inputs
#[test]
fn merge_by_distance_nodes_open_as_weld_nodes() {
use crate::messages::portfolio::document_storage_io::load_legacy_document;
use crate::test_utils::capture_logged_errors;
use graph_craft::application_io::resource::HashMapResourceStorage;
use graphene_std::vector::misc::WeldMethod;

let weld = graphene_std::vector::weld::IDENTIFIER;
let definition = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(weld.clone())).expect("the Weld definition should exist");
let content = definition.default_node_template().inputs[0].clone();
let distance = NodeInput::value(TaggedValue::Number(2.), false);

// Older nodes stored their title and input names, with any "Algorithm" choice saved here as a placeholder method
let legacy_node = |identifier: &'static str, display_name: &str, placeholder_method: Option<WeldMethod>| {
let mut inputs = vec![content.clone(), distance.clone()];
inputs.extend(placeholder_method.map(|method| NodeInput::value(TaggedValue::WeldMethod(method), false)));
let input_names: &[&str] = if placeholder_method.is_some() {
&["Content", "Distance", "Algorithm"]
} else {
&["Source", "Distance"]
};

NodeTemplate {
implementation: NodeTemplateImplementation::ProtoNode(ProtoNodeIdentifier::new(identifier)),
inputs,
display_name: display_name.to_string(),
input_metadata: input_names.iter().map(|name| (*name, "").into()).collect(),
..Default::default()
}
};
let legacy_nodes = [
legacy_node("core_types::vector::MergeByDistanceNode", "Merge by Distance", Some(WeldMethod::All)),
legacy_node("graphene_core::vector::MergeByDistanceNode", "Merge by Distance", Some(WeldMethod::Connected)),
legacy_node("graphene_core::vector::MergeByDistanceNode", "Merge by Distance", None),
legacy_node("graphene_core::vector::SpatialMergeByDistanceNode", "Spatial Merge by Distance", None),
];
let mut document = DocumentMessageHandler::default();
for (index, node_template) in legacy_nodes.into_iter().enumerate() {
document.network_interface.insert_node(NodeId(index as u64 + 1), node_template, &[]);
}

// The placeholder methods are saved under the old value names, which load as the methods they became
let mut serialized = document.serialize_document();
assert!(!serialized.contains("WeldNode"), "the saved document should name only the old node identifiers");
for (placeholder, saved) in [("All", "Topological"), ("Connected", "Spatial")] {
let placeholder = format!(r#"{{"WeldMethod":"{placeholder}"}}"#);
assert_eq!(serialized.matches(&placeholder).count(), 1);
serialized = serialized.replace(&placeholder, &format!(r#"{{"MergeByDistanceAlgorithm":"{saved}"}}"#));
}

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:?}");

let definition_inputs: Vec<(String, String)> = definition
.node_template
.input_metadata
.iter()
.map(|metadata| (metadata.persistent_metadata.input_name.clone(), metadata.persistent_metadata.input_description.clone()))
.collect();
assert_eq!(definition_inputs.iter().map(|(name, _)| name.as_str()).collect::<Vec<_>>(), ["Content", "Distance", "Method"]);

for (node_id, method) in [(1, WeldMethod::Connected), (2, WeldMethod::All), (3, WeldMethod::Connected), (4, WeldMethod::All)].map(|(id, method)| (NodeId(id), method)) {
let interface = &opened.network_interface;
let node = interface.document_node(&node_id, &[]).expect("the node should still exist");
assert_eq!(node.implementation, DocumentNodeImplementation::ProtoNode(weld.clone()), "node {node_id}");
assert_eq!(node.inputs[1].as_value(), Some(&TaggedValue::Number(2.)), "node {node_id}");
assert_eq!(node.inputs[2].as_value(), Some(&TaggedValue::WeldMethod(method)), "node {node_id}");
assert_eq!(interface.display_name(&node_id, &[]), "Weld", "node {node_id}");

let inputs: Vec<(String, String)> = (0..node.inputs.len()).map(|index| interface.displayed_input_name_and_description(&node_id, index, &[])).collect();
assert_eq!(inputs, definition_inputs, "node {node_id}");
}
}

// Cut Segments nodes open as Unweld nodes splitting every anchor, still wired to the node feeding them
#[test]
fn cut_segments_nodes_open_as_unweld_nodes() {
use crate::messages::portfolio::document_storage_io::load_legacy_document;
use crate::test_utils::capture_logged_errors;
use graph_craft::application_io::resource::HashMapResourceStorage;
use graphene_std::vector::misc::UnweldMethod;

let unweld = graphene_std::vector::unweld::IDENTIFIER;
let definition = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(unweld.clone())).expect("the Unweld definition should exist");
let rectangle = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::vector::generator_nodes::rectangle::IDENTIFIER)).expect("the Rectangle definition should exist");

// The current Cut Segments node and its oldest form, which stored their titles and single content input
let content = definition.default_node_template().inputs[0].clone();
let legacy_node = |identifier: &'static str, display_name: &str| NodeTemplate {
implementation: NodeTemplateImplementation::ProtoNode(ProtoNodeIdentifier::new(identifier)),
inputs: vec![content.clone()],
display_name: display_name.to_string(),
input_metadata: vec![("Content", "").into()],
..Default::default()
};
let (upstream, cut_segments, split_segments) = (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(cut_segments, legacy_node("core_types::vector::CutSegmentsNode", "Cut Segments"), &[]);
document
.network_interface
.insert_node(split_segments, legacy_node("graphene_core::vector::vector_nodes::SplitSegmentsNode", "Split Segments"), &[]);
document.network_interface.set_input(&InputConnector::node_at_index(cut_segments, 0), NodeInput::node(upstream, 0), &[]);

let serialized = document.serialize_document();
assert!(!serialized.contains("UnweldNode"), "the saved document should name only the old node identifiers");

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

let definition_inputs: Vec<(String, String)> = definition
.node_template
.input_metadata
.iter()
.map(|metadata| (metadata.persistent_metadata.input_name.clone(), metadata.persistent_metadata.input_description.clone()))
.collect();
for node_id in [cut_segments, split_segments] {
let interface = &opened.network_interface;
let node = interface.document_node(&node_id, &[]).expect("the node should still exist");
assert_eq!(node.implementation, DocumentNodeImplementation::ProtoNode(unweld.clone()), "node {node_id}");
assert_eq!(
node.inputs.get(1).and_then(|input| input.as_value()),
Some(&TaggedValue::UnweldMethod(UnweldMethod::All)),
"node {node_id}"
);
assert_eq!(interface.display_name(&node_id, &[]), "Unweld", "node {node_id}");

let inputs: Vec<(String, String)> = (0..node.inputs.len()).map(|index| interface.displayed_input_name_and_description(&node_id, index, &[])).collect();
assert_eq!(inputs, definition_inputs, "node {node_id}");
}

// The wire from the upstream node survives in both directions
let cut_segments_content = opened.network_interface.document_node(&cut_segments, &[]).map(|node| node.inputs[0].clone());
assert!(matches!(cut_segments_content, Some(NodeInput::Node { node_id, output_index: 0, .. }) if node_id == upstream));
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()));
}
}
Loading
Loading