动画存在问题

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

View File

@ -686,6 +686,101 @@ class AnimationTools:
except Exception:
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):
"""从常见标签恢复模型路径,尽量避免退化到纯内存 autoBind。"""
try:
@ -905,6 +1000,16 @@ class AnimationTools:
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)
if ssbo_owner and not ssbo_owner.isEmpty():
try:
@ -1431,6 +1536,13 @@ class AnimationTools:
except Exception:
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 _collect_autobind_source_candidates():
candidates = []
@ -1554,6 +1666,34 @@ class AnimationTools:
return mem_proxy
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 插件只有在加载文件时才能抽取完整的动画名称。
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'}")
@ -1564,10 +1704,6 @@ class AnimationTools:
except Exception as 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}'")
if not filepath:
self._anim_log(f"[Actor加载调试] filepath为空触发 _try_memory_fallback()")
@ -1622,14 +1758,24 @@ class AnimationTools:
for p in load_paths:
self._anim_log(f"[Actor加载验证] 正在尝试通过路径读取骨骼和动画文件: {p}")
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
lower_p = str(p).lower()
if lower_p.endswith(".glb") or lower_p.endswith(".gltf"):
actor = _try_create_actor_via_gltf_path(p)
if 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)
if actor:
owner_model.setTag("model_path", p)
@ -1638,7 +1784,9 @@ class AnimationTools:
self._actor_cache[owner_model] = actor
return actor
# 路径 Actor 失败后,再尝试把文件作为普通模型加载并 autoBind
if is_gltf_family:
continue
try:
model_source = p
if isinstance(p, (str, os.PathLike)):
@ -1831,6 +1979,8 @@ class AnimationTools:
pass
valid_names = []
best_name = None
best_frames = -1
for anim_name in anim_names:
try:
control = actor.getAnimControl(anim_name)
@ -1838,12 +1988,17 @@ class AnimationTools:
control = None
if not control:
continue
frame_count = -1
try:
if control.getNumFrames() <= 1:
frame_count = control.getNumFrames()
if frame_count <= 1:
continue
except Exception:
pass
valid_names.append(anim_name)
if frame_count > best_frames:
best_frames = frame_count
best_name = anim_name
if not valid_names:
valid_names = list(anim_names)
@ -1855,7 +2010,7 @@ class AnimationTools:
break
if current_anim not in valid_names:
current_anim = valid_names[0]
current_anim = best_name or valid_names[0]
try:
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)
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):
self._ensure_owner_hidden_lock_task(owner_model, enabled=True)
self._apply_actor_owner_visibility(owner_model, actor, prefer_actor_visible=True)
@ -2093,6 +2260,11 @@ class AnimationTools:
except Exception:
pass
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}")
def _pauseAnimation(self, origin_model):

View File

@ -1541,6 +1541,8 @@ class AppActions:
Legacy mode: load via SceneManager.
"""
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:
prefer_scene_manager = True
ssbo_editor = getattr(self, 'ssbo_editor', None)
@ -1550,6 +1552,11 @@ class AppActions:
except Exception:
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 animated_model:
print(f"[AnimationImport] 检测到动画模型跳过SSBO导入: {file_path}")