Files
engine/modules/agent_fbx/agent_fbx_importer.cpp
ozan d291dcdc74 feat: 9 agentic engine modules for agent-native Godot
agent_api (HTTP server), agent_log (structured logging), agent_events (event bus),
agent_console (GameConsole), agent_replay (snapshots), agent_vision (depth/segmentation),
agent_fbx (bone remapping), agent_auth (multi-agent), agent_analytics (feature flags + tracking)

All modules compile clean with mono. Binary uploaded to S3 v1.0.0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-15 03:44:28 +01:00

112 lines
3.3 KiB
C++

#include "agent_fbx_importer.h"
#include "core/io/file_access.h"
#include "core/io/json.h"
#include "core/string/print_string.h"
AgentFBXImporter *AgentFBXImporter::singleton = nullptr;
AgentFBXImporter::AgentFBXImporter() {
singleton = this;
_load_bone_map();
}
AgentFBXImporter::~AgentFBXImporter() {
singleton = nullptr;
}
void AgentFBXImporter::_bind_methods() {
ClassDB::bind_method(D_METHOD("load_bone_map", "path"), &AgentFBXImporter::load_bone_map, DEFVAL("res://bone_map.json"));
ClassDB::bind_method(D_METHOD("remap_bone", "original_name"), &AgentFBXImporter::remap_bone);
ClassDB::bind_method(D_METHOD("get_bone_map"), &AgentFBXImporter::get_bone_map);
ClassDB::bind_method(D_METHOD("has_mapping", "bone_name"), &AgentFBXImporter::has_mapping);
ClassDB::bind_method(D_METHOD("set_mapping", "from", "to"), &AgentFBXImporter::set_mapping);
ClassDB::bind_method(D_METHOD("remove_mapping", "from"), &AgentFBXImporter::remove_mapping);
ClassDB::bind_method(D_METHOD("save_bone_map", "path"), &AgentFBXImporter::save_bone_map, DEFVAL("res://bone_map.json"));
ClassDB::bind_method(D_METHOD("is_loaded"), &AgentFBXImporter::is_loaded);
}
void AgentFBXImporter::_load_bone_map() {
load_bone_map("res://bone_map.json");
}
bool AgentFBXImporter::load_bone_map(const String &p_path) {
if (!FileAccess::exists(p_path)) {
// No bone map file — not an error, just means no remapping.
bone_map_loaded = false;
return false;
}
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::READ);
if (file.is_null()) {
ERR_PRINT(vformat("AgentFBXImporter: Failed to open '%s'", p_path));
return false;
}
String content = file->get_as_text();
Variant parsed = JSON::parse_string(content);
if (parsed.get_type() != Variant::DICTIONARY) {
ERR_PRINT(vformat("AgentFBXImporter: Failed to parse '%s' as JSON dictionary", p_path));
return false;
}
bone_map = parsed;
bone_map_loaded = true;
print_line(vformat("AgentFBXImporter: Loaded %d bone mappings from '%s'", bone_map.size(), p_path));
return true;
}
String AgentFBXImporter::remap_bone(const String &p_original_name) const {
if (!bone_map_loaded) {
return p_original_name;
}
if (bone_map.has(p_original_name)) {
return bone_map[p_original_name];
}
// Case-insensitive fallback.
for (const Variant &key : bone_map.keys()) {
if (String(key).to_lower() == p_original_name.to_lower()) {
return bone_map[key];
}
}
return p_original_name;
}
bool AgentFBXImporter::has_mapping(const String &p_bone_name) const {
if (bone_map.has(p_bone_name)) {
return true;
}
// Case-insensitive check.
for (const Variant &key : bone_map.keys()) {
if (String(key).to_lower() == p_bone_name.to_lower()) {
return true;
}
}
return false;
}
void AgentFBXImporter::set_mapping(const String &p_from, const String &p_to) {
bone_map[p_from] = p_to;
bone_map_loaded = true;
}
void AgentFBXImporter::remove_mapping(const String &p_from) {
bone_map.erase(p_from);
}
bool AgentFBXImporter::save_bone_map(const String &p_path) {
Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE);
if (file.is_null()) {
ERR_PRINT(vformat("AgentFBXImporter: Failed to write '%s'", p_path));
return false;
}
file->store_string(JSON::stringify(bone_map, "\t"));
print_line(vformat("AgentFBXImporter: Saved %d bone mappings to '%s'", bone_map.size(), p_path));
return true;
}