Reshape the node catalog: rename the list access nodes, split Text to Vector, reinstate Upload Texture as a proto node, and delete the vestigial debug nodes

This commit is contained in:
Keavon Chambers
2026-07-20 16:37:43 -07:00
committed by Dennis Kobert
parent 1bac1d694f
commit ead622b969
11 changed files with 178 additions and 243 deletions

View File

@@ -2758,7 +2758,7 @@ impl DocumentMessageHandler {
}
let solidify_stroke_definition = document_node_definitions::resolve_proto_node_type(graphene_std::vector::solidify_stroke::IDENTIFIER).expect("Solidify Stroke node should exist");
let index_elements_definition = document_node_definitions::resolve_proto_node_type(graphene_std::graphic::index_elements::IDENTIFIER).expect("Index Elements node should exist");
let item_at_index_definition = document_node_definitions::resolve_proto_node_type(graphene_std::graphic::item_at_index::IDENTIFIER).expect("Item at Index node should exist");
let mut resulting_layers: Vec<NodeId> = Vec::new();
@@ -2791,7 +2791,7 @@ impl DocumentMessageHandler {
if has_fill && has_stroke {
let (existing_index, new_index) = (0_f64, 1_f64);
let existing_index_template = index_elements_definition.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(existing_index), false))]);
let existing_index_template = item_at_index_definition.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(existing_index), false))]);
let existing_index_id = NodeId::new();
self.network_interface.insert_node(existing_index_id, existing_index_template, &[]);
self.network_interface.move_node_to_chain_start(&existing_index_id, layer, &[], false);
@@ -2813,7 +2813,7 @@ impl DocumentMessageHandler {
self.network_interface.set_display_name(&new_layer_id, original_name, &[]);
}
let new_index_template = index_elements_definition.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(new_index), false))]);
let new_index_template = item_at_index_definition.node_template_input_override([None, Some(NodeInput::value(TaggedValue::F64(new_index), false))]);
let new_index_id = NodeId::new();
self.network_interface.insert_node(new_index_id, new_index_template, &[]);
self.network_interface.move_node_to_chain_start(&new_index_id, new_layer, &[], false);

View File

@@ -488,7 +488,7 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
},
// 1: Count Elements (number of subpaths)
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(vector::count_elements::IDENTIFIER),
implementation: DocumentNodeImplementation::ProtoNode(vector::list_length::IDENTIFIER),
inputs: vec![NodeInput::node(NodeId(0), 0)],
..Default::default()
},
@@ -1289,78 +1289,6 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
),
properties: None,
},
#[cfg(feature = "gpu")]
DocumentNodeDefinition {
identifier: "Upload Texture",
category: "Debug",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::import(concrete!(List<Raster<CPU>>), 0), NodeInput::scope(platform_application_io::wgpu_executor::IDENTIFIER)],
call_argument: generic!(T),
implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::texture_conversion::upload_texture::IDENTIFIER),
..Default::default()
},
DocumentNode {
call_argument: generic!(T),
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::memoize::IDENTIFIER),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
inputs: vec![NodeInput::type_default(descriptor!(List<Raster<CPU>>), true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
output_names: vec!["Texture".to_string()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(-7, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
},
..Default::default()
}),
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
properties: None,
},
DocumentNodeDefinition {
identifier: "Extract",
category: "",
@@ -1407,13 +1335,13 @@ fn document_node_definitions() -> HashMap<DefinitionIdentifier, DocumentNodeDefi
// Node 1: extract_element at index 0, extracts the whole match as a bare String (drops the item's start/end/name attributes since the unwrapped String can't carry them)
DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::value(TaggedValue::F64(0.), false)],
implementation: DocumentNodeImplementation::ProtoNode(graphic::extract_element::IDENTIFIER),
implementation: DocumentNodeImplementation::ProtoNode(graphic::item_at_index::IDENTIFIER),
..Default::default()
},
// Node 2: omit_element at index 0, returns the capture group items as a List<String>, preserving each item's start/end/name attributes
DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::value(TaggedValue::F64(0.), false)],
implementation: DocumentNodeImplementation::ProtoNode(graphic::omit_element::IDENTIFIER),
implementation: DocumentNodeImplementation::ProtoNode(graphic::remove_at_index::IDENTIFIER),
..Default::default()
},
]

View File

