动画存在问题

This commit is contained in:
Hector 2026-03-25 08:56:20 +08:00
parent 4723bd9746
commit 90239b7051
3 changed files with 207 additions and 26 deletions

View File

@ -31,21 +31,21 @@ DockId=0x0000000D,0
[Window][场景树] [Window][场景树]
Pos=0,20 Pos=0,20
Size=339,1084 Size=339,611
Collapsed=0 Collapsed=0
DockId=0x00000007,0 DockId=0x00000003,0
[Window][属性面板] [Window][属性面板]
Pos=1694,20 Pos=1574,20
Size=346,1084 Size=346,1012
Collapsed=0 Collapsed=0
DockId=0x00000002,0 DockId=0x00000002,0
[Window][控制台] [Window][控制台]
Pos=341,705 Pos=0,633
Size=1351,399 Size=339,399
Collapsed=0 Collapsed=0
DockId=0x00000006,1 DockId=0x00000004,0
[Window][脚本管理] [Window][脚本管理]
Pos=1950,20 Pos=1950,20
@ -59,7 +59,7 @@ Collapsed=0
[Window][WindowOverViewport_11111111] [Window][WindowOverViewport_11111111]
Pos=0,20 Pos=0,20
Size=2040,1084 Size=1920,1012
Collapsed=0 Collapsed=0
[Window][测试窗口1] [Window][测试窗口1]
@ -98,8 +98,8 @@ Size=600,500
Collapsed=0 Collapsed=0
[Window][资源管理器] [Window][资源管理器]
Pos=341,705 Pos=341,633
Size=1351,399 Size=1231,399
Collapsed=0 Collapsed=0
DockId=0x00000006,0 DockId=0x00000006,0
@ -226,9 +226,11 @@ Size=460,260
Collapsed=0 Collapsed=0
[Docking][Data] [Docking][Data]
DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=2040,1084 Split=X DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=1920,1012 Split=X
DockNode ID=0x00000001 Parent=0x08BD597D SizeRef=2212,1012 Split=X DockNode ID=0x00000001 Parent=0x08BD597D SizeRef=2212,1012 Split=X
DockNode ID=0x00000007 Parent=0x00000001 SizeRef=339,1084 Selected=0xE0015051 DockNode ID=0x00000007 Parent=0x00000001 SizeRef=339,1084 Split=Y Selected=0xE0015051
DockNode ID=0x00000003 Parent=0x00000007 SizeRef=339,611 Selected=0xE0015051
DockNode ID=0x00000004 Parent=0x00000007 SizeRef=339,399 Selected=0x5428E753
DockNode ID=0x00000008 Parent=0x00000001 SizeRef=1871,1084 Split=Y DockNode ID=0x00000008 Parent=0x00000001 SizeRef=1871,1084 Split=Y
DockNode ID=0x00000005 Parent=0x00000008 SizeRef=2048,683 Split=Y DockNode ID=0x00000005 Parent=0x00000008 SizeRef=2048,683 Split=Y
DockNode ID=0x0000000D Parent=0x00000005 SizeRef=1318,383 HiddenTabBar=1 Selected=0x43A39006 DockNode ID=0x0000000D Parent=0x00000005 SizeRef=1318,383 HiddenTabBar=1 Selected=0x43A39006

View File

@ -686,6 +686,101 @@ class AnimationTools:
except Exception: except Exception:
return None return None
def _find_animation_driver_node(self, node):
"""Find the concrete node that should drive animation playback."""
try:
if not node or node.isEmpty():
return None
cached_driver = node.getPythonTag("animation_driver_node")
if cached_driver and (not cached_driver.isEmpty()):
return cached_driver
except Exception:
pass
try:
candidates = []
def add_candidate(candidate):
try:
if not candidate or candidate.isEmpty():
return
for existing in candidates:
if existing == candidate:
return
candidates.append(candidate)
except Exception:
return
add_candidate(node)
current = node
for _ in range(32):
if not current or current.isEmpty() or self._is_scene_root_node(current):
break
add_candidate(current)
try:
for child in current.getChildren():
add_candidate(child)
except Exception:
pass
parent = current.getParent()
if not parent or parent.isEmpty() or parent == current:
break
current = parent
best = None
best_score = -1
for candidate in candidates:
try:
has_anim = self._node_has_animation_nodes(candidate)
if not has_anim:
continue
has_geom = self._node_has_geom(candidate)
character_count = candidate.findAllMatches("**/+Character").getNumPaths()
bundle_count = candidate.findAllMatches("**/+AnimBundleNode").getNumPaths()
is_character = character_count > 0
is_bundle = bundle_count > 0
is_same_node = candidate == node
is_descendant = False
is_ancestor = False
try:
if candidate != node:
is_descendant = node.isAncestorOf(candidate)
is_ancestor = candidate.isAncestorOf(node)
except Exception:
is_descendant = False
is_ancestor = False
score = 0
score += 140 if has_anim and has_geom else 0
score += 90 if has_geom else 0
score += 80 if is_character else 0
score += 40 if is_bundle else 0
score += 35 if is_descendant else 0
score += 15 if is_same_node else 0
score -= 20 if is_ancestor else 0
score += character_count * 5
score += bundle_count * 3
if score > best_score:
best_score = score
best = candidate
except Exception:
continue
if best:
try:
node.setPythonTag("animation_driver_node", best)
except Exception:
pass
try:
best.setPythonTag("animation_driver_node", best)
except Exception:
pass
return best
except Exception:
pass
return None
def _recover_model_path_from_tags(self, node): def _recover_model_path_from_tags(self, node):
"""从常见标签恢复模型路径,尽量避免退化到纯内存 autoBind。""" """从常见标签恢复模型路径,尽量避免退化到纯内存 autoBind。"""
try: try:
@ -905,6 +1000,16 @@ class AnimationTools:
def _resolve_animation_owner_model(self, node): def _resolve_animation_owner_model(self, node):
"""向上查找动画所属的模型根节点,避免选中子节点时绑定失败。""" """向上查找动画所属的模型根节点,避免选中子节点时绑定失败。"""
try:
if node and (not node.isEmpty()):
# If the user explicitly selected the animated skeleton node,
# respect that directly. More aggressive driver remapping made
# playback regress to "cannot play at all".
if self._node_has_animation_nodes(node):
return node
except Exception:
pass
ssbo_owner = self._resolve_ssbo_source_owner(node) ssbo_owner = self._resolve_ssbo_source_owner(node)
if ssbo_owner and not ssbo_owner.isEmpty(): if ssbo_owner and not ssbo_owner.isEmpty():
try: try:
@ -1431,6 +1536,13 @@ class AnimationTools:
except Exception: except Exception:
has_animation_nodes = False has_animation_nodes = False
filepath = owner_model.getTag("model_path") if owner_model.hasTag("model_path") else ""
if not filepath and owner_model.hasTag("original_path"):
filepath = owner_model.getTag("original_path")
lower_filepath = str(filepath).lower() if filepath else ""
is_gltf_family = lower_filepath.endswith(".glb") or lower_filepath.endswith(".gltf")
def _try_memory_fallback(): def _try_memory_fallback():
def _collect_autobind_source_candidates(): def _collect_autobind_source_candidates():
candidates = [] candidates = []
@ -1554,6 +1666,34 @@ class AnimationTools:
return mem_proxy return mem_proxy
return None return None
if is_gltf_family and has_animation_nodes:
# For imported GLTF/GLB, prefer binding controls directly onto the
# visible scene model. A separate runtime Armature proxy can report
# playing=True while not driving the visible mesh the user selected.
direct_proxy = _try_create_autobind_proxy(
owner_model,
f"当前模型({owner_model.getName()})",
owns_node=False,
)
if direct_proxy:
self._mark_runtime_animation_node(direct_proxy, owner_model)
self._actor_cache[owner_model] = direct_proxy
owner_model.setTag("has_animations", "true")
return direct_proxy
if filepath:
actor = _try_create_actor_via_gltf_path(filepath)
if actor:
owner_model.setTag("model_path", filepath)
owner_model.setTag("has_animations", "true")
self._mark_runtime_animation_node(actor, owner_model)
self._actor_cache[owner_model] = actor
return actor
actor = _try_memory_fallback()
if actor:
return actor
# 始终优先尝试从文件路径加载,因为底层 gltf 插件只有在加载文件时才能抽取完整的动画名称。 # 始终优先尝试从文件路径加载,因为底层 gltf 插件只有在加载文件时才能抽取完整的动画名称。
self._anim_log(f"[Actor加载调试] 传入的 origin_model: {origin_model.getName() if origin_model else 'None'}") self._anim_log(f"[Actor加载调试] 传入的 origin_model: {origin_model.getName() if origin_model else 'None'}")
self._anim_log(f"[Actor加载调试] origin_model tags: {origin_model.getTags() if origin_model else 'None'}") self._anim_log(f"[Actor加载调试] origin_model tags: {origin_model.getTags() if origin_model else 'None'}")
@ -1564,10 +1704,6 @@ class AnimationTools:
except Exception as e: except Exception as e:
self._anim_log(f"[Actor加载调试] 获取 tags 异常: {e}") self._anim_log(f"[Actor加载调试] 获取 tags 异常: {e}")
filepath = owner_model.getTag("model_path") if owner_model.hasTag("model_path") else ""
if not filepath and owner_model.hasTag("original_path"):
filepath = owner_model.getTag("original_path")
self._anim_log(f"[Actor加载调试] 获取到的 filepath: '{filepath}'") self._anim_log(f"[Actor加载调试] 获取到的 filepath: '{filepath}'")
if not filepath: if not filepath:
self._anim_log(f"[Actor加载调试] filepath为空触发 _try_memory_fallback()") self._anim_log(f"[Actor加载调试] filepath为空触发 _try_memory_fallback()")
@ -1622,14 +1758,24 @@ class AnimationTools:
for p in load_paths: for p in load_paths:
self._anim_log(f"[Actor加载验证] 正在尝试通过路径读取骨骼和动画文件: {p}") self._anim_log(f"[Actor加载验证] 正在尝试通过路径读取骨骼和动画文件: {p}")
actor = _try_create_actor_from_source(p, f"文件路径({p})") lower_p = str(p).lower()
if actor:
owner_model.setTag("model_path", p) if lower_p.endswith(".glb") or lower_p.endswith(".gltf"):
owner_model.setTag("has_animations", "true") actor = _try_create_actor_via_gltf_path(p)
self._actor_cache[owner_model] = actor if actor:
return actor owner_model.setTag("model_path", p)
owner_model.setTag("has_animations", "true")
self._mark_runtime_animation_node(actor, owner_model)
self._actor_cache[owner_model] = actor
return actor
else:
actor = _try_create_actor_from_source(p, f"文件路径({p})")
if actor:
owner_model.setTag("model_path", p)
owner_model.setTag("has_animations", "true")
self._actor_cache[owner_model] = actor
return actor
# 标准 Actor 路径失败时,针对 GLTF/GLB 走插件加载兜底
actor = _try_create_actor_via_gltf_path(p) actor = _try_create_actor_via_gltf_path(p)
if actor: if actor:
owner_model.setTag("model_path", p) owner_model.setTag("model_path", p)
@ -1638,7 +1784,9 @@ class AnimationTools:
self._actor_cache[owner_model] = actor self._actor_cache[owner_model] = actor
return actor return actor
# 路径 Actor 失败后,再尝试把文件作为普通模型加载并 autoBind if is_gltf_family:
continue
try: try:
model_source = p model_source = p
if isinstance(p, (str, os.PathLike)): if isinstance(p, (str, os.PathLike)):
@ -1831,6 +1979,8 @@ class AnimationTools:
pass pass
valid_names = [] valid_names = []
best_name = None
best_frames = -1
for anim_name in anim_names: for anim_name in anim_names:
try: try:
control = actor.getAnimControl(anim_name) control = actor.getAnimControl(anim_name)
@ -1838,12 +1988,17 @@ class AnimationTools:
control = None control = None
if not control: if not control:
continue continue
frame_count = -1
try: try:
if control.getNumFrames() <= 1: frame_count = control.getNumFrames()
if frame_count <= 1:
continue continue
except Exception: except Exception:
pass pass
valid_names.append(anim_name) valid_names.append(anim_name)
if frame_count > best_frames:
best_frames = frame_count
best_name = anim_name
if not valid_names: if not valid_names:
valid_names = list(anim_names) valid_names = list(anim_names)
@ -1855,7 +2010,7 @@ class AnimationTools:
break break
if current_anim not in valid_names: if current_anim not in valid_names:
current_anim = valid_names[0] current_anim = best_name or valid_names[0]
try: try:
origin_model.setPythonTag("selected_animation", current_anim) origin_model.setPythonTag("selected_animation", current_anim)
@ -2078,6 +2233,18 @@ class AnimationTools:
is_scene_bound_proxy = self._sync_actor_transform_for_playback(owner_model, actor) is_scene_bound_proxy = self._sync_actor_transform_for_playback(owner_model, actor)
debug_actor_name = getattr(actor, "getName", None)
try:
debug_actor_name = debug_actor_name() if callable(debug_actor_name) else getattr(getattr(actor, "_node", None), "getName", lambda: "Unknown")()
except Exception:
debug_actor_name = "Unknown"
debug_frames = -1
try:
debug_frames = control.getNumFrames()
except Exception:
pass
if self._can_swap_actor_owner_visibility(owner_model, actor): if self._can_swap_actor_owner_visibility(owner_model, actor):
self._ensure_owner_hidden_lock_task(owner_model, enabled=True) self._ensure_owner_hidden_lock_task(owner_model, enabled=True)
self._apply_actor_owner_visibility(owner_model, actor, prefer_actor_visible=True) self._apply_actor_owner_visibility(owner_model, actor, prefer_actor_visible=True)
@ -2093,6 +2260,11 @@ class AnimationTools:
except Exception: except Exception:
pass pass
actor.play(current_anim) actor.play(current_anim)
try:
is_playing = control.isPlaying()
except Exception:
is_playing = "unknown"
print(f"[动画调试] owner={owner_model.getName()} actor={debug_actor_name} anim={current_anim} frames={debug_frames} playing={is_playing} scene_proxy={is_scene_bound_proxy}")
print(f"『动画播放』:{current_anim}") print(f"『动画播放』:{current_anim}")
def _pauseAnimation(self, origin_model): def _pauseAnimation(self, origin_model):

View File

@ -1541,6 +1541,8 @@ class AppActions:
Legacy mode: load via SceneManager. Legacy mode: load via SceneManager.
""" """
animated_model = bool(file_path and self._model_file_has_animation(file_path)) animated_model = bool(file_path and self._model_file_has_animation(file_path))
lower_path = str(file_path).lower() if file_path else ""
is_gltf_family = lower_path.endswith((".glb", ".gltf"))
if animated_model: if animated_model:
prefer_scene_manager = True prefer_scene_manager = True
ssbo_editor = getattr(self, 'ssbo_editor', None) ssbo_editor = getattr(self, 'ssbo_editor', None)
@ -1550,6 +1552,11 @@ class AppActions:
except Exception: except Exception:
pass pass
if is_gltf_family:
fallback_model = self._import_model_via_gltf_fallback(file_path)
if fallback_model:
return fallback_model
if self.use_ssbo_mouse_picking and getattr(self, 'ssbo_editor', None): if self.use_ssbo_mouse_picking and getattr(self, 'ssbo_editor', None):
if animated_model: if animated_model:
print(f"[AnimationImport] 检测到动画模型跳过SSBO导入: {file_path}") print(f"[AnimationImport] 检测到动画模型跳过SSBO导入: {file_path}")