From fd13c53e52c63a53ad9a989b1d6a9c175f461769 Mon Sep 17 00:00:00 2001 From: Martin Felis Date: Sat, 24 Jan 2026 15:38:27 +0100 Subject: [PATCH] Exposed additional functions to GDScript to implement a GDScript based EditorPlugin. --- blendalot_animation_graph.cpp | 2 +- blendalot_animation_node.cpp | 45 +++++++---- blendalot_animation_node.h | 116 ++++++++++++++++++++++------ tests/test_synced_animation_graph.h | 23 +++--- 4 files changed, 132 insertions(+), 54 deletions(-) diff --git a/blendalot_animation_graph.cpp b/blendalot_animation_graph.cpp index 976db5f..53a013c 100644 --- a/blendalot_animation_graph.cpp +++ b/blendalot_animation_graph.cpp @@ -51,7 +51,7 @@ void BLTAnimationGraph::_update_properties_for_node(const String &p_base_path, R p_node->get_child_nodes(&children); for (const Ref &child_node : children) { - _update_properties_for_node(p_base_path + child_node->name + "/", child_node); + _update_properties_for_node(p_base_path + child_node->get_name() + "/", child_node); } } diff --git a/blendalot_animation_node.cpp b/blendalot_animation_node.cpp index 1418d25..936eb83 100644 --- a/blendalot_animation_node.cpp +++ b/blendalot_animation_node.cpp @@ -5,9 +5,17 @@ #include "blendalot_animation_node.h" void BLTAnimationNode::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_position", "position"), &BLTAnimationNode::set_position); + ClassDB::bind_method(D_METHOD("get_position"), &BLTAnimationNode::get_position); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position"), "set_position", "get_position"); + ADD_SIGNAL(MethodInfo("tree_changed")); ADD_SIGNAL(MethodInfo("animation_node_renamed", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "old_name"), PropertyInfo(Variant::STRING, "new_name"))); ADD_SIGNAL(MethodInfo("animation_node_removed", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "name"))); + + ClassDB::bind_method(D_METHOD("get_input_names"), &BLTAnimationNode::get_input_names_as_typed_array); + ClassDB::bind_method(D_METHOD("get_input_count"), &BLTAnimationNode::get_input_count); + ClassDB::bind_method(D_METHOD("get_input_index"), &BLTAnimationNode::get_input_index); } void BLTAnimationNode::get_parameter_list(List *r_list) const { @@ -42,8 +50,13 @@ void BLTAnimationNode::_animation_node_removed(const ObjectID &p_oid, const Stri void BLTAnimationNodeBlendTree::_bind_methods() { ClassDB::bind_method(D_METHOD("add_node", "animation_node"), &BLTAnimationNodeBlendTree::add_node); + ClassDB::bind_method(D_METHOD("get_node", "node_name"), &BLTAnimationNodeBlendTree::get_node); ClassDB::bind_method(D_METHOD("get_output_node"), &BLTAnimationNodeBlendTree::get_output_node); + ClassDB::bind_method(D_METHOD("get_node_names"), &BLTAnimationNodeBlendTree::get_node_names_as_typed_array); + + ClassDB::bind_method(D_METHOD("is_connection_valid", "source_node", "target_node", "target_port_name"), &BLTAnimationNodeBlendTree::is_connection_valid); ClassDB::bind_method(D_METHOD("add_connection", "source_node", "target_node", "target_port_name"), &BLTAnimationNodeBlendTree::add_connection); + ClassDB::bind_method(D_METHOD("get_connections"), &BLTAnimationNodeBlendTree::get_connections_as_array); BIND_CONSTANT(CONNECTION_OK); BIND_CONSTANT(CONNECTION_ERROR_GRAPH_ALREADY_INITIALIZED); @@ -57,7 +70,7 @@ void BLTAnimationNodeBlendTree::_bind_methods() { void BLTAnimationNodeBlendTree::_get_property_list(List *p_list) const { for (const Ref &node : tree_graph.nodes) { - String prop_name = node->name; + String prop_name = node->get_name(); if (prop_name != "Output") { p_list->push_back(PropertyInfo(Variant::OBJECT, "nodes/" + prop_name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NO_EDITOR)); } @@ -93,9 +106,9 @@ bool BLTAnimationNodeBlendTree::_get(const StringName &p_name, Variant &r_value) int idx = 0; for (const BLTBlendTreeConnection &connection : tree_graph.connections) { - conns[idx * 3 + 0] = connection.target_node->name; + conns[idx * 3 + 0] = connection.target_node->get_name(); conns[idx * 3 + 1] = connection.target_node->get_input_index(connection.target_port_name); - conns[idx * 3 + 2] = connection.source_node->name; + conns[idx * 3 + 2] = connection.source_node->get_name(); idx++; } @@ -115,7 +128,7 @@ bool BLTAnimationNodeBlendTree::_set(const StringName &p_name, const Variant &p_ if (what == "node") { Ref anode = p_value; if (anode.is_valid()) { - anode->name = node_name; + anode->set_name(node_name); add_node(anode); } return true; @@ -138,8 +151,7 @@ bool BLTAnimationNodeBlendTree::_set(const StringName &p_name, const Variant &p_ int source_node_index = find_node_index_by_name(conns[i + 2]); Ref target_node = tree_graph.nodes[target_node_index]; - Vector target_input_names; - target_node->get_input_names(target_input_names); + Vector target_input_names = target_node->get_input_names(); add_connection(tree_graph.nodes[source_node_index], target_node, target_input_names[target_node_port_index]); } @@ -255,11 +267,13 @@ void AnimationDataAllocator::register_track_values(const Ref &animati } bool BLTAnimationNodeSampler::initialize(GraphEvaluationContext &context) { - BLTAnimationNode::initialize(context); + if (!BLTAnimationNode::initialize(context)) { + return false; + } animation = context.animation_player->get_animation(animation_name); if (!animation.is_valid()) { - print_error(vformat("Cannot initialize node %s: animation '%s' not found in animation player.", name, animation_name)); + print_error(vformat("Cannot initialize node %s: animation '%s' not found in animation player.", get_name(), animation_name)); return false; } @@ -412,7 +426,7 @@ bool BLTAnimationNodeBlend2::_set(const StringName &p_name, const Variant &p_val BLTAnimationNodeBlendTree::BLTBlendTreeGraph::BLTBlendTreeGraph() { Ref output_node; output_node.instantiate(); - output_node->name = "Output"; + output_node->set_name("Output"); add_node(output_node); } @@ -432,7 +446,7 @@ int BLTAnimationNodeBlendTree::BLTBlendTreeGraph::find_node_index(const Refname == name) { + if (nodes[i]->get_name() == name) { return i; } } @@ -441,15 +455,15 @@ int BLTAnimationNodeBlendTree::BLTBlendTreeGraph::find_node_index_by_name(const } void BLTAnimationNodeBlendTree::BLTBlendTreeGraph::add_node(const Ref &node) { - StringName node_base_name = node->name; + StringName node_base_name = node->get_name(); if (node_base_name.is_empty()) { node_base_name = node->get_class_name(); } - node->name = node_base_name; + node->set_name(node_base_name); int number_suffix = 1; - while (find_node_index_by_name(node->name) != -1) { - node->name = vformat("%s %d", node_base_name, number_suffix); + while (find_node_index_by_name(node->get_name()) != -1) { + node->set_name(vformat("%s %d", node_base_name, number_suffix)); number_suffix++; } @@ -546,8 +560,7 @@ BLTAnimationNodeBlendTree::ConnectionError BLTAnimationNodeBlendTree::BLTBlendTr return CONNECTION_ERROR_NO_TARGET_NODE; } - Vector target_inputs; - target_node->get_input_names(target_inputs); + Vector target_inputs = target_node->get_input_names(); if (!target_inputs.has(target_port_name)) { print_error("Cannot connect nodes: target port not found."); diff --git a/blendalot_animation_node.h b/blendalot_animation_node.h index a3f4ee7..3bea3a7 100644 --- a/blendalot_animation_node.h +++ b/blendalot_animation_node.h @@ -262,7 +262,6 @@ public: NodeTimeInfo node_time_info; bool active = false; - StringName name; Vector2 position; virtual ~BLTAnimationNode() override = default; @@ -285,6 +284,7 @@ public: node_time_info.loop_mode = input_nodes[0]->node_time_info.loop_mode; } } + virtual void update_time(double p_time) { if (node_time_info.is_synced) { node_time_info.sync_position = p_time; @@ -293,6 +293,7 @@ public: node_time_info.position += p_time; } } + virtual void evaluate(GraphEvaluationContext &context, const LocalVector &input_datas, AnimationData &output_data) { // By default, use the AnimationData of the first input. if (input_datas.size() > 0) { @@ -300,16 +301,32 @@ public: } } - virtual void get_input_names(Vector &inputs) const {} + void set_position(const Vector2 &p_position) { + position = p_position; + } + + Vector2 get_position() const { + return position; + } + + virtual Vector get_input_names() const { return {}; } + + TypedArray get_input_names_as_typed_array() const { + TypedArray typed_arr; + Vector vec = get_input_names(); + typed_arr.resize(vec.size()); + for (uint32_t i = 0; i < vec.size(); i++) { + typed_arr[i] = vec[i]; + } + return typed_arr; + } int get_input_index(const StringName &port_name) const { - Vector inputs; - get_input_names(inputs); + Vector inputs = get_input_names(); return inputs.find(port_name); } int get_input_count() const { - Vector inputs; - get_input_names(inputs); + Vector inputs = get_input_names(); return inputs.size(); } @@ -341,8 +358,8 @@ class BLTAnimationNodeOutput : public BLTAnimationNode { GDCLASS(BLTAnimationNodeOutput, BLTAnimationNode); public: - void get_input_names(Vector &inputs) const override { - inputs.push_back("Input"); + Vector get_input_names() const override { + return { "Output" }; } }; @@ -353,20 +370,21 @@ public: float blend_weight = 0.0f; bool sync = true; - void get_input_names(Vector &inputs) const override { - inputs.push_back("Input0"); - inputs.push_back("Input1"); + Vector get_input_names() const override { + return { "Input0", "Input1" }; } bool initialize(GraphEvaluationContext &context) override { - bool result = BLTAnimationNode::initialize(context); + if (!BLTAnimationNode::initialize(context)) { + return false; + } if (sync) { // TODO: do we always want looping in this case or do we traverse the graph to check what's reasonable? node_time_info.loop_mode = Animation::LOOP_LINEAR; } - return result; + return true; } void activate_inputs(const Vector> &input_nodes) override { input_nodes[0]->active = true; @@ -549,10 +567,6 @@ public: }; LocalVector _node_runtime_data; - Ref get_output_node() const { - return tree_graph.nodes[0]; - } - int find_node_index(const Ref &node) const { return tree_graph.find_node_index(node); } @@ -561,14 +575,6 @@ public: return tree_graph.find_node_index_by_name(name); } - Ref get_node(int node_index) { - if (node_index < 0 || node_index > tree_graph.nodes.size()) { - return nullptr; - } - - return tree_graph.nodes[node_index]; - } - void add_node(const Ref &node) { if (tree_initialized) { print_error("Cannot add node to BlendTree: BlendTree already initialized."); @@ -578,6 +584,51 @@ public: tree_graph.add_node(node); } + TypedArray get_node_names_as_typed_array() const { + Vector vec; + for (const Ref &node : tree_graph.nodes) { + vec.push_back(node->get_name()); + } + + TypedArray typed_arr; + typed_arr.resize(vec.size()); + for (uint32_t i = 0; i < vec.size(); i++) { + typed_arr[i] = vec[i]; + } + return typed_arr; + } + + Ref get_node(const StringName &node_name) const { + int node_index = tree_graph.find_node_index_by_name(node_name); + + if (node_index >= 0) { + return tree_graph.nodes[node_index]; + } + + return nullptr; + } + + Ref get_node_by_index(int node_index) const { + if (node_index < 0 || node_index > tree_graph.nodes.size()) { + return nullptr; + } + + return tree_graph.nodes[node_index]; + } + + Ref get_output_node() const { + return tree_graph.nodes[0]; + } + + ConnectionError is_connection_valid(const Ref &source_node, const Ref &target_node, const StringName &target_port_name) { + if (tree_initialized) { + print_error("Cannot add connection to BlendTree: BlendTree already initialized."); + return CONNECTION_ERROR_GRAPH_ALREADY_INITIALIZED; + } + + return tree_graph.is_connection_valid(source_node, target_node, target_port_name); + } + ConnectionError add_connection(const Ref &source_node, const Ref &target_node, const StringName &target_port_name) { if (tree_initialized) { print_error("Cannot add connection to BlendTree: BlendTree already initialized."); @@ -587,8 +638,23 @@ public: return tree_graph.add_connection(source_node, target_node, target_port_name); } + Array get_connections_as_array() const { + Array result; + for (const BLTBlendTreeConnection &connection : tree_graph.connections) { + result.push_back(connection.source_node); + result.push_back(connection.target_node); + result.push_back(connection.target_port_name); + } + + return result; + } + // overrides from SyncedAnimationNode bool initialize(GraphEvaluationContext &context) override { + if (!BLTAnimationNode::initialize(context)) { + return false; + } + sort_nodes(); setup_runtime_data(); diff --git a/tests/test_synced_animation_graph.h b/tests/test_synced_animation_graph.h index f3923a9..94ef779 100644 --- a/tests/test_synced_animation_graph.h +++ b/tests/test_synced_animation_graph.h @@ -146,27 +146,27 @@ TEST_CASE("[SyncedAnimationGraph] Test BlendTree construction") { Ref animation_sampler_node0; animation_sampler_node0.instantiate(); - animation_sampler_node0->name = "Sampler0"; + animation_sampler_node0->set_name("Sampler0"); tree_constructor.add_node(animation_sampler_node0); Ref animation_sampler_node1; animation_sampler_node1.instantiate(); - animation_sampler_node1->name = "Sampler1"; + animation_sampler_node1->set_name("Sampler1"); tree_constructor.add_node(animation_sampler_node1); Ref animation_sampler_node2; animation_sampler_node2.instantiate(); - animation_sampler_node2->name = "Sampler2"; + animation_sampler_node2->set_name("Sampler2"); tree_constructor.add_node(animation_sampler_node2); Ref node_blend0; node_blend0.instantiate(); - node_blend0->name = "Blend0"; + node_blend0->set_name("Blend0"); tree_constructor.add_node(node_blend0); Ref node_blend1; node_blend1.instantiate(); - node_blend1->name = "Blend1"; + node_blend1->set_name("Blend1"); tree_constructor.add_node(node_blend1); // Tree @@ -201,7 +201,7 @@ TEST_CASE("[SyncedAnimationGraph] Test BlendTree construction") { CHECK(tree_constructor.node_connection_info[blend1_index].input_subtree_node_indices.has(blend0_index)); // Perform remaining connections - CHECK(BLTAnimationNodeBlendTree::CONNECTION_OK == tree_constructor.add_connection(node_blend1, tree_constructor.get_output_node(), "Input")); + CHECK(BLTAnimationNodeBlendTree::CONNECTION_OK == tree_constructor.add_connection(node_blend1, tree_constructor.get_output_node(), "Output")); CHECK(BLTAnimationNodeBlendTree::CONNECTION_OK == tree_constructor.add_connection(animation_sampler_node2, node_blend1, "Input1")); // Output node must have all nodes in its subtree: @@ -287,7 +287,7 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph animation_sampler_node->animation_name = "animation_library/TestAnimationA"; synced_blend_tree_node->add_node(animation_sampler_node); - REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == synced_blend_tree_node->add_connection(animation_sampler_node, synced_blend_tree_node->get_output_node(), "Input")); + REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == synced_blend_tree_node->add_connection(animation_sampler_node, synced_blend_tree_node->get_output_node(), "Output")); synced_blend_tree_node->initialize(synced_animation_graph->get_context()); @@ -329,18 +329,17 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph // Blend2 Ref blend2_node; blend2_node.instantiate(); - blend2_node->name = "Blend2"; + blend2_node->set_name("Blend2"); blend2_node->blend_weight = 0.5; blend2_node->sync = false; synced_blend_tree_node->add_node(blend2_node); // Connect nodes - Vector blend2_inputs; - blend2_node->get_input_names(blend2_inputs); + Vector blend2_inputs = blend2_node->get_input_names(); REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == synced_blend_tree_node->add_connection(animation_sampler_node_a, blend2_node, blend2_inputs[0])); REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == synced_blend_tree_node->add_connection(animation_sampler_node_b, blend2_node, blend2_inputs[1])); - REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == synced_blend_tree_node->add_connection(blend2_node, synced_blend_tree_node->get_output_node(), "Input")); + REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == synced_blend_tree_node->add_connection(blend2_node, synced_blend_tree_node->get_output_node(), "Output")); synced_blend_tree_node->initialize(synced_animation_graph->get_context()); @@ -437,7 +436,7 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph Ref loaded_synced_blend_tree = ResourceLoader::load("synced_blend_tree_node.tres"); REQUIRE(loaded_synced_blend_tree.is_valid()); - Ref loaded_blend2_node = loaded_synced_blend_tree->get_node(loaded_synced_blend_tree->find_node_index_by_name("Blend2")); + Ref loaded_blend2_node = loaded_synced_blend_tree->get_node_by_index(loaded_synced_blend_tree->find_node_index_by_name("Blend2")); REQUIRE(loaded_blend2_node.is_valid()); CHECK(loaded_blend2_node->sync == false); CHECK(loaded_blend2_node->blend_weight == blend2_node->blend_weight);