Made BlendTree Editor more robust.

This commit is contained in:
Martin Felis 2026-01-29 23:21:38 +01:00
parent 50243eafba
commit 89c3c38757
4 changed files with 73 additions and 27 deletions

View File

@ -477,11 +477,16 @@ void BLTAnimationNodeBlendTree::BLTBlendTreeGraph::add_node(const Ref<BLTAnimati
} }
void BLTAnimationNodeBlendTree::BLTBlendTreeGraph::remove_node(const Ref<BLTAnimationNode> &node) { void BLTAnimationNodeBlendTree::BLTBlendTreeGraph::remove_node(const Ref<BLTAnimationNode> &node) {
if (node == get_output_node()) {
// Output node not allowed to be removed
return;
}
int removed_node_index = find_node_index(node); int removed_node_index = find_node_index(node);
assert(removed_node_index >= 0); assert(removed_node_index >= 0);
// Remove all connections to and from this node // Remove all connections to and from this node
for (uint32_t i = connections.size() - 1; i > 0; i--) { for (int i = static_cast<int>(connections.size()) - 1; i >= 0; i--) {
if (connections[i].source_node == node || connections[i].target_node == node) { 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); remove_connection(connections[i].source_node, connections[i].target_node, connections[i].target_port_name);
} }

View File

@ -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]) EditorInterface.get_inspector().edit(graph_node_to_blend_tree_node[graph_node])
func _on_blend_tree_graph_edit_node_deselected(node: Node) -> void: func _on_blend_tree_graph_edit_node_deselected(graph_node: Node) -> void:
if selected_nodes.has(node): if selected_nodes.has(graph_node):
selected_nodes.erase(node) selected_nodes.erase(graph_node)
func _on_blend_tree_graph_edit_popup_request(at_position: Vector2) -> void: 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 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: func _on_blend_tree_graph_edit_delete_nodes_request(nodes: Array[StringName]) -> void:
for node_name:StringName in nodes: for node_name:StringName in nodes:
print("remove node '%s'" % node_name) print("remove node '%s'" % node_name)
var blend_tree_node:BLTAnimationNode = blend_tree.get_node(node_name) var blend_tree_node:BLTAnimationNode = blend_tree.get_node(node_name)
if blend_tree_node == null: if blend_tree_node == null:
push_error("Cannot delete node '%s': node not found." % node_name) push_error("Cannot delete node '%s': node not found." % node_name)
continue 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] var graph_node:GraphNode = blend_tree_node_to_graph_node[blend_tree_node]
blend_tree.remove_node(blend_tree_node) blend_tree.remove_node(blend_tree_node)
blend_tree_node_to_graph_node.erase(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) graph_node_to_blend_tree_node.erase(graph_node)
blend_tree_graph_edit.remove_child(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() EditorInterface.get_inspector().edit(null)
if node_name in selected_nodes.keys():
selected_nodes.erase(node_name)
func _on_blend_tree_graph_edit_disconnection_request(from_node: StringName, from_port: int, to_node: StringName, to_port: int) -> void: func _on_blend_tree_graph_edit_disconnection_request(from_node: StringName, from_port: int, to_node: StringName, to_port: int) -> void:

View File

@ -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: 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 * 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: Advantages:

View File

@ -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(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")); 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_b, blend2_node_b, "Input0"));
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_graph.add_connection(animation_sampler_node_c, blend2_node_b, "Input1")); REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_graph.add_connection(animation_sampler_node_c, blend2_node_b, "Input1"));
@ -537,6 +537,7 @@ 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[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); CHECK(blend_tree_graph.node_connection_info[blend2_node_a_index_pre_remove].input_subtree_node_indices.size() == 5);
SUBCASE("Remove animation_sampler_node_a") {
blend_tree_graph.remove_node(animation_sampler_node_a); blend_tree_graph.remove_node(animation_sampler_node_a);
for (const BLTBlendTreeConnection &connection : blend_tree_graph.connections) { for (const BLTBlendTreeConnection &connection : blend_tree_graph.connections) {
@ -559,6 +560,31 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
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(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_b_index_post_remove));
} }
SUBCASE("Remove blend2_node_a") {
blend_tree_graph.remove_node(blend2_node_a);
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);
}
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));
}
}
} }
} //namespace TestSyncedAnimationGraph } //namespace TestSyncedAnimationGraph