@@ -93,10 +93,6 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
node: graphene_std::animation::animation_time::IDENTIFIER,
aliases: &["graphene_core::animation::AnimationTimeNode"],
},
NodeReplacement {
node: graphene_std::debug::clone::IDENTIFIER,
aliases: &["graphene_core::ops::CloneNode"],
},
NodeReplacement {
node: graphene_std::extract_xy::extract_xy::IDENTIFIER,
aliases: &["graphene_core::ops::ExtractXyNode"],
@@ -112,6 +108,14 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
"graphene_core::transform_nodes::FreezeRealTimeNode",
"graphene_core::vector::SubpathSegmentLengthsNode",
"core_types::vector::SubpathSegmentLengthsNode",
// The deleted debug Option trio degrades to a passthrough of its single input (audit resolution 8)
"graphene_core::ops::SizeOfNode",
"graphene_core::debug::SizeOfNode",
"graphene_core::ops::SomeNode",
"graphene_core::debug::SomeNode",
"graphene_core::ops::UnwrapNode",
"graphene_core::debug::UnwrapNode",
"graphene_core::debug::UnwrapOptionNode",
],
},
NodeReplacement {
@@ -126,18 +130,6 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
node: graphene_std::animation::real_time::IDENTIFIER,
aliases: &["graphene_core::animation::RealTimeNode"],
},
NodeReplacement {
node: graphene_std::debug::size_of::IDENTIFIER,
aliases: &["graphene_core::ops::SizeOfNode"],
},
NodeReplacement {
node: graphene_std::debug::some::IDENTIFIER,
aliases: &["graphene_core::ops::SomeNode"],
},
NodeReplacement {
node: graphene_std::debug::unwrap_option::IDENTIFIER,
aliases: &["graphene_core::ops::UnwrapNode", "graphene_core::debug::UnwrapNode"],
},
// ================================
// graphic
// ================================
@@ -167,13 +159,19 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
aliases: &["graphene_core::graphic::FlattenVectorNode", "graphene_core::graphic_element::FlattenVectorNode"],
},
NodeReplacement {
node: graphene_std::graphic::index_elements::IDENTIFIER,
node: graphene_std::graphic::item_at_index::IDENTIFIER,
aliases: &[
"graphene_core::graphic_element::IndexNode",
"graphene_core::graphic::IndexNode",
"graphene_core::graphic::IndexElementsNode",
"graphic_nodes::graphic::IndexElementsNode",
"graphic_nodes::graphic::ExtractElementNode",
],
},
NodeReplacement {
node: graphene_std::graphic::remove_at_index::IDENTIFIER,
aliases: &["graphic_nodes::graphic::OmitElementNode"],
},
NodeReplacement {
node: graphene_std::graphic::legacy_layer_extend::IDENTIFIER,
aliases: &[
@@ -777,7 +775,7 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
aliases: &["graphene_core::vector::ClosePathNode"],
},
NodeReplacement {
node: graphene_std::vector::count_elements::IDENTIFIER,
node: graphene_std::vector::list_length::IDENTIFIER,
aliases: &["graphene_core::vector::CountElementsNode"],
},
NodeReplacement {
@@ -1876,6 +1874,25 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
}
// The Text to Vector node's runtime `separate_glyphs` toggle became the dedicated "Text to Vector Glyphs" node, leaving Text to Vector as a plain
// string-to-compound-path converter. A 2-input Text to Vector is the old toggled shape: a `true` toggle routes to Text to Vector Glyphs, otherwise
// the node stays Text to Vector; either way the toggle input is dropped and the string wire is preserved.
if reference == DefinitionIdentifier::ProtoNode(graphene_std::text::text_to_vector::IDENTIFIER) && inputs_count == 2 {
let separate_glyphs = matches!(node.inputs.get(1).and_then(|input| input.as_value()), Some(TaggedValue::Bool(true)));
let target = if separate_glyphs {
graphene_std::text::text_to_vector_glyphs::IDENTIFIER
} else {
graphene_std::text::text_to_vector::IDENTIFIER
};
let mut node_template = resolve_proto_node_type(target)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
if let Some(string_input) = node.inputs.first() {
document.network_interface.set_input(&InputConnector::node(*node_id, 0), string_input.clone(), network_path);
}
}
// Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016
if reference == DefinitionIdentifier::ProtoNode(ProtoNodeIdentifier::new("graphene_std::text::TextNode")) && inputs_count == 8 {
let mut template: NodeTemplate = legacy_text_node_template()?;
@@ -2406,14 +2423,14 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
return None;
};
// Create Count Elements node: counts content `List` items → N
let Some(count_elements_def) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::vector::count_elements::IDENTIFIER)) else {
log::error!("Could not get count_elements node from definition when upgrading morph");
// Create List Length node: counts content `List` items → N
let Some(list_length_def) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::vector::list_length::IDENTIFIER)) else {
log::error!("Could not get list_length node from definition when upgrading morph");
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
return None;
};
let count_elements_template = count_elements_def.default_node_template();
let count_elements_id = NodeId::new();
let list_length_template = list_length_def.default_node_template();
let list_length_id = NodeId::new();
// Create Subtract node: N → N-1
let Some(subtract_def) = resolve_document_node_type(&DefinitionIdentifier::ProtoNode(graphene_std::math_nodes::subtract::IDENTIFIER)) else {
@@ -2435,10 +2452,10 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
let divide_id = NodeId::new();
// Insert and position nodes
document.network_interface.insert_node(count_elements_id, count_elements_template, network_path);
document.network_interface.insert_node(list_length_id, list_length_template, network_path);
document
.network_interface
.shift_absolute_node_position(&count_elements_id, morph_position + IVec2::new(-21, 2), network_path);
.shift_absolute_node_position(&list_length_id, morph_position + IVec2::new(-21, 2), network_path);
document.network_interface.insert_node(subtract_id, subtract_template, network_path);
document.network_interface.shift_absolute_node_position(&subtract_id, morph_position + IVec2::new(-14, 2), network_path);
@@ -2447,12 +2464,12 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
document.network_interface.shift_absolute_node_position(&divide_id, morph_position + IVec2::new(-7, 1), network_path);
// Wire: content source → Count Elements input 0
document.network_interface.set_input(&InputConnector::node(count_elements_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(list_length_id, 0), old_inputs[0].clone(), network_path);
// Wire: Count Elements output → Subtract input 0 (minuend)
document
.network_interface
.set_input(&InputConnector::node(subtract_id, 0), NodeInput::node(count_elements_id, 0), network_path);
.set_input(&InputConnector::node(subtract_id, 0), NodeInput::node(list_length_id, 0), network_path);
// Wire: old progression → Divide input 0 (numerator)
document.network_interface.set_input(&InputConnector::node(divide_id, 0), old_inputs[1].clone(), network_path);
@@ -2745,6 +2762,31 @@ fn migrate_removed_catalog_definitions(node_id: &NodeId, node: &DocumentNode, ne
}
}
// The removed Attach Attribute node (merged into Write Attribute per audit resolution 6) degrades to a passthrough of its
// content: its eager whole-list source input cannot be mechanically rewired as Write Attribute's lazy per-item value producer.
if let Some(DefinitionIdentifier::ProtoNode(identifier)) = document.network_interface.reference(node_id, network_path)
&& identifier.as_str().ends_with("::AttachAttributeNode")
{
let mut node_template = resolve_proto_node_type(graphene_std::ops::passthrough::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)?;
if let Some(content) = old_inputs.first() {
document.network_interface.set_input(&InputConnector::node(*node_id, 0), content.clone(), network_path);
}
}
// The Upload Texture node's old wrapper-network form maps onto its proto node form, which draws the executor from scope
if let Some(DefinitionIdentifier::Network(name)) = document.network_interface.reference(node_id, network_path)
&& name == "Upload Texture"
{
let mut node_template = resolve_proto_node_type(graphene_std::platform_application_io::upload_texture::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)?;
if let Some(content) = old_inputs.first() {
document.network_interface.set_input(&InputConnector::node(*node_id, 0), content.clone(), network_path);
}
}
Some(())
}
@@ -2752,6 +2794,13 @@ fn migrate_removed_catalog_definitions(node_id: &NodeId, node: &DocumentNode, ne
mod tests {
use super::*;
// The removed-definition blocks above abort silently via `?` if their swap target ever leaves the catalog
#[test]
fn removed_definition_swap_targets_resolve() {
assert!(resolve_proto_node_type(graphene_std::ops::passthrough::IDENTIFIER).is_some());
assert!(resolve_proto_node_type(graphene_std::platform_application_io::upload_texture::IDENTIFIER).is_some());
}
#[test]
fn a_written_attribute_no_longer_resets_the_layer_definitions() {
// The node resolves natively again, so a document carrying it keeps