Split 'To Graphic' and 'Wrap Graphic' into the 'As Graphic' type assertion and 'Into Group' reducer nodes (#4441)

* Split 'To Graphic' and 'Wrap Graphic' into the 'As Graphic' type assertion and 'Into Group' reducer nodes

* Make 'Into Group' reduce List<DVec2> not Item<DVec2>

* Reset the Merge definition for every legacy alias of its coercion nodes

* Improve test
This commit is contained in:
Keavon Chambers
2026-08-16 23:48:43 -07:00
parent 104b8b71e6
commit d63362718f
14 changed files with 162 additions and 53 deletions

View File

@@ -154,14 +154,14 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
// Primary (bottom) input type coercion
NodeTemplate {
inputs: vec![NodeInput::import(generic!(T), 0)],
implementation: NodeTemplateImplementation::ProtoNode(graphic::to_graphic::IDENTIFIER),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -3)),
implementation: NodeTemplateImplementation::ProtoNode(graphic::as_graphic::IDENTIFIER),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -2)),
..Default::default()
},
// Secondary (left) input type coercion
NodeTemplate {
inputs: vec![NodeInput::import(generic!(T), 1)],
implementation: NodeTemplateImplementation::ProtoNode(graphic::wrap_graphic::IDENTIFIER),
implementation: NodeTemplateImplementation::ProtoNode(graphic::into_group::IDENTIFIER),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, -1)),
..Default::default()
},
@@ -169,7 +169,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
NodeTemplate {
inputs: vec![NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
implementation: NodeTemplateImplementation::ProtoNode(graphic::path_of_subgraph::IDENTIFIER),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, 1)),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-21, 0)),
..Default::default()
},
// Stamp each item of the content with the parent layer's NodeId via the `editor:layer_path` attribute,
@@ -196,7 +196,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
call_argument: generic!(T),
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(4), 0)],
implementation: NodeTemplateImplementation::ProtoNode(graphic::extend::IDENTIFIER),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, -3)),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, -2)),
..Default::default()
},
]

View File

@@ -45,6 +45,14 @@ pub struct NodeReplacement<'a> {
aliases: &'a [&'a str],
}
/// Every name the Merge layer network's two type-coercion nodes have gone by, which is every alias of the node they both converged on.
fn into_group_aliases() -> impl Iterator<Item = &'static &'static str> {
NODE_REPLACEMENTS
.iter()
.filter(|replacement| replacement.node == graphene_std::graphic::into_group::IDENTIFIER)
.flat_map(|replacement| replacement.aliases)
}
const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
// ================================
// blending
@@ -194,22 +202,20 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
],
},
NodeReplacement {
node: graphene_std::graphic::to_graphic::IDENTIFIER,
node: graphene_std::graphic::into_group::IDENTIFIER,
aliases: &[
"graphene_core::ToGraphicGroupNode",
"graphene_core::graphic_element::ToGroupNode",
"graphene_core::graphic_types::ToGroupNode",
"graphene_core::graphic::ToGraphicNode",
],
},
NodeReplacement {
node: graphene_std::graphic::wrap_graphic::IDENTIFIER,
aliases: &[
// Converted from "To Element"
// Converted from "To Element", then "Wrap Graphic"
"graphene_core::ToGraphicElementNode",
"graphene_core::graphic_element::ToElementNode",
"graphene_core::graphic_types::ToElementNode",
"graphene_core::graphic::WrapGraphicNode",
"graphic_nodes::graphic::WrapGraphicNode",
// Converted from "To Graphic", whose grouping of non-graphical content this node now carries alone
"graphene_core::ToGraphicGroupNode",
"graphene_core::graphic_element::ToGroupNode",
"graphene_core::graphic_types::ToGroupNode",
"graphene_core::graphic::ToGraphicNode",
"graphic_nodes::graphic::ToGraphicNode",
],
},
// ================================
@@ -1071,6 +1077,12 @@ pub fn document_migration_reset_node_definition(document_serialized_content: &st
return true;
}
// Every Merge layer network is built from the two nodes that became "As Graphic" and "Into Group", so their definitions
// are reset to pick up the current plumbing instead of the alias migration meant for standalone copies of those nodes.
if into_group_aliases().any(|alias| document_serialized_content.contains(alias)) {
return true;
}
false
}
@@ -2958,6 +2970,20 @@ mod tests {
assert!(resolve_proto_node_type(graphene_std::platform_application_io::upload_texture::IDENTIFIER).is_some());
}
// Migrating a Merge network's coercion nodes by alias would leave a reducer in the primary slot, so every alias must reset instead
#[test]
fn every_into_group_alias_resets_the_merge_definition() {
let aliases = into_group_aliases().collect::<Vec<_>>();
assert!(!aliases.is_empty(), "the reset is driven by these aliases, so losing them all would disable it unnoticed");
for alias in aliases {
assert!(
document_migration_reset_node_definition(&format!(r#""implementation":{{"ProtoNode":"{alias}"}}"#)),
"a document referencing `{alias}` should reset its layer definitions"
);
}
}
#[test]
fn test_no_duplicate_node_replacements() {
let mut hashmap = HashMap::<ProtoNodeIdentifier, u32>::new();