From 89c3c387571a0d4f7942cf2176a97684fa952efc Mon Sep 17 00:00:00 2001 From: Martin Felis Date: Thu, 29 Jan 2026 23:21:38 +0100 Subject: [PATCH] Made BlendTree Editor more robust. --- blendalot_animation_node.cpp | 7 ++- demo/addons/blendalot/blendalot_main_panel.gd | 31 +++++++--- doc/design.md | 2 +- tests/test_synced_animation_graph.h | 60 +++++++++++++------ 4 files changed, 73 insertions(+), 27 deletions(-) diff --git a/blendalot_animation_node.cpp b/blendalot_animation_node.cpp index 05cc9b3..5afb8a3 100644 --- a/blendalot_animation_node.cpp +++ b/blendalot_animation_node.cpp @@ -477,11 +477,16 @@ void BLTAnimationNodeBlendTree::BLTBlendTreeGraph::add_node(const Ref &node) { + if (node == get_output_node()) { + // Output node not allowed to be removed + return; + } + int removed_node_index = find_node_index(node); assert(removed_node_index >= 0); // Remove all connections to and from this node - for (uint32_t i = connections.size() - 1; i > 0; i--) { + for (int i = static_cast(connections.size()) - 1; i >= 0; i--) { if (connections[i].source_node == node || connections[i].target_node == node) { remove_connection(connections[i].source_node, connections[i].target_node, connections[i].target_port_name); } diff --git a/demo/addons/blendalot/blendalot_main_panel.gd b/demo/addons/blendalot/blendalot_main_panel.gd index 627e92e..b6e54b7 100644 --- a/demo/addons/blendalot/blendalot_main_panel.gd +++ b/demo/addons/blendalot/blendalot_main_panel.gd @@ -210,9 +210,9 @@ func _on_blend_tree_graph_edit_node_selected(graph_node: Node) -> void: EditorInterface.get_inspector().edit(graph_node_to_blend_tree_node[graph_node]) -func _on_blend_tree_graph_edit_node_deselected(node: Node) -> void: - if selected_nodes.has(node): - selected_nodes.erase(node) +func _on_blend_tree_graph_edit_node_deselected(graph_node: Node) -> void: + if selected_nodes.has(graph_node): + selected_nodes.erase(graph_node) func _on_blend_tree_graph_edit_popup_request(at_position: Vector2) -> void: @@ -238,26 +238,41 @@ func _on_add_node_popup_menu_index_pressed(index: int) -> void: new_node_position = Vector2.INF +func _blend_tree_graph_edit_remove_node_connections(graph_node:GraphNode): + var node_connections:Array = [] + + for connection:Dictionary in blend_tree_graph_edit.connections: + if connection["from_node"] == graph_node.name or connection["to_node"] == graph_node.name: + node_connections.append(connection) + + for node_connection:Dictionary in node_connections: + print("Removing connection %s" % str(node_connection)) + blend_tree_graph_edit.disconnect_node(node_connection["from_node"], node_connection["from_port"], node_connection["to_node"], node_connection["to_port"]) + + func _on_blend_tree_graph_edit_delete_nodes_request(nodes: Array[StringName]) -> void: for node_name:StringName in nodes: print("remove node '%s'" % node_name) var blend_tree_node:BLTAnimationNode = blend_tree.get_node(node_name) + if blend_tree_node == null: push_error("Cannot delete node '%s': node not found." % node_name) continue + if blend_tree_node == blend_tree.get_output_node(): + push_warning("Output node not allowed to be removed.") + continue + var graph_node:GraphNode = blend_tree_node_to_graph_node[blend_tree_node] blend_tree.remove_node(blend_tree_node) blend_tree_node_to_graph_node.erase(blend_tree_node) + _blend_tree_graph_edit_remove_node_connections(graph_node) graph_node_to_blend_tree_node.erase(graph_node) blend_tree_graph_edit.remove_child(graph_node) - graph_node.queue_free() + _on_blend_tree_graph_edit_node_deselected(graph_node) - blend_tree_graph_edit.clear_connections() - - if node_name in selected_nodes.keys(): - selected_nodes.erase(node_name) + EditorInterface.get_inspector().edit(null) func _on_blend_tree_graph_edit_disconnection_request(from_node: StringName, from_port: int, to_node: StringName, to_port: int) -> void: diff --git a/doc/design.md b/doc/design.md index 4ae8c5f..7d84fb3 100644 --- a/doc/design.md +++ b/doc/design.md @@ -111,7 +111,7 @@ Some nodes have special names in the Blend Tree: Except for the output node of a Blend Tree the following properties hold: * all Blend Tree nodes only operate on properties they own and any other data (e.g. inputs and outputs) are specified - via arguments to `SyncedAnimationNode::evaluate(context, inputs, output)` function of the node. + via arguments to `BLTAnimationNode::evaluate(context, inputs, output)` function of the node. Advantages: diff --git a/tests/test_synced_animation_graph.h b/tests/test_synced_animation_graph.h index ac97d2a..3720023 100644 --- a/tests/test_synced_animation_graph.h +++ b/tests/test_synced_animation_graph.h @@ -501,7 +501,7 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_graph.add_connection(blend2_node_a, blend_tree_graph.get_output_node(), "Output")); REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_graph.add_connection(animation_sampler_node_a, blend2_node_a, "Input0")); - // Connect nodes: Subgraph Blend2A, SamplerB, SamplerB + // Connect nodes: Subgraph Blend2A, SamplerB, SamplerC REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_graph.add_connection(animation_sampler_node_b, blend2_node_b, "Input0")); REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_graph.add_connection(animation_sampler_node_c, blend2_node_b, "Input1")); @@ -537,27 +537,53 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph CHECK(blend_tree_graph.node_connection_info[0].input_subtree_node_indices.size() == 6); CHECK(blend_tree_graph.node_connection_info[blend2_node_a_index_pre_remove].input_subtree_node_indices.size() == 5); - blend_tree_graph.remove_node(animation_sampler_node_a); + SUBCASE("Remove animation_sampler_node_a") { + blend_tree_graph.remove_node(animation_sampler_node_a); - for (const BLTBlendTreeConnection &connection : blend_tree_graph.connections) { - bool is_connection_with_removed_node = connection.source_node == animation_sampler_node_a || connection.target_node == animation_sampler_node_a; - CHECK(is_connection_with_removed_node == false); + for (const BLTBlendTreeConnection &connection : blend_tree_graph.connections) { + bool is_connection_with_removed_node = connection.source_node == animation_sampler_node_a || connection.target_node == animation_sampler_node_a; + CHECK(is_connection_with_removed_node == false); + } + + int animation_sampler_node_b_index_post_remove = blend_tree_graph.find_node_index(animation_sampler_node_b); + int blend2_node_a_index_post_remove = blend_tree_graph.find_node_index(blend2_node_a); + int blend2_node_b_index_post_remove = blend_tree_graph.find_node_index(blend2_node_b); + + CHECK(blend_tree_graph.find_node_index(animation_sampler_node_a) == -1); + CHECK(blend2_node_b_index_post_remove == blend2_node_b_index_pre_remove - 1); + CHECK(animation_sampler_node_b_index_post_remove == animation_sampler_node_b_index_pre_remove - 1); + + CHECK(blend_tree_graph.node_connection_info[0].input_subtree_node_indices.size() == 5); + CHECK(blend_tree_graph.node_connection_info[blend2_node_a_index_post_remove].input_subtree_node_indices.size() == 4); + CHECK(blend_tree_graph.node_connection_info[blend2_node_a_index_post_remove].connected_child_node_index_at_port[0] == -1); + CHECK(blend_tree_graph.node_connection_info[blend2_node_a_index_post_remove].connected_child_node_index_at_port[1] == blend2_node_b_index_post_remove); + CHECK(blend_tree_graph.node_connection_info[blend2_node_b_index_post_remove].input_subtree_node_indices.has(blend2_node_b_index_post_remove)); + CHECK(blend_tree_graph.node_connection_info[blend2_node_b_index_post_remove].input_subtree_node_indices.has(animation_sampler_node_b_index_post_remove)); } - int animation_sampler_node_b_index_post_remove = blend_tree_graph.find_node_index(animation_sampler_node_b); - int blend2_node_a_index_post_remove = blend_tree_graph.find_node_index(blend2_node_a); - int blend2_node_b_index_post_remove = blend_tree_graph.find_node_index(blend2_node_b); + SUBCASE("Remove blend2_node_a") { + blend_tree_graph.remove_node(blend2_node_a); - CHECK(blend_tree_graph.find_node_index(animation_sampler_node_a) == -1); - CHECK(blend2_node_b_index_post_remove == blend2_node_b_index_pre_remove - 1); - CHECK(animation_sampler_node_b_index_post_remove == animation_sampler_node_b_index_pre_remove - 1); + for (const BLTBlendTreeConnection &connection : blend_tree_graph.connections) { + bool is_connection_with_removed_node = connection.source_node == blend2_node_a || connection.target_node == blend2_node_a; + CHECK(is_connection_with_removed_node == false); + } - CHECK(blend_tree_graph.node_connection_info[0].input_subtree_node_indices.size() == 5); - CHECK(blend_tree_graph.node_connection_info[blend2_node_a_index_post_remove].input_subtree_node_indices.size() == 4); - CHECK(blend_tree_graph.node_connection_info[blend2_node_a_index_post_remove].connected_child_node_index_at_port[0] == -1); - CHECK(blend_tree_graph.node_connection_info[blend2_node_a_index_post_remove].connected_child_node_index_at_port[1] == blend2_node_b_index_post_remove); - CHECK(blend_tree_graph.node_connection_info[blend2_node_b_index_post_remove].input_subtree_node_indices.has(blend2_node_b_index_post_remove)); - CHECK(blend_tree_graph.node_connection_info[blend2_node_b_index_post_remove].input_subtree_node_indices.has(animation_sampler_node_b_index_post_remove)); + int animation_sampler_node_b_index_post_remove = blend_tree_graph.find_node_index(animation_sampler_node_b); + int animation_sampler_node_c_index_post_remove = blend_tree_graph.find_node_index(animation_sampler_node_c); + int blend2_node_b_index_post_remove = blend_tree_graph.find_node_index(blend2_node_b); + + CHECK(blend_tree_graph.find_node_index(blend2_node_a) == -1); + CHECK(blend2_node_b_index_post_remove == blend2_node_b_index_pre_remove - 1); + CHECK(animation_sampler_node_b_index_post_remove == animation_sampler_node_b_index_pre_remove); + + CHECK(blend_tree_graph.node_connection_info[0].input_subtree_node_indices.size() == 1); + CHECK(blend_tree_graph.node_connection_info[blend2_node_b_index_post_remove].input_subtree_node_indices.size() == 3); + blend_tree_graph.node_connection_info[blend2_node_b_index_post_remove]._print_subtree(); + CHECK(blend_tree_graph.node_connection_info[blend2_node_b_index_post_remove].input_subtree_node_indices.has(blend2_node_b_index_post_remove)); + CHECK(blend_tree_graph.node_connection_info[blend2_node_b_index_post_remove].input_subtree_node_indices.has(animation_sampler_node_b_index_post_remove)); + CHECK(blend_tree_graph.node_connection_info[blend2_node_b_index_post_remove].input_subtree_node_indices.has(animation_sampler_node_c_index_post_remove)); + } } }