fix(import): skeleton rest fixer animation conversion in headless mode

The %GeneralSkeleton unique node resolution via get_node() fails during
headless import (--import flag), causing ALL animation tracks to be
silently skipped by ERR_CONTINUE. Rest poses get overwritten but animation
keyframe values stay in the old coordinate system, producing incorrect
bone orientations (e.g., arms crossed behind back).

Fix: fall back to find_child() when unique name resolution fails.
Added extensive logging for retarget debugging.

Tinqs fork v1.0.1

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 20:01:15 +01:00
parent 001aa128b1
commit 4e16c3f5bd
@@ -730,13 +730,34 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
} }
String track_path = String(anim->track_get_path(i).get_concatenated_names()); String track_path = String(anim->track_get_path(i).get_concatenated_names());
Node *node = (ap->get_node(ap->get_root_node()))->get_node(NodePath(track_path));
ERR_CONTINUE(!node); // [TINQS] Fix: %UniqueNode resolution fails in headless/import context.
// Fall back to find_child when get_node fails (e.g., headless --import).
Node *node = nullptr;
Node *anim_root = ap->get_node_or_null(ap->get_root_node());
if (anim_root) {
node = anim_root->get_node_or_null(NodePath(track_path));
}
if (!node && track_path.begins_with(UNIQUE_NODE_PREFIX)) {
// Unique name resolution failed — try direct child search.
String skel_name = track_path.substr(1); // Strip % prefix.
node = p_base_scene->find_child(skel_name, true, false);
if (node) {
print_verbose(vformat("[RetargetRestFixer] Resolved '%s' via find_child fallback -> '%s'", track_path, node->get_name()));
} else {
WARN_PRINT(vformat("[RetargetRestFixer] Failed to resolve skeleton node '%s' — animation keyframes will NOT be converted to new rest pose. This may cause incorrect bone orientations.", track_path));
}
}
if (!node) {
WARN_PRINT(vformat("[RetargetRestFixer] Skipping track %d: cannot resolve node path '%s' from AnimationPlayer root.", i, track_path));
continue;
}
Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node); Skeleton3D *track_skeleton = Object::cast_to<Skeleton3D>(node);
if (!track_skeleton || if (!track_skeleton ||
(is_using_modifier && track_skeleton != profile_skeleton && track_skeleton != orig_skeleton) || (is_using_modifier && track_skeleton != profile_skeleton && track_skeleton != orig_skeleton) ||
(!is_using_modifier && track_skeleton != src_skeleton)) { (!is_using_modifier && track_skeleton != src_skeleton)) {
print_verbose(vformat("[RetargetRestFixer] Skipping track %d: skeleton mismatch (found '%s', expected src_skeleton).", i, node->get_name()));
continue; continue;
} }
@@ -746,6 +767,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
} }
int bone_idx = src_skeleton->find_bone(bn); int bone_idx = src_skeleton->find_bone(bn);
print_verbose(vformat("[RetargetRestFixer] Converting track %d bone '%s' (idx=%d) — %d keys", i, bn, bone_idx, anim->track_get_key_count(i)));
if (is_using_modifier) { if (is_using_modifier) {
int prof_idx = profile->find_bone(bn); int prof_idx = profile->find_bone(bn);