Add the 'Blend Shapes' and 'Origins to Polyline' nodes; generalize the 'Morph' node to >2 states (#3405)

* New nodes: 'Morph' and 'Multi-Morph'

* Blend Shapes node

* Add the 'Index Points' node

* Fix failing test
This commit is contained in:
Keavon Chambers
2025-11-25 22:17:55 -08:00
committed by GitHub
parent d247b81966
commit 9eb8835bd5
6 changed files with 783 additions and 116 deletions

View File

@@ -416,6 +416,539 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("Creates a new Artboard which can be used as a working surface."),
properties: None,
},
DocumentNodeDefinition {
identifier: "Blend Shapes",
category: "Vector",
// [IMPORTS]2 -> 0[0:Floor]
// [0:Floor]0 -> 0[1:Subtract]
// "1: f64" -> 1[1:Subtract]
// "(): ()" -> 0[2:Instance Index]
// "0: u32" -> 1[2:Instance Index]
// [2:Instance Index]0 -> 0[3:Divide]
// [1:Subtract]0 -> 1[3:Divide]
// [IMPORTS]1 -> 0[4:Position on Path]
// [3:Divide]0 -> 1[4:Position on Path]
// "false: bool" -> 2[4:Position on Path]
// "false: bool" -> 3[4:Position on Path]
// "(): ()" -> 0[5:Instance Vector]
// [5:Instance Vector]0 -> 0[6:Reset Transform]
// "true: bool" -> 1[6:Reset Transform]
// "false: bool" -> 2[6:Reset Transform]
// "false: bool" -> 3[6:Reset Transform]
// [12:Flatten Vector]0 -> 0[7:Instance Map]
// [6:Reset Transform]0 -> 1[7:Instance Map]
// [7:Instance Map]0 -> 0[8:Morph]
// [15:Multiply]0 -> 1[8:Morph]
// [8:Morph]0 -> 0[9:Transform]
// [4:Position on Path]0 -> 1[9:Transform]
// "0: f64" -> 2[9:Transform]
// "(0, 0): DVec2" -> 3[9:Transform]
// "(0, 0): DVec2" -> 4[9:Transform]
// [IMPORTS]1 -> 0[10:Count Points]
// [10:Count Points]0 -> 0[11:Equals]
// [13:Count Elements]0 -> 1[11:Equals]
// [IMPORTS]0 -> 0[12:Flatten Vector]
// [12:Flatten Vector]0 -> 0[13:Count Elements]
// [13:Count Elements]0 -> 0[14:Subtract]
// "1: f64" -> 1[14:Subtract]
// [3:Divide]0 -> 0[15:Multiply]
// [14:Subtract]0 -> 1[15:Multiply]
// [12:Flatten Vector]0 -> 0[16:Morph]
// [15:Multiply]0 -> 1[16:Morph]
// [11:Equals]0 -> 0[17:Switch]
// [9:Transform]0 -> 1[17:Switch]
// [16:Morph]0 -> 2[17:Switch]
// [17:Switch]0 -> 0[18:Instance Repeat]
// [0:Floor]0 -> 1[18:Instance Repeat]
// [IMPORTS]3 -> 2[18:Instance Repeat]
// [18:Instance Repeat]0 -> 0[EXPORTS]
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(18), 0)],
nodes: [
// 0: Floor
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::floor::IDENTIFIER),
inputs: vec![NodeInput::import(concrete!(f64), 2)],
..Default::default()
},
// 1: Subtract
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::subtract::IDENTIFIER),
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::value(TaggedValue::F64(1.), false)],
..Default::default()
},
// 2: Instance Index
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::instance_index::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::U32(0), false)],
..Default::default()
},
// 3: Divide
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::divide::IDENTIFIER),
inputs: vec![NodeInput::node(NodeId(2), 0), NodeInput::node(NodeId(1), 0)],
..Default::default()
},
// 4: Position on Path
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::position_on_path::IDENTIFIER),
inputs: vec![
NodeInput::import(generic!(T), 1),
NodeInput::node(NodeId(3), 0),
NodeInput::value(TaggedValue::Bool(false), false),
NodeInput::value(TaggedValue::Bool(false), false),
],
..Default::default()
},
// 5: Instance Vector
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::instance_vector::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::None, false)],
..Default::default()
},
// 6: Reset Transform
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::reset_transform::IDENTIFIER),
inputs: vec![
NodeInput::node(NodeId(5), 0),
NodeInput::value(TaggedValue::Bool(true), false),
NodeInput::value(TaggedValue::Bool(false), false),
NodeInput::value(TaggedValue::Bool(false), false),
],
..Default::default()
},
// 7: Instance Map
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::instance_map::IDENTIFIER),
inputs: vec![NodeInput::node(NodeId(12), 0), NodeInput::node(NodeId(6), 0)],
..Default::default()
},
// 8: Morph
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(vector::morph::IDENTIFIER),
inputs: vec![NodeInput::node(NodeId(7), 0), NodeInput::node(NodeId(15), 0)],
..Default::default()
},
// 9: Transform
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::transform::IDENTIFIER),
inputs: vec![
NodeInput::node(NodeId(8), 0),
NodeInput::node(NodeId(4), 0),
NodeInput::value(TaggedValue::F64(0.), false),
NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false),
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
],
..Default::default()
},
// 10: Count Points
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::count_points::IDENTIFIER),
inputs: vec![NodeInput::import(generic!(T), 1)],
..Default::default()
},
// 11: Equals
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::equals::IDENTIFIER),
inputs: vec![NodeInput::node(NodeId(10), 0), NodeInput::node(NodeId(13), 0)],
..Default::default()
},
// 12: Flatten Vector
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(graphic_nodes::graphic::flatten_vector::IDENTIFIER),
inputs: vec![NodeInput::import(generic!(T), 0)],
..Default::default()
},
// 13: Count Elements
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(vector::count_elements::IDENTIFIER),
inputs: vec![NodeInput::node(NodeId(12), 0)],
..Default::default()
},
// 14: Subtract
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::subtract::IDENTIFIER),
inputs: vec![NodeInput::node(NodeId(13), 0), NodeInput::value(TaggedValue::F64(1.), false)],
..Default::default()
},
// 15: Multiply
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(math_nodes::multiply::IDENTIFIER),
inputs: vec![NodeInput::node(NodeId(3), 0), NodeInput::node(NodeId(14), 0)],
..Default::default()
},
// 16: Morph
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(vector::morph::IDENTIFIER),
inputs: vec![NodeInput::node(NodeId(12), 0), NodeInput::node(NodeId(15), 0)],
..Default::default()
},
// 17: Switch
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(logic::switch::IDENTIFIER),
inputs: vec![NodeInput::node(NodeId(11), 0), NodeInput::node(NodeId(9), 0), NodeInput::node(NodeId(16), 0)],
..Default::default()
},
// 18: Instance Repeat
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::instance_repeat::IDENTIFIER),
inputs: vec![NodeInput::node(NodeId(17), 0), NodeInput::node(NodeId(0), 0), NodeInput::import(generic!(T), 3)],
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
inputs: vec![
NodeInput::value(TaggedValue::Vector(Default::default()), true),
NodeInput::value(TaggedValue::Vector(Default::default()), true),
NodeInput::value(TaggedValue::F64(10.), false),
NodeInput::value(TaggedValue::Bool(Default::default()), false),
],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_metadata: vec![("Content", "TODO").into(), ("Path", "TODO").into(), ("Count", "TODO").into(), ("Reverse", "TODO").into()],
output_names: vec!["Out".to_string()],
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
// 0: Floor
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
},
// 1: Subtract
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, -1)),
..Default::default()
},
..Default::default()
},
// 2: Instance Index
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, -2)),
..Default::default()
},
..Default::default()
},
// 3: Divide
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, -2)),
..Default::default()
},
..Default::default()
},
// 4: Position on Path
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(28, -3)),
..Default::default()
},
..Default::default()
},
// 5: Instance Vector
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 2)),
..Default::default()
},
..Default::default()
},
// 6: Reset Transform
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 2)),
..Default::default()
},
..Default::default()
},
// 7: Instance Map
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 1)),
..Default::default()
},
..Default::default()
},
// 8: Morph
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(28, 1)),
..Default::default()
},
..Default::default()
},
// 9: Transform
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(35, 1)),
..Default::default()
},
..Default::default()
},
// 10: Count Points
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 4)),
..Default::default()
},
..Default::default()
},
// 11: Equals
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 4)),
..Default::default()
},
..Default::default()
},
// 12: Flatten Vector
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 6)),
..Default::default()
},
..Default::default()
},
// 13: Count Elements
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 8)),
..Default::default()
},
..Default::default()
},
// 14: Subtract
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 8)),
..Default::default()
},
..Default::default()
},
// 15: Multiply
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 7)),
..Default::default()
},
..Default::default()
},
// 16: Morph
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(28, 6)),
..Default::default()
},
..Default::default()
},
// 17: Switch
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(42, 4)),
..Default::default()
},
..Default::default()
},
// 18: Instance Repeat
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(49, -1)),
..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: "Origins to Polyline",
category: "Vector",
// "(): ()" -> 0[0:Instance Vector]
// [0:Instance Vector]0 -> 0[1:Extract Transform]
// [1:Extract Transform]0 -> 0[2:Decompose Translation]
// [2:Decompose Translation]0 -> 0[3:Vec2 to Point]
// [IMPORTS]0 -> 0[4:Flatten Vector]
// [4:Flatten Vector]0 -> 0[5:Instance Map]
// [3:Vec2 to Point]0 -> 1[5:Instance Map]
// [5:Instance Map]0 -> 0[6: Flatten Path]
// [6:Flatten Path]0 -> 0[7:Points to Polyline]
// "false: bool" -> 1[7:Points to Polyline]
// [7:Points to Polyline]0 -> 0[EXPORTS]
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(7), 0)],
nodes: [
// 0: Instance Vector
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::instance_vector::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::None, false)],
..Default::default()
},
// 1: Extract Transform
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::extract_transform::IDENTIFIER),
inputs: vec![NodeInput::node(NodeId(0), 0)],
..Default::default()
},
// 2: Decompose Translation
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::decompose_translation::IDENTIFIER),
inputs: vec![NodeInput::node(NodeId(1), 0)],
..Default::default()
},
// 3: Vec2 to Point
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::vec_2_to_point::IDENTIFIER),
inputs: vec![NodeInput::node(NodeId(2), 0)],
..Default::default()
},
// 4: Flatten Vector
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(graphic_nodes::graphic::flatten_vector::IDENTIFIER),
inputs: vec![NodeInput::import(generic!(T), 0)],
..Default::default()
},
// 5: Instance Map
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(vector_nodes::instance_map::IDENTIFIER),
inputs: vec![NodeInput::node(NodeId(4), 0), NodeInput::node(NodeId(3), 0)],
..Default::default()
},
// 6: Flatten Path
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(vector::flatten_path::IDENTIFIER),
inputs: vec![NodeInput::node(NodeId(5), 0)],
..Default::default()
},
// 7: Points to Polyline
DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(vector::points_to_polyline::IDENTIFIER),
inputs: vec![NodeInput::node(NodeId(6), 0), NodeInput::value(TaggedValue::Bool(false), false)],
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
inputs: vec![NodeInput::value(TaggedValue::Vector(Default::default()), true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_metadata: vec![("Vector", "TODO").into()],
output_names: vec!["Vector".to_string()],
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
// 0: Instance Vector
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 1)),
..Default::default()
},
..Default::default()
},
// 1: Extract Transform
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 1)),
..Default::default()
},
..Default::default()
},
// 2: Decompose Transform
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 1)),
..Default::default()
},
..Default::default()
},
// 3: Vec2 to Point
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 1)),
..Default::default()
},
..Default::default()
},
// 4: Flatten Vector
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(21, 0)),
..Default::default()
},
..Default::default()
},
// 5: Instance Map
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(28, 0)),
..Default::default()
},
..Default::default()
},
// 6: Flatten Path
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(35, 0)),
..Default::default()
},
..Default::default()
},
// 7: Points to Polyline
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(42, 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: "Load Image",
category: "Web Request",
@@ -745,13 +1278,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::value(TaggedValue::None, false), NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(1), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::import(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
inputs: vec![NodeInput::import(concrete!(DVec2), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
call_argument: generic!(T),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::import(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)],
inputs: vec![NodeInput::import(concrete!(DVec2), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)],
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
call_argument: generic!(T),
..Default::default()

View File

@@ -154,6 +154,7 @@ pub(crate) fn property_from_type(
Some("PixelLength") => number_widget(default_info, number_input.min(min(0.)).unit(unit.unwrap_or(" px"))).into(),
Some("Length") => number_widget(default_info, number_input.min(min(0.))).into(),
Some("Fraction") => number_widget(default_info, number_input.mode_range().min(min(0.)).max(max(1.))).into(),
Some("Progression") => progression_widget(default_info, number_input.min(min(0.))).into(),
Some("SignedInteger") => number_widget(default_info, number_input.int()).into(),
Some("IntegerCount") => number_widget(default_info, number_input.int().min(min(1.))).into(),
Some("SeedValue") => number_widget(default_info, number_input.int().min(min(0.))).into(),
@@ -794,6 +795,50 @@ pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetH
(first_widgets, second_widgets)
}
// Two number fields beside one another, the first for the fractional part (decimals, range mode) and the second for the whole part (integers, increment mode)
pub fn progression_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: NumberInput) -> Vec<WidgetHolder> {
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
let mut widgets = start_widgets(parameter_widgets_info);
let Some(document_node) = document_node else { return Vec::new() };
let Some(input) = document_node.inputs.get(index) else {
log::warn!("A widget failed to be built because its node's input index is invalid.");
return vec![];
};
if let Some(&TaggedValue::F64(x)) = input.as_non_exposed_value() {
let whole_part = x.trunc();
let fractional_part = x.fract();
widgets.extend_from_slice(&[
Separator::new(SeparatorType::Unrelated).widget_holder(),
number_props
.clone()
.label("Progress")
.mode_range()
.min(0.)
.max(0.99999)
.value(Some(fractional_part))
.on_update(update_value(move |input: &NumberInput| TaggedValue::F64(whole_part + input.value.unwrap()), node_id, index))
.on_commit(commit_value)
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
TextLabel::new("+").widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
number_props
.label("Element #")
.mode_increment()
.min(0.)
.is_integer(true)
.value(Some(whole_part))
.on_update(update_value(move |input: &NumberInput| TaggedValue::F64(input.value.unwrap() + fractional_part), node_id, index))
.on_commit(commit_value)
.widget_holder(),
])
}
widgets
}
pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: NumberInput) -> Vec<WidgetHolder> {
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;

View File

@@ -1582,6 +1582,53 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
}
}
// Migrate from the old source/target "Morph" node to the new vector table based "Morph" node.
// This doesn't produce exactly equivalent results in cases involving input vector tables with multiple rows.
// The old version would zip the source and target table rows, interpoleating each pair together.
// The migrated version will instead deeply flatten both merged tables and morph sequentially between all source vectors and all target vector elements.
// This migration assumes most usages didn't involve multiple parallel vector elements, and instead morphed from a single source to a single target vector element.
if reference == "Morph" && inputs_count == 3 {
// Old signature:
// async fn morph(_: impl Ctx, source: Table<Vector>, #[expose] target: Table<Vector>, #[default(0.5)] time: Fraction) -> Table<Vector> { ... }
//
// New signature:
// async fn morph<I: IntoGraphicTable>(_: impl Ctx, content: #[implementations(Table<Graphic>, Table<Vector>)] content: I, progression: Progression) -> Table<Vector> { ... }
let mut node_template = resolve_document_node_type(reference)?.default_node_template();
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
// Create a new Merge node
let Some(merge_node_type) = resolve_document_node_type("Merge") else {
log::error!("Could not get merge node from definition when upgrading morph");
return None;
};
let merge_template = merge_node_type.default_node_template();
let merge_node_id = NodeId::new();
// Decide on the placement position of the new Merge node
let Some(morph_position) = document.network_interface.position_from_downstream_node(node_id, network_path) else {
log::error!("Could not get position for morph node {node_id}");
return None;
};
let merge_position = morph_position + IVec2::new(-7, 0);
// Insert the new Merge node into the network
document.network_interface.insert_node(merge_node_id, merge_template, network_path);
document.network_interface.set_to_node_or_layer(&merge_node_id, network_path, false);
document.network_interface.shift_absolute_node_position(&merge_node_id, merge_position, network_path);
// Connect the old 'source' and 'target' inputs to the new Merge node
document.network_interface.set_input(&InputConnector::node(merge_node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(merge_node_id, 1), old_inputs[1].clone(), network_path);
// Connect the new Merge node to the 'content' input of the Morph node
document
.network_interface
.set_input(&InputConnector::node(*node_id, 0), NodeInput::node(merge_node_id, 0), network_path);
// Connect the old 'progression' input to the new 'progression' input of the Morph node
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[2].clone(), network_path);
}
// Add context features to nodes that don't have them (fine-grained context caching migration)
if node.context_features == graphene_std::ContextDependencies::default()
&& let Some(reference) = document.network_interface.reference(node_id, network_path).cloned().flatten()