SSBO加载模型提高帧率,解决动画播放BUG
This commit is contained in:
parent
d609cb5f39
commit
46fa1756a1
@ -2089,6 +2089,13 @@ class SelectionSystem:
|
||||
node_name = nodePath.getName()
|
||||
#print(f"新选择的节点: {node_name}")
|
||||
|
||||
animation_tools = getattr(self.world, "animation_tools", None)
|
||||
if animation_tools and hasattr(animation_tools, "_stop_all_active_animations"):
|
||||
try:
|
||||
animation_tools._stop_all_active_animations()
|
||||
except Exception as e:
|
||||
print(f"停止活动动画失败: {e}")
|
||||
|
||||
ssbo_editor = getattr(self.world, "ssbo_editor", None)
|
||||
if ssbo_editor:
|
||||
try:
|
||||
|
||||
@ -551,6 +551,7 @@ class ObjectController:
|
||||
# Even medium flattening can still collapse or rewrite per-object PBR/effect
|
||||
# state after save/load, which manifests as black materials until selection
|
||||
# switches back to the dynamic objects.
|
||||
static_np.flatten_strong()
|
||||
|
||||
chunk["static_np"] = static_np
|
||||
chunk["dirty"] = False
|
||||
|
||||
@ -1033,6 +1033,14 @@ class SSBOEditor:
|
||||
self._set_node_name(source_model, unique_root_name)
|
||||
imported_root = source_model.copyTo(source_root)
|
||||
self._set_node_name(imported_root, unique_root_name)
|
||||
try:
|
||||
imported_root.setTag("model_path", model_path)
|
||||
imported_root.setTag("original_path", model_path)
|
||||
imported_root.setTag("saved_model_path", model_path)
|
||||
imported_root.setTag("is_model_root", "1")
|
||||
imported_root.setTag("tree_item_type", "IMPORTED_MODEL_NODE")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if keep_source_model and not append:
|
||||
self.source_model = imported_root
|
||||
@ -2110,6 +2118,35 @@ class SSBOEditor:
|
||||
"is_group": len(self.selected_ids) > 1 and not self._is_root_selection(),
|
||||
}
|
||||
|
||||
def estimate_selection_cost(self, key):
|
||||
"""Estimate how expensive it is to activate a tree node in dynamic mode."""
|
||||
if not self.controller or key is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
object_ids = list(self.controller.name_to_ids.get(key, []))
|
||||
except Exception:
|
||||
object_ids = []
|
||||
|
||||
chunk_ids = set()
|
||||
for gid in object_ids:
|
||||
try:
|
||||
chunk_id = self.controller.id_to_chunk.get(gid)
|
||||
except Exception:
|
||||
chunk_id = None
|
||||
if chunk_id is not None:
|
||||
chunk_ids.add(chunk_id)
|
||||
|
||||
return {
|
||||
"key": key,
|
||||
"object_count": len(object_ids),
|
||||
"chunk_count": len(chunk_ids),
|
||||
"is_root": bool(
|
||||
self.controller and
|
||||
key == getattr(self.controller, "tree_root_key", None)
|
||||
),
|
||||
}
|
||||
|
||||
def _sync_editor_selection_reference(self, node):
|
||||
selection = getattr(self.base, "selection", None)
|
||||
if not selection:
|
||||
|
||||
@ -339,6 +339,7 @@ class AnimationTools:
|
||||
if not related_found and node_name:
|
||||
fallback_best = None
|
||||
fallback_score = -1
|
||||
fallback_count = 0
|
||||
for model in list(models):
|
||||
try:
|
||||
if not model or model.isEmpty() or self._is_scene_root_node(model):
|
||||
@ -358,12 +359,13 @@ class AnimationTools:
|
||||
score += 80 if has_anim and has_geom else 0
|
||||
score += 40 if has_model_path else 0
|
||||
score += 25 if has_saved_path else 0
|
||||
fallback_count += 1
|
||||
if score > fallback_score:
|
||||
fallback_score = score
|
||||
fallback_best = model
|
||||
except Exception:
|
||||
continue
|
||||
if fallback_best:
|
||||
if fallback_best and fallback_count == 1:
|
||||
return fallback_best
|
||||
|
||||
# 单模型场景兜底
|
||||
@ -549,12 +551,214 @@ class AnimationTools:
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def _resolve_ssbo_source_owner(self, node):
|
||||
"""将 SSBO 运行时选择节点映射回 source_model_root 中的真实模型根。"""
|
||||
try:
|
||||
ssbo_editor = getattr(self, "ssbo_editor", None)
|
||||
if not ssbo_editor:
|
||||
return None
|
||||
|
||||
source_root = getattr(ssbo_editor, "source_model_root", None)
|
||||
if not source_root:
|
||||
return None
|
||||
try:
|
||||
if source_root.isEmpty():
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
selection_key = None
|
||||
try:
|
||||
if node and (not node.isEmpty()) and node.hasTag("ssbo_selection_key"):
|
||||
selection_key = node.getTag("ssbo_selection_key")
|
||||
except Exception:
|
||||
selection_key = None
|
||||
|
||||
if not selection_key:
|
||||
try:
|
||||
selection_key = ssbo_editor._find_tree_key_for_scene_node(node)
|
||||
except Exception:
|
||||
selection_key = None
|
||||
|
||||
if not selection_key:
|
||||
selection_key = getattr(ssbo_editor, "selected_name", None)
|
||||
|
||||
if not selection_key:
|
||||
return None
|
||||
|
||||
try:
|
||||
source_owner = ssbo_editor._resolve_source_node_by_tree_key(selection_key)
|
||||
except Exception:
|
||||
source_owner = None
|
||||
|
||||
if not source_owner:
|
||||
return None
|
||||
try:
|
||||
if source_owner.isEmpty():
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
try:
|
||||
if node and (not node.isEmpty()):
|
||||
for tag_name in ("model_path", "original_path", "saved_model_path", "file"):
|
||||
if (
|
||||
(not source_owner.hasTag(tag_name) or not source_owner.getTag(tag_name))
|
||||
and node.hasTag(tag_name)
|
||||
and node.getTag(tag_name)
|
||||
):
|
||||
source_owner.setTag(tag_name, node.getTag(tag_name))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return source_owner
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _resolve_animation_owner_model(self, node):
|
||||
"""向上查找动画所属的模型根节点,避免选中子节点时绑定失败。"""
|
||||
ssbo_owner = self._resolve_ssbo_source_owner(node)
|
||||
if ssbo_owner and not ssbo_owner.isEmpty():
|
||||
try:
|
||||
if node and not node.isEmpty():
|
||||
for tag_name in ("model_path", "original_path", "saved_model_path", "file"):
|
||||
if (
|
||||
(not node.hasTag(tag_name) or not node.getTag(tag_name))
|
||||
and ssbo_owner.hasTag(tag_name)
|
||||
and ssbo_owner.getTag(tag_name)
|
||||
):
|
||||
node.setTag(tag_name, ssbo_owner.getTag(tag_name))
|
||||
try:
|
||||
node.setPythonTag("animation_source_owner", ssbo_owner)
|
||||
except Exception:
|
||||
pass
|
||||
return node
|
||||
except Exception:
|
||||
pass
|
||||
return ssbo_owner
|
||||
|
||||
scene_owner = self._find_scene_model_owner(node)
|
||||
chain_best = None
|
||||
try:
|
||||
current = node
|
||||
max_depth = 64
|
||||
chain = []
|
||||
for _ in range(max_depth):
|
||||
if not current or current.isEmpty():
|
||||
break
|
||||
# 绝不把场景根节点当作动画 owner,否则 hide() 会把全场景隐藏
|
||||
if self._is_scene_root_node(current):
|
||||
break
|
||||
|
||||
chain.append(current)
|
||||
parent = current.getParent()
|
||||
if not parent or parent.isEmpty() or parent == current:
|
||||
break
|
||||
current = parent
|
||||
|
||||
# 标签缺失/层级复杂时:按可见播放优先级打分选 owner(动画+几何体最高)
|
||||
if chain:
|
||||
path_source = None
|
||||
original_path_source = None
|
||||
|
||||
def score_candidate(c):
|
||||
try:
|
||||
has_character = c.findAllMatches("**/+Character").getNumPaths() > 0
|
||||
has_bundle = c.findAllMatches("**/+AnimBundleNode").getNumPaths() > 0
|
||||
has_anim = has_character or has_bundle
|
||||
has_geom = c.findAllMatches("**/+GeomNode").getNumPaths() > 0
|
||||
except Exception:
|
||||
has_anim = False
|
||||
has_geom = False
|
||||
|
||||
has_model_path = c.hasTag("model_path") and bool(c.getTag("model_path"))
|
||||
has_saved_path = c.hasTag("saved_model_path") and bool(c.getTag("saved_model_path"))
|
||||
has_original_path = c.hasTag("original_path") and bool(c.getTag("original_path"))
|
||||
is_model_root = c.hasTag("is_model_root") and c.getTag("is_model_root") == "1"
|
||||
is_imported_root = c.hasTag("tree_item_type") and c.getTag("tree_item_type") == "IMPORTED_MODEL_NODE"
|
||||
|
||||
# 分数越高越优先:保证“具体模型根”优先于聚合场景根
|
||||
score = 0
|
||||
score += 140 if has_anim and has_geom else 0
|
||||
score += 90 if has_model_path and has_geom else 0
|
||||
score += 55 if has_saved_path and has_geom else 0
|
||||
score += 45 if has_original_path and has_geom else 0
|
||||
score += 40 if has_anim else 0
|
||||
score += 20 if has_geom else 0
|
||||
score += 25 if has_model_path else 0
|
||||
score += 15 if has_saved_path else 0
|
||||
score += 12 if has_original_path else 0
|
||||
score += 10 if is_model_root else 0
|
||||
score += 8 if is_imported_root else 0
|
||||
return score
|
||||
|
||||
best = chain[0]
|
||||
best_score = -1
|
||||
for candidate in chain:
|
||||
try:
|
||||
if not path_source and candidate.hasTag("model_path") and candidate.getTag("model_path"):
|
||||
path_source = candidate
|
||||
if not original_path_source and candidate.hasTag("original_path") and candidate.getTag("original_path"):
|
||||
original_path_source = candidate
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
s = score_candidate(candidate)
|
||||
if s > best_score:
|
||||
best_score = s
|
||||
best = candidate
|
||||
|
||||
# 把路径标签补到最终 owner,避免后续格式识别为“未知”
|
||||
try:
|
||||
if path_source and ((not best.hasTag("model_path")) or (not best.getTag("model_path"))):
|
||||
best.setTag("model_path", path_source.getTag("model_path"))
|
||||
if original_path_source and ((not best.hasTag("original_path")) or (not best.getTag("original_path"))):
|
||||
best.setTag("original_path", original_path_source.getTag("original_path"))
|
||||
if best.hasTag("saved_model_path") and ((not best.hasTag("model_path")) or (not best.getTag("model_path"))):
|
||||
best.setTag("model_path", best.getTag("saved_model_path"))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
chain_best = best
|
||||
except Exception:
|
||||
chain_best = None
|
||||
|
||||
if scene_owner and not scene_owner.isEmpty():
|
||||
try:
|
||||
# SSBO 聚合场景下,scene_manager.models 常常只有总根节点。
|
||||
# 如果祖先链里存在更具体、带路径/动画/几何体的节点,应优先使用它,
|
||||
# 否则会退回到场景级内存代理,导致播放时原模型不隐藏、位置不同步。
|
||||
if chain_best and chain_best != scene_owner:
|
||||
scene_score = 0
|
||||
chain_score = 0
|
||||
for candidate, target in ((scene_owner, "scene"), (chain_best, "chain")):
|
||||
try:
|
||||
has_anim = (
|
||||
candidate.findAllMatches("**/+Character").getNumPaths() > 0 or
|
||||
candidate.findAllMatches("**/+AnimBundleNode").getNumPaths() > 0
|
||||
)
|
||||
has_geom = candidate.findAllMatches("**/+GeomNode").getNumPaths() > 0
|
||||
has_path = (
|
||||
(candidate.hasTag("model_path") and bool(candidate.getTag("model_path"))) or
|
||||
(candidate.hasTag("saved_model_path") and bool(candidate.getTag("saved_model_path"))) or
|
||||
(candidate.hasTag("original_path") and bool(candidate.getTag("original_path")))
|
||||
)
|
||||
score = 0
|
||||
score += 140 if has_anim and has_geom else 0
|
||||
score += 60 if has_path and has_geom else 0
|
||||
score += 25 if has_anim else 0
|
||||
score += 15 if has_geom else 0
|
||||
score += 20 if has_path else 0
|
||||
except Exception:
|
||||
score = 0
|
||||
if target == "scene":
|
||||
scene_score = score
|
||||
else:
|
||||
chain_score = score
|
||||
if chain_score > scene_score:
|
||||
scene_owner = chain_best
|
||||
|
||||
# 同步路径标签,避免后续格式识别为“未知”
|
||||
if scene_owner.hasTag("model_path") and scene_owner.getTag("model_path"):
|
||||
if (not node.hasTag("model_path")) or (not node.getTag("model_path")):
|
||||
@ -1317,6 +1521,146 @@ class AnimationTools:
|
||||
|
||||
return current_anim
|
||||
|
||||
def _cleanup_conflicting_animation_actors(self, owner_model):
|
||||
"""Remove stale cached actors that belong to the same logical model."""
|
||||
try:
|
||||
owner_path_candidates = set()
|
||||
for tag_name in ("model_path", "original_path", "saved_model_path"):
|
||||
try:
|
||||
if owner_model.hasTag(tag_name):
|
||||
value = owner_model.getTag(tag_name)
|
||||
if value:
|
||||
owner_path_candidates.add(os.path.normcase(os.path.normpath(value)))
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
for cached_owner, actor in list(self._actor_cache.items()):
|
||||
if cached_owner == owner_model:
|
||||
continue
|
||||
|
||||
related = False
|
||||
try:
|
||||
related = (
|
||||
cached_owner == owner_model or
|
||||
cached_owner.isAncestorOf(owner_model) or
|
||||
owner_model.isAncestorOf(cached_owner)
|
||||
)
|
||||
except Exception:
|
||||
related = False
|
||||
|
||||
if (not related) and owner_path_candidates:
|
||||
for tag_name in ("model_path", "original_path", "saved_model_path"):
|
||||
try:
|
||||
if not cached_owner.hasTag(tag_name):
|
||||
continue
|
||||
value = cached_owner.getTag(tag_name)
|
||||
if not value:
|
||||
continue
|
||||
if os.path.normcase(os.path.normpath(value)) in owner_path_candidates:
|
||||
related = True
|
||||
break
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if not related:
|
||||
continue
|
||||
|
||||
try:
|
||||
taskMgr.remove(f"maintain_anim_pos_{id(actor)}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
if self._should_swap_visibility_for_actor(cached_owner, actor):
|
||||
cached_owner.show()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
actor.hide()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
if hasattr(actor, "cleanup"):
|
||||
actor.cleanup()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
if hasattr(actor, "removeNode"):
|
||||
actor.removeNode()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
del self._actor_cache[cached_owner]
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _stop_all_active_animations(self, except_owner=None):
|
||||
"""Destroy all cached animation actors except the optional owner."""
|
||||
try:
|
||||
for cached_owner in list(self._actor_cache.keys()):
|
||||
try:
|
||||
if except_owner is not None and cached_owner == except_owner:
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
self._discard_cached_actor(cached_owner, restore_owner=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _discard_cached_actor(self, owner_model, restore_owner=True):
|
||||
"""Destroy one owner's cached actor before rebuilding it."""
|
||||
try:
|
||||
actor = self._actor_cache.get(owner_model)
|
||||
if not actor:
|
||||
return
|
||||
|
||||
try:
|
||||
actor.stop()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
taskMgr.remove(f"maintain_anim_pos_{id(actor)}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
actor.hide()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if restore_owner:
|
||||
try:
|
||||
if owner_model and not owner_model.isEmpty():
|
||||
owner_model.show()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
if hasattr(actor, "cleanup"):
|
||||
actor.cleanup()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
if hasattr(actor, "removeNode"):
|
||||
actor.removeNode()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
del self._actor_cache[owner_model]
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _should_swap_visibility_for_actor(self, owner_model, actor):
|
||||
"""
|
||||
是否需要“隐藏原模型/显示Actor”的切换。
|
||||
@ -1350,6 +1694,9 @@ class AnimationTools:
|
||||
pass
|
||||
|
||||
owner_model = self._resolve_animation_owner_model(origin_model)
|
||||
self._stop_all_active_animations()
|
||||
self._cleanup_conflicting_animation_actors(owner_model)
|
||||
self._discard_cached_actor(owner_model, restore_owner=True)
|
||||
actor = self._getActor(owner_model)
|
||||
if not actor:
|
||||
return
|
||||
@ -1373,9 +1720,14 @@ class AnimationTools:
|
||||
|
||||
is_scene_bound_proxy = isinstance(actor, _BoundAnimationProxy) and (not getattr(actor, "_owns_node", False))
|
||||
|
||||
original_world_pos = owner_model.getPos(self.render)
|
||||
original_world_hpr = owner_model.getHpr(self.render)
|
||||
original_world_scale = owner_model.getScale(self.render)
|
||||
if not is_scene_bound_proxy:
|
||||
actor.setPos(self.render, owner_model.getPos(self.render))
|
||||
actor.setHpr(self.render, owner_model.getHpr(self.render))
|
||||
actor.setScale(self.render, owner_model.getScale(self.render))
|
||||
|
||||
# original_world_pos = owner_model.getPos(self.render)
|
||||
# original_world_hpr = owner_model.getHpr(self.render)
|
||||
# original_world_scale = owner_model.getScale(self.render)
|
||||
|
||||
if not is_scene_bound_proxy:
|
||||
actor.setPos(owner_model.getPos())
|
||||
@ -1394,9 +1746,9 @@ class AnimationTools:
|
||||
def maintainWorldPosition(task):
|
||||
try:
|
||||
if not actor.isEmpty() and not owner_model.isEmpty():
|
||||
actor.setPos(self.render, original_world_pos)
|
||||
actor.setHpr(self.render, original_world_hpr)
|
||||
actor.setScale(self.render, original_world_scale)
|
||||
actor.setPos(self.render, owner_model.getPos(self.render))
|
||||
actor.setHpr(self.render, owner_model.getHpr(self.render))
|
||||
actor.setScale(self.render, owner_model.getScale(self.render))
|
||||
return task.cont
|
||||
else:
|
||||
return task.done
|
||||
@ -1497,6 +1849,9 @@ class AnimationTools:
|
||||
pass
|
||||
|
||||
owner_model = self._resolve_animation_owner_model(origin_model)
|
||||
self._stop_all_active_animations()
|
||||
self._cleanup_conflicting_animation_actors(owner_model)
|
||||
self._discard_cached_actor(owner_model, restore_owner=True)
|
||||
actor = self._getActor(owner_model)
|
||||
if not actor:
|
||||
return
|
||||
@ -1508,9 +1863,9 @@ class AnimationTools:
|
||||
original_world_scale = owner_model.getScale(self.render)
|
||||
|
||||
if not is_scene_bound_proxy:
|
||||
actor.setPos(owner_model.getPos())
|
||||
actor.setHpr(owner_model.getHpr())
|
||||
actor.setScale(owner_model.getScale())
|
||||
actor.setPos(self.render, owner_model.getPos(self.render))
|
||||
actor.setHpr(self.render, owner_model.getHpr(self.render))
|
||||
actor.setScale(self.render, owner_model.getScale(self.render))
|
||||
|
||||
if self._should_swap_visibility_for_actor(owner_model, actor):
|
||||
owner_model.hide()
|
||||
@ -1523,9 +1878,9 @@ class AnimationTools:
|
||||
def maintainWorldPosition(task):
|
||||
try:
|
||||
if not actor.isEmpty() and not owner_model.isEmpty():
|
||||
actor.setPos(self.render, original_world_pos)
|
||||
actor.setHpr(self.render, original_world_hpr)
|
||||
actor.setScale(self.render, original_world_scale)
|
||||
actor.setPos(self.render, owner_model.getPos(self.render))
|
||||
actor.setHpr(self.render, owner_model.getHpr(self.render))
|
||||
actor.setScale(self.render, owner_model.getScale(self.render))
|
||||
return task.cont
|
||||
else:
|
||||
return task.done
|
||||
|
||||
@ -1497,7 +1497,19 @@ class AppActions:
|
||||
|
||||
if hasattr(self.scene_manager, 'models'):
|
||||
if getattr(self, "use_ssbo_mouse_picking", False):
|
||||
self.scene_manager.models = [model_node]
|
||||
try:
|
||||
existing_models = []
|
||||
for existing in getattr(self.scene_manager, "models", []):
|
||||
try:
|
||||
if existing and not existing.isEmpty():
|
||||
existing_models.append(existing)
|
||||
except Exception:
|
||||
continue
|
||||
if model_node and all(existing != model_node for existing in existing_models):
|
||||
existing_models.append(model_node)
|
||||
self.scene_manager.models = existing_models
|
||||
except Exception:
|
||||
self.scene_manager.models = [model_node] if model_node else []
|
||||
elif model_node not in self.scene_manager.models:
|
||||
self.scene_manager.models.append(model_node)
|
||||
|
||||
@ -1507,7 +1519,33 @@ class AppActions:
|
||||
and getattr(self, "ssbo_editor", None)
|
||||
and getattr(self.ssbo_editor, "last_import_tree_key", None)
|
||||
):
|
||||
self.ssbo_editor.select_node(self.ssbo_editor.last_import_tree_key)
|
||||
selected_key = self.ssbo_editor.last_import_tree_key
|
||||
selection_cost = None
|
||||
if hasattr(self.ssbo_editor, "estimate_selection_cost"):
|
||||
try:
|
||||
selection_cost = self.ssbo_editor.estimate_selection_cost(selected_key)
|
||||
except Exception:
|
||||
selection_cost = None
|
||||
|
||||
should_auto_select = True
|
||||
if selection_cost:
|
||||
object_count = int(selection_cost.get("object_count", 0))
|
||||
chunk_count = int(selection_cost.get("chunk_count", 0))
|
||||
is_root = bool(selection_cost.get("is_root"))
|
||||
# SSBO 导入后的顶层节点通常覆盖整片模型;
|
||||
# 只要它跨多个对象或多个 chunk,被自动选中后就会让静态批处理失效。
|
||||
# 因此这里只允许极小代价的单对象选择自动激活,其余统一保持未选中。
|
||||
if is_root or object_count > 1 or chunk_count > 1:
|
||||
should_auto_select = False
|
||||
|
||||
if should_auto_select:
|
||||
self.ssbo_editor.select_node(selected_key)
|
||||
else:
|
||||
self.ssbo_editor.clear_selection(sync_world_selection=False)
|
||||
if hasattr(self.ssbo_editor, "_sync_editor_selection_reference"):
|
||||
self.ssbo_editor._sync_editor_selection_reference(None)
|
||||
if show_info_message:
|
||||
self.add_info_message("模型导入完成,已保持 SSBO 静态模式以避免导入后掉帧")
|
||||
elif hasattr(self, 'selection') and self.selection:
|
||||
self.selection.updateSelection(model_node)
|
||||
|
||||
|
||||
@ -620,21 +620,23 @@ class EditorPanelsRightMixin(
|
||||
needs_path = (not anim_node.hasTag("model_path")) or (not anim_node.getTag("model_path"))
|
||||
if needs_path and hasattr(self, "scene_manager") and self.scene_manager:
|
||||
models = getattr(self.scene_manager, "models", [])
|
||||
matched_models = []
|
||||
for model in list(models):
|
||||
try:
|
||||
if not model or model.isEmpty():
|
||||
continue
|
||||
if (model == anim_node or model.isAncestorOf(anim_node) or anim_node.isAncestorOf(model)):
|
||||
if model.hasTag("model_path") and model.getTag("model_path"):
|
||||
anim_node.setTag("model_path", model.getTag("model_path"))
|
||||
if model.hasTag("original_path") and model.getTag("original_path"):
|
||||
anim_node.setTag("original_path", model.getTag("original_path"))
|
||||
break
|
||||
if model.hasTag("saved_model_path") and model.getTag("saved_model_path"):
|
||||
anim_node.setTag("model_path", model.getTag("saved_model_path"))
|
||||
break
|
||||
matched_models.append(model)
|
||||
except Exception:
|
||||
continue
|
||||
if len(matched_models) == 1:
|
||||
model = matched_models[0]
|
||||
if model.hasTag("model_path") and model.getTag("model_path"):
|
||||
anim_node.setTag("model_path", model.getTag("model_path"))
|
||||
if model.hasTag("original_path") and model.getTag("original_path"):
|
||||
anim_node.setTag("original_path", model.getTag("original_path"))
|
||||
elif model.hasTag("saved_model_path") and model.getTag("saved_model_path"):
|
||||
anim_node.setTag("model_path", model.getTag("saved_model_path"))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ -690,18 +692,11 @@ class EditorPanelsRightMixin(
|
||||
|
||||
should_force_probe = False
|
||||
if not (has_animation_tag or has_animation_nodes or has_cached_animation):
|
||||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "此模型未检测到动画结构")
|
||||
if self.app.style_manager.draw_toolbar_button(
|
||||
"尝试强制检测",
|
||||
size=(108, 26),
|
||||
tooltip="重新扫描当前模型的动画结构",
|
||||
):
|
||||
should_force_probe = True
|
||||
anim_node.setPythonTag("cached_anim_info", None)
|
||||
anim_node.setPythonTag("cached_processed_names", None)
|
||||
has_animation_nodes = self._get_cached_animation_structure_state(anim_node, force_refresh=True)
|
||||
else:
|
||||
return
|
||||
should_force_probe = True
|
||||
anim_node.setPythonTag("cached_anim_info", None)
|
||||
anim_node.setPythonTag("cached_processed_names", None)
|
||||
anim_node.setPythonTag("cached_has_animation_nodes", None)
|
||||
has_animation_nodes = self._get_cached_animation_structure_state(anim_node, force_refresh=True)
|
||||
|
||||
# 只有在没有缓存时才进行完整的动画检测和处理
|
||||
if cached_anim_info is None or cached_processed_names is None:
|
||||
@ -711,7 +706,7 @@ class EditorPanelsRightMixin(
|
||||
if has_animation_tag or has_animation_nodes:
|
||||
imgui.text_colored((1.0, 0.7, 0.3, 1.0), "检测到动画结构,但当前未成功绑定Actor")
|
||||
elif should_force_probe:
|
||||
imgui.text_colored((0.9, 0.6, 0.3, 1.0), "强制检测未发现可播放动画")
|
||||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "此模型未检测到动画结构")
|
||||
anim_node.setPythonTag("animation", False)
|
||||
anim_node.setPythonTag("cached_processed_names", [])
|
||||
anim_node.setPythonTag("cached_anim_info", "无动画")
|
||||
|
||||
@ -123,8 +123,40 @@ class PanelDelegates:
|
||||
# 检查是否为相机
|
||||
if "camera" in node_name.lower() or "Camera" in node_name:
|
||||
return "相机"
|
||||
|
||||
|
||||
# 检查是否为模型
|
||||
try:
|
||||
if hasattr(self, "_resolve_animation_owner_model"):
|
||||
owner = self._resolve_animation_owner_model(node)
|
||||
if owner and not owner.isEmpty() and owner != node:
|
||||
return "模型"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
if hasattr(self, "animation_tools") and hasattr(self.animation_tools, "_resolve_ssbo_source_owner"):
|
||||
source_owner = self.animation_tools._resolve_ssbo_source_owner(node)
|
||||
if source_owner and not source_owner.isEmpty():
|
||||
return "模型"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
model_tag_names = ("model_path", "original_path", "saved_model_path")
|
||||
if any(node.hasTag(tag_name) and bool(node.getTag(tag_name)) for tag_name in model_tag_names):
|
||||
return "模型"
|
||||
if node.hasTag("is_model_root") and node.getTag("is_model_root") == "1":
|
||||
return "模型"
|
||||
if node.hasTag("tree_item_type") and node.getTag("tree_item_type") == "IMPORTED_MODEL_NODE":
|
||||
return "模型"
|
||||
if node.hasTag("has_animations") and node.getTag("has_animations").lower() == "true":
|
||||
return "模型"
|
||||
cached_anim = node.getPythonTag("animation") if hasattr(node, "getPythonTag") else None
|
||||
if cached_anim is True:
|
||||
return "模型"
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if hasattr(self, 'scene_manager') and self.scene_manager:
|
||||
if hasattr(self.scene_manager, 'models') and node in self.scene_manager.models:
|
||||
return "模型"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user