0324
This commit is contained in:
parent
2822dfee71
commit
d908b7d699
@ -389,45 +389,20 @@ class EventHandler:
|
||||
def _handleSelectionClick(self, hitNode):
|
||||
"""处理选择工具的点击事件"""
|
||||
print(f"开始处理选择点击,碰撞节点: {hitNode.getName()}")
|
||||
|
||||
# 查找对应的实际模型节点
|
||||
selectedModel = None
|
||||
|
||||
# 如果点击的是碰撞节点,找到它的父模型
|
||||
if isinstance(hitNode.node(), CollisionNode):
|
||||
print(f"点击的是碰撞节点: {hitNode.getName()}")
|
||||
# 碰撞节点的父节点应该是模型
|
||||
parent = hitNode.getParent()
|
||||
model_list = self.world.models if hasattr(self.world, 'models') else []
|
||||
if parent in model_list or parent.hasTag('is_model_root'):
|
||||
selectedModel = parent
|
||||
print(f"找到对应的模型: {selectedModel.getName()}")
|
||||
else:
|
||||
print(f"碰撞节点的父节点不是模型: {parent.getName()}")
|
||||
else:
|
||||
# 查找可选择的节点(模型或其子节点)
|
||||
current = hitNode
|
||||
while current != self.world.render:
|
||||
# 检查是否是模型
|
||||
model_list = self.world.models if hasattr(self.world, 'models') else []
|
||||
if current in model_list or current.hasTag('is_model_root'):
|
||||
selectedModel = current
|
||||
print(f"找到模型节点: {selectedModel.getName()}")
|
||||
break
|
||||
|
||||
# 检查是否是模型的子节点
|
||||
model_list = self.world.models if hasattr(self.world, 'models') else []
|
||||
for model in model_list:
|
||||
if current.getParent() == model or current.isAncestorOf(model):
|
||||
selectedModel = model
|
||||
print(f"找到父模型: {selectedModel.getName()}")
|
||||
break
|
||||
|
||||
if selectedModel:
|
||||
break
|
||||
|
||||
current = current.getParent()
|
||||
|
||||
selectedModel = None
|
||||
selection_system = getattr(self.world, "selection", None)
|
||||
if selection_system and hasattr(selection_system, "_resolve_scene_model_owner"):
|
||||
try:
|
||||
selectedModel = selection_system._resolve_scene_model_owner(hitNode)
|
||||
except Exception:
|
||||
selectedModel = None
|
||||
|
||||
if selectedModel and not selectedModel.isEmpty() and selectedModel != hitNode:
|
||||
print(f"归一化选中到模型根节点: {selectedModel.getName()}")
|
||||
elif isinstance(hitNode.node(), CollisionNode):
|
||||
print(f"点击的是碰撞节点: {hitNode.getName()}")
|
||||
|
||||
if selectedModel:
|
||||
#print(f"✓ 最终选中模型: {selectedModel.getName()}")
|
||||
self.world.selection.handleMouseClick(selectedModel)
|
||||
|
||||
@ -124,6 +124,77 @@ class SelectionSystem:
|
||||
"""统一获取场景树控件。"""
|
||||
return self._editor_context.get_tree_widget()
|
||||
|
||||
def _resolve_scene_model_owner(self, node):
|
||||
"""将场景中的任意子节点/碰撞节点归一到可编辑的模型根节点。"""
|
||||
if not self._is_valid_node(node):
|
||||
return node
|
||||
|
||||
try:
|
||||
current = node
|
||||
for _ in range(64):
|
||||
if not self._is_valid_node(current):
|
||||
break
|
||||
|
||||
runtime_owner = current.getPythonTag("animation_source_owner")
|
||||
if self._is_valid_node(runtime_owner, require_attached=True):
|
||||
return runtime_owner
|
||||
|
||||
if current.hasTag("runtime_animation_actor") and current.getTag("runtime_animation_actor") == "true":
|
||||
runtime_owner = current.getPythonTag("animation_source_owner")
|
||||
if self._is_valid_node(runtime_owner, require_attached=True):
|
||||
return runtime_owner
|
||||
|
||||
parent = current.getParent()
|
||||
if not self._is_valid_node(parent) or parent == current:
|
||||
break
|
||||
current = parent
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
if node.hasTag("is_model_root") and node.getTag("is_model_root") == "1":
|
||||
return node
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
scene_manager = getattr(self.world, "scene_manager", None)
|
||||
models = getattr(scene_manager, "models", None) if scene_manager else None
|
||||
if not models:
|
||||
return node
|
||||
|
||||
best_model = None
|
||||
best_score = -1
|
||||
for model in list(models):
|
||||
try:
|
||||
if not self._is_valid_node(model, require_attached=True):
|
||||
continue
|
||||
|
||||
relation = 0
|
||||
if model == node:
|
||||
relation = 4
|
||||
elif model.isAncestorOf(node):
|
||||
relation = 3
|
||||
elif node.isAncestorOf(model):
|
||||
relation = 2
|
||||
elif node.getParent() == model:
|
||||
relation = 1
|
||||
|
||||
if relation <= 0:
|
||||
continue
|
||||
|
||||
score = relation * 100
|
||||
if model.hasTag("is_model_root") and model.getTag("is_model_root") == "1":
|
||||
score += 20
|
||||
if model.hasTag("model_path") and model.getTag("model_path"):
|
||||
score += 10
|
||||
if score > best_score:
|
||||
best_score = score
|
||||
best_model = model
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
return best_model or node
|
||||
|
||||
def _clear_tree_selection(self):
|
||||
"""清空场景树选中项。"""
|
||||
tree_widget = self._get_tree_widget()
|
||||
@ -736,86 +807,6 @@ class SelectionSystem:
|
||||
except Exception as e:
|
||||
print(f"设置坐标轴渲染属性失败: {str(e)}")
|
||||
|
||||
def updateGizmoTask(self, task):
|
||||
"""坐标轴更新任务 - 包含固定大小功能"""
|
||||
return task.done
|
||||
try:
|
||||
# 限制更新频率
|
||||
if not hasattr(self, '_last_gizmo_update'):
|
||||
self._last_gizmo_update = 0
|
||||
|
||||
import time
|
||||
current_time = time.time()
|
||||
if current_time - self._last_gizmo_update < 0.5: # 每0.05秒更新一次
|
||||
return task.cont
|
||||
self._last_gizmo_update = current_time
|
||||
|
||||
#检查目标节点是否已被删除
|
||||
self.checkAndClearIfTargetDeleted()
|
||||
|
||||
if not self.gizmo or not self.gizmoTarget:
|
||||
return task.done
|
||||
|
||||
# 检查目标节点是否还存在
|
||||
if self.gizmoTarget.isEmpty():
|
||||
self.clearGizmo()
|
||||
return task.done
|
||||
|
||||
is_scale_tool = self.world.tool_manager.isScaleTool() if self.world.tool_manager else False
|
||||
is_rotate_tool = self.world.tool_manager.isRotateTool() if self.world.tool_manager else False
|
||||
was_scale_tool = getattr(self,'_last_tool_scale_state',False)
|
||||
was_rotate_tool =getattr(self,'_last_tool_rotate_state',False)
|
||||
|
||||
tool_changed = (is_scale_tool!=was_scale_tool) or (is_rotate_tool != was_rotate_tool)
|
||||
|
||||
if tool_changed:
|
||||
self._last_tool_scale_state = is_scale_tool
|
||||
self._last_tool_rotate_state = is_rotate_tool
|
||||
|
||||
if self.gizmoXAxis:
|
||||
self.gizmoXAxis.removeNode()
|
||||
self.gizmoXAxis = None
|
||||
if self.gizmoYAxis:
|
||||
self.gizmoYAxis.removeNode()
|
||||
self.gizmoYAxis = None
|
||||
if self.gizmoZAxis:
|
||||
self.gizmoZAxis.removeNode()
|
||||
self.gizmoZAxis = None
|
||||
if self.gizmoRotXAxis:
|
||||
self.gizmoRotXAxis.removeNode()
|
||||
self.gizmoRotXAxis = None
|
||||
if self.gizmoRotYAxis:
|
||||
self.gizmoRotYAxis.removeNode()
|
||||
self.gizmoRotYAxis = None
|
||||
if self.gizmoRotZAxis:
|
||||
self.gizmoRotZAxis.removeNode()
|
||||
self.gizmoRotZAxis = None
|
||||
|
||||
self.createGizmoGeometry()
|
||||
|
||||
self.setGizmoAxisColor("x",self.gizmo_colors["x"])
|
||||
self.setGizmoAxisColor("y",self.gizmo_colors["y"])
|
||||
self.setGizmoAxisColor("z",self.gizmo_colors["z"])
|
||||
|
||||
self.setupGizmoCollision()
|
||||
|
||||
light_object = self.gizmoTarget.getPythonTag("rp_light_object")
|
||||
if light_object:
|
||||
# 以节点位置为真值并回写 RP 灯光,避免“手柄能动但灯光不动”
|
||||
self._sync_rp_light_position(self.gizmoTarget, light_object)
|
||||
self.gizmo.setPos(self.gizmoTarget.getPos(self.world.render))
|
||||
else:
|
||||
# 只在必要时更新位置和朝向
|
||||
self._updateGizmoPositionAndOrientation()
|
||||
|
||||
# 【新功能】:动态调整坐标轴大小,保持固定的屏幕大小
|
||||
self._updateGizmoScreenSize()
|
||||
|
||||
return task.cont
|
||||
|
||||
except Exception as e:
|
||||
print(f"坐标轴更新任务出错: {str(e)}")
|
||||
return task.done
|
||||
|
||||
def _updateGizmoPositionAndOrientation(self):
|
||||
"""优化的Gizmo位置和朝向更新"""
|
||||
@ -2074,6 +2065,7 @@ class SelectionSystem:
|
||||
|
||||
def updateSelection(self, nodePath):
|
||||
try:
|
||||
nodePath = self._resolve_scene_model_owner(nodePath)
|
||||
if self._same_valid_node(self.selectedNode, nodePath):
|
||||
return
|
||||
#print(f"\n=== 更新选择状态 ===")
|
||||
@ -2089,13 +2081,6 @@ 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:
|
||||
@ -2151,51 +2136,19 @@ class SelectionSystem:
|
||||
if self._clear_tree_selection():
|
||||
print("✓ 树形控件选中状态已清空")
|
||||
|
||||
try:
|
||||
animation_tools = getattr(self.world, "animation_tools", None)
|
||||
if animation_tools and hasattr(animation_tools, "_refresh_active_animation_visibility"):
|
||||
animation_tools._refresh_active_animation_visibility()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
#print("=== 选择状态更新完成 ===\n")
|
||||
except Exception as e:
|
||||
print(f"更新选择状态失败{str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
# def _updateSelectionVisuals(self, nodePath):
|
||||
# """更新选择的视觉效果(选择框和坐标轴)"""
|
||||
# try:
|
||||
# if nodePath and not nodePath.isEmpty():
|
||||
# node_name = nodePath.getName()
|
||||
# print(f"开始为节点 {node_name} 创建选择框和坐标轴...")
|
||||
#
|
||||
# # 创建选择框
|
||||
# print("创建选择框...")
|
||||
# self.createSelectionBox(nodePath)
|
||||
# if self.selectionBox:
|
||||
# box_name = "Unknown"
|
||||
# if self.selectionBox and not self.selectionBox.isEmpty():
|
||||
# box_name = self.selectionBox.getName()
|
||||
# print(f"✓ 选择框创建成功: {box_name}")
|
||||
# else:
|
||||
# print("× 选择框创建失败")
|
||||
#
|
||||
# # 创建坐标轴
|
||||
# print("创建坐标轴...")
|
||||
# self.createGizmo(nodePath)
|
||||
# if self.gizmo:
|
||||
# gizmo_name = "Unknown"
|
||||
# if self.gizmo and not self.gizmo.isEmpty():
|
||||
# gizmo_name = self.gizmo.getName()
|
||||
# print(f"✓ 坐标轴创建成功: {gizmo_name}")
|
||||
# else:
|
||||
# print("× 坐标轴创建失败")
|
||||
#
|
||||
# print(f"✓ 选中了节点: {node_name}")
|
||||
# else:
|
||||
# print("清除选择...")
|
||||
# self.clearSelectionBox()
|
||||
# self.clearGizmo()
|
||||
# print("✓ 取消选择")
|
||||
#
|
||||
# except Exception as e:
|
||||
# print(f"更新选择视觉效果失败: {e}")
|
||||
|
||||
def getSelectedNode(self):
|
||||
"""获取当前选中的节点"""
|
||||
return self._get_effective_selected_node()
|
||||
@ -2250,171 +2203,6 @@ class SelectionSystem:
|
||||
self.selectedObject = None
|
||||
self._updateSelectionOutline(None)
|
||||
|
||||
def setupGizmoCollision(self):
|
||||
return
|
||||
if not self.gizmo or not self.gizmoXAxis or not self.gizmoYAxis or not self.gizmoZAxis:
|
||||
return
|
||||
|
||||
# 清除现有的碰撞节点
|
||||
for axis_name in ["x", "y", "z"]:
|
||||
axis_node = getattr(self, f"gizmo{axis_name.upper()}Axis")
|
||||
if axis_node:
|
||||
# 查找并移除所有现有的碰撞节点
|
||||
collision_nodes = axis_node.findAllMatches("**/gizmo_collision_*")
|
||||
for collision_node in collision_nodes:
|
||||
collision_node.removeNode()
|
||||
|
||||
# 为每个轴创建碰撞体
|
||||
self.createAxisCollision("x", self.gizmoXAxis)
|
||||
self.createAxisCollision("y", self.gizmoYAxis)
|
||||
self.createAxisCollision("z", self.gizmoZAxis)
|
||||
|
||||
def createAxisCollision(self, axis_name, axis_node):
|
||||
return
|
||||
# 为单个轴创建碰撞体
|
||||
try:
|
||||
handle_node = axis_node.find(f"{axis_name}_handle")
|
||||
if not handle_node or handle_node.isEmpty():
|
||||
children = axis_node.getChildren()
|
||||
if children.getNumPaths() > 0:
|
||||
handle_node = children[0]
|
||||
else:
|
||||
print(f"警告: 未找到 {axis_name} 轴的 handle 节点")
|
||||
return
|
||||
|
||||
collision_node = CollisionNode(f"gizmo_collision_{axis_name}")
|
||||
collision_node.setIntoCollideMask(BitMask32.bit(1)) # 设置为into对象
|
||||
collision_node.setFromCollideMask(BitMask32.allOff()) # 不作为from对象
|
||||
|
||||
# 调整碰撞尺寸以匹配实际的轴长度和坐标轴缩放
|
||||
scale_factor = self.gizmo.getScale().x if self.gizmo else 1.0
|
||||
axis_length = 2.0 * scale_factor
|
||||
radius = 0.3 * scale_factor
|
||||
|
||||
# 根据轴的类型创建合适的碰撞体
|
||||
if axis_name == "x":
|
||||
capsule = CollisionCapsule(
|
||||
Point3(0, 0, 0),
|
||||
Point3(axis_length, 0, 0),
|
||||
radius
|
||||
)
|
||||
collision_node.addSolid(capsule)
|
||||
elif axis_name == "y":
|
||||
capsule = CollisionCapsule(
|
||||
Point3(0, 0, 0),
|
||||
Point3(0, axis_length, 0),
|
||||
radius
|
||||
)
|
||||
collision_node.addSolid(capsule)
|
||||
elif axis_name == "z":
|
||||
capsule = CollisionCapsule(
|
||||
Point3(0, 0, 0),
|
||||
Point3(0, 0, axis_length),
|
||||
radius
|
||||
)
|
||||
collision_node.addSolid(capsule)
|
||||
|
||||
# 将碰撞节点附加到handle节点,使其与可视化几何体保持一致
|
||||
collision_np = handle_node.attachNewNode(collision_node)
|
||||
|
||||
# 设置标签以便识别
|
||||
collision_np.setTag("gizmo_axis", axis_name)
|
||||
collision_np.setTag("pickable", "1")
|
||||
|
||||
collision_np.hide() # 隐藏碰撞体,只用于检测
|
||||
|
||||
#print(f"✓ 成功创建 {axis_name} 轴碰撞体")
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建{axis_name}轴碰撞体失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def detectGizmoAxisWithCollision(self, mouseX, mouseY):
|
||||
return None
|
||||
# 使用碰撞体检测鼠标是否悬停在坐标轴上
|
||||
if not self.gizmo:
|
||||
return None
|
||||
|
||||
try:
|
||||
ray = CollisionRay()
|
||||
|
||||
win_width, win_height = self.world.getWindowSize()
|
||||
|
||||
mouse_x_ndc = (mouseX / win_width) * 2.0 - 1.0
|
||||
mouse_y_ndc = 1.0 - (mouseY / win_height) * 2.0
|
||||
|
||||
ray.setFromLens(self.world.cam.node(), mouse_x_ndc, mouse_y_ndc)
|
||||
|
||||
traverser = CollisionTraverser("gizmo_traverser")
|
||||
handler = CollisionHandlerQueue()
|
||||
|
||||
# 创建射线节点
|
||||
ray_node = CollisionNode('mouseRay')
|
||||
ray_node.addSolid(ray)
|
||||
ray_node.setFromCollideMask(BitMask32.bit(1)) # 射线作为from对象
|
||||
ray_node.setIntoCollideMask(BitMask32.allOff()) # 射线不作为into对象
|
||||
ray_np = self.world.render.attachNewNode(ray_node)
|
||||
|
||||
# 为所有轴的碰撞体设置正确的掩码并添加到遍历器
|
||||
collision_found = False
|
||||
for axis_name in ["x", "y", "z"]:
|
||||
axis_node = getattr(self, f"gizmo{axis_name.upper()}Axis")
|
||||
if axis_node:
|
||||
collision_node_path = axis_node.find("**/gizmo_collision_*")
|
||||
if not collision_node_path.isEmpty():
|
||||
collision_node = collision_node_path.node()
|
||||
collision_node.setFromCollideMask(BitMask32.allOff()) # 碰撞体不作为from对象
|
||||
collision_node.setIntoCollideMask(BitMask32.bit(1)) # 碰撞体作为into对象
|
||||
collision_found = True
|
||||
|
||||
if not collision_found:
|
||||
ray_np.removeNode()
|
||||
return None
|
||||
|
||||
# 执行碰撞检测 - 这里是关键修复点
|
||||
traverser.addCollider(ray_np, handler)
|
||||
traverser.traverse(self.world.render)
|
||||
|
||||
ray_np.removeNode()
|
||||
|
||||
# 检查是否有碰撞
|
||||
if handler.getNumEntries() > 0:
|
||||
handler.sortEntries()
|
||||
closest_entry = handler.getEntry(0)
|
||||
|
||||
# 获取碰撞的对象
|
||||
collided_object = closest_entry.getIntoNodePath()
|
||||
axis_tag = collided_object.getTag("gizmo_axis")
|
||||
|
||||
if axis_tag in ["x", "y", "z"]:
|
||||
return axis_tag
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"使用碰撞体检测坐标轴失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def debugGizmoCollision(self):
|
||||
return
|
||||
print("===碰撞体调试信息===")
|
||||
for axis_name in ["x","y","z"]:
|
||||
axis_node = getattr(self,f"gizmo{axis_name.upper()}Axis")
|
||||
if axis_node:
|
||||
handle_node = axis_node.find(f"{axis_name}_handle")
|
||||
collision_node = axis_node.find("**/gizmo_collision_*")
|
||||
print(f"{axis_name.upper()}轴:")
|
||||
print(f" - 轴节点: {axis_node}")
|
||||
print(f" - Handle节点: {handle_node}")
|
||||
print(f" - 碰撞节点: {collision_node}")
|
||||
if not collision_node.isEmpty():
|
||||
print(f" - 碰撞体标签: {collision_node.getTag('gizmo_axis')}")
|
||||
else:
|
||||
print(f"{axis_name.upper()}轴节点不存在")
|
||||
|
||||
def focusCameraOnSelectedNodeAdvanced(self):
|
||||
"""高级版的摄像机聚焦功能,包含平滑动画效果"""
|
||||
try:
|
||||
@ -2835,69 +2623,6 @@ class SelectionSystem:
|
||||
self._double_click_task = None
|
||||
return task.done
|
||||
|
||||
# 修改 updateSelection 方法以集成双击检测
|
||||
|
||||
# def updateSelection(self, nodePath):
|
||||
# """更新选择状态"""
|
||||
# print(f"\n=== 更新选择状态 ===")
|
||||
#
|
||||
# # 如果正在删除节点,避免更新选择
|
||||
# if hasattr(self, '_deleting_node') and self._deleting_node:
|
||||
# print("正在删除节点,跳过选择更新")
|
||||
# print("=== 选择状态更新完成 ===\n")
|
||||
# return
|
||||
#
|
||||
# node_name = "None"
|
||||
# if nodePath and not nodePath.isEmpty():
|
||||
# node_name = nodePath.getName()
|
||||
# print(f"新选择的节点: {node_name}")
|
||||
#
|
||||
# # 检查是否为双击
|
||||
# is_double_click = self.checkDoubleClick(nodePath)
|
||||
# if is_double_click:
|
||||
# print(f"检测到双击 {node_name},执行聚焦")
|
||||
# # 启动聚焦(在下一帧执行,确保选择状态已更新)
|
||||
# taskMgr.doMethodLater(0.01, self._delayedFocusTask, "delayedFocus")
|
||||
#
|
||||
# self.selectedNode = nodePath
|
||||
# # 添加兼容性属性
|
||||
# self.selectedObject = nodePath
|
||||
#
|
||||
# if nodePath and not nodePath.isEmpty():
|
||||
# node_name = nodePath.getName()
|
||||
# print(f"开始为节点 {node_name} 创建选择框和坐标轴...")
|
||||
#
|
||||
# # 创建选择框
|
||||
# print("创建选择框...")
|
||||
# self.createSelectionBox(nodePath)
|
||||
# if self.selectionBox:
|
||||
# box_name = "Unknown"
|
||||
# if self.selectionBox and not self.selectionBox.isEmpty():
|
||||
# box_name = self.selectionBox.getName()
|
||||
# print(f"✓ 选择框创建成功: {box_name}")
|
||||
# else:
|
||||
# print("× 选择框创建失败")
|
||||
#
|
||||
# # 创建坐标轴
|
||||
# print("创建坐标轴...")
|
||||
# self.createGizmo(nodePath)
|
||||
# if self.gizmo:
|
||||
# gizmo_name = "Unknown"
|
||||
# if self.gizmo and not self.gizmo.isEmpty():
|
||||
# gizmo_name = self.gizmo.getName()
|
||||
# print(f"✓ 坐标轴创建成功: {gizmo_name}")
|
||||
# else:
|
||||
# print("× 坐标轴创建失败")
|
||||
#
|
||||
# print(f"✓ 选中了节点: {node_name}")
|
||||
# else:
|
||||
# print("清除选择...")
|
||||
# self.clearSelectionBox()
|
||||
# self.clearGizmo()
|
||||
# print("✓ 取消选择")
|
||||
#
|
||||
# print("=== 选择状态更新完成 ===\n")
|
||||
|
||||
def _delayedFocusTask(self, task):
|
||||
"""延迟执行聚焦任务"""
|
||||
try:
|
||||
|
||||
@ -3,7 +3,7 @@ from pathlib import Path
|
||||
|
||||
from direct.actor.Actor import Actor
|
||||
from direct.task.TaskManagerGlobal import taskMgr
|
||||
from panda3d.core import NodePath, PartSubset, Filename
|
||||
from panda3d.core import NodePath, PartSubset, Filename, BitMask32
|
||||
|
||||
|
||||
class _BoundAnimationProxy:
|
||||
@ -121,9 +121,200 @@ class AnimationTools:
|
||||
def _ensure_animation_runtime_state(self):
|
||||
if not hasattr(self, "_animation_sync_tasks") or self._animation_sync_tasks is None:
|
||||
self._animation_sync_tasks = {}
|
||||
if not hasattr(self, "_animation_hide_tasks") or self._animation_hide_tasks is None:
|
||||
self._animation_hide_tasks = {}
|
||||
if not hasattr(self, "_animation_debug_enabled"):
|
||||
self._animation_debug_enabled = False
|
||||
|
||||
def _mark_runtime_animation_node(self, runtime_node, owner_model):
|
||||
"""Bind runtime animation nodes back to their logical owner and disable scene picking."""
|
||||
try:
|
||||
if isinstance(runtime_node, _BoundAnimationProxy):
|
||||
runtime_node = getattr(runtime_node, "_node", None)
|
||||
if not runtime_node or runtime_node.isEmpty() or not owner_model or owner_model.isEmpty():
|
||||
return
|
||||
|
||||
runtime_node.setPythonTag("animation_source_owner", owner_model)
|
||||
runtime_node.setTag("runtime_animation_actor", "true")
|
||||
try:
|
||||
runtime_node.setCollideMask(BitMask32.allOff())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
for child in runtime_node.findAllMatches("**"):
|
||||
if not child or child.isEmpty():
|
||||
continue
|
||||
child.setPythonTag("animation_source_owner", owner_model)
|
||||
child.setTag("runtime_animation_actor", "true")
|
||||
try:
|
||||
child.setCollideMask(BitMask32.allOff())
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _set_owner_animation_lock(self, owner_model, locked):
|
||||
"""Mark whether the logical owner should stay hidden while animation is active."""
|
||||
try:
|
||||
if not owner_model or owner_model.isEmpty():
|
||||
return
|
||||
owner_model.setPythonTag("animation_owner_locked_hidden", bool(locked))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _set_owner_stashed_for_animation(self, owner_model, stashed):
|
||||
try:
|
||||
if not owner_model or owner_model.isEmpty():
|
||||
return
|
||||
owner_model.setPythonTag("animation_owner_stashed", bool(stashed))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _is_owner_stashed_for_animation(self, owner_model):
|
||||
try:
|
||||
if not owner_model or owner_model.isEmpty():
|
||||
return False
|
||||
return bool(owner_model.getPythonTag("animation_owner_stashed"))
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _is_owner_animation_locked(self, owner_model):
|
||||
try:
|
||||
if not owner_model or owner_model.isEmpty():
|
||||
return False
|
||||
return bool(owner_model.getPythonTag("animation_owner_locked_hidden"))
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _show_owner_model_if_allowed(self, owner_model, force=False):
|
||||
try:
|
||||
if not owner_model or owner_model.isEmpty():
|
||||
return
|
||||
if (not force) and self._is_owner_animation_locked(owner_model):
|
||||
self._set_owner_stashed_for_animation(owner_model, True)
|
||||
owner_model.stash()
|
||||
return
|
||||
if self._is_owner_stashed_for_animation(owner_model):
|
||||
owner_model.unstash()
|
||||
self._set_owner_stashed_for_animation(owner_model, False)
|
||||
owner_model.show()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _refresh_active_animation_visibility(self):
|
||||
"""
|
||||
Re-assert visibility constraints for active animation owners.
|
||||
Used after selection/UI code that may call show() on scene nodes.
|
||||
"""
|
||||
try:
|
||||
for owner_model, actor in list(getattr(self, "_actor_cache", {}).items()):
|
||||
try:
|
||||
if not owner_model or owner_model.isEmpty() or not actor or actor.isEmpty():
|
||||
continue
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
try:
|
||||
self._apply_actor_owner_visibility(owner_model, actor)
|
||||
except Exception:
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _can_swap_actor_owner_visibility(self, owner_model, actor):
|
||||
try:
|
||||
if not owner_model or owner_model.isEmpty() or not actor or actor.isEmpty():
|
||||
return False
|
||||
if self._is_scene_root_node(owner_model):
|
||||
return False
|
||||
if actor == owner_model:
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
try:
|
||||
if isinstance(actor, _BoundAnimationProxy):
|
||||
return getattr(actor, "_node", None) != owner_model
|
||||
except Exception:
|
||||
pass
|
||||
return True
|
||||
|
||||
def _apply_actor_owner_visibility(self, owner_model, actor, prefer_actor_visible=None):
|
||||
"""
|
||||
Hard invariant:
|
||||
- actor visible => owner hidden
|
||||
- owner visible => actor hidden
|
||||
"""
|
||||
try:
|
||||
if not self._can_swap_actor_owner_visibility(owner_model, actor):
|
||||
return
|
||||
|
||||
if prefer_actor_visible is None:
|
||||
try:
|
||||
prefer_actor_visible = not actor.isHidden()
|
||||
except Exception:
|
||||
prefer_actor_visible = self._is_owner_animation_locked(owner_model)
|
||||
|
||||
if prefer_actor_visible:
|
||||
self._set_owner_animation_lock(owner_model, True)
|
||||
self._set_owner_stashed_for_animation(owner_model, True)
|
||||
owner_model.stash()
|
||||
actor.show()
|
||||
else:
|
||||
self._set_owner_animation_lock(owner_model, False)
|
||||
if self._is_owner_stashed_for_animation(owner_model):
|
||||
owner_model.unstash()
|
||||
self._set_owner_stashed_for_animation(owner_model, False)
|
||||
actor.hide()
|
||||
owner_model.show()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _ensure_owner_hidden_lock_task(self, owner_model, enabled):
|
||||
self._ensure_animation_runtime_state()
|
||||
task_name = self._animation_hide_tasks.get(owner_model)
|
||||
|
||||
if not enabled:
|
||||
if task_name:
|
||||
try:
|
||||
taskMgr.remove(task_name)
|
||||
except Exception:
|
||||
pass
|
||||
self._animation_hide_tasks.pop(owner_model, None)
|
||||
return
|
||||
|
||||
task_name = f"maintain_anim_owner_hidden_{id(owner_model)}"
|
||||
self._animation_hide_tasks[owner_model] = task_name
|
||||
|
||||
def maintain_hidden(task):
|
||||
try:
|
||||
if not owner_model or owner_model.isEmpty():
|
||||
self._animation_hide_tasks.pop(owner_model, None)
|
||||
return task.done
|
||||
if not self._is_owner_animation_locked(owner_model):
|
||||
self._animation_hide_tasks.pop(owner_model, None)
|
||||
return task.done
|
||||
actor = self._actor_cache.get(owner_model)
|
||||
if actor:
|
||||
self._apply_actor_owner_visibility(owner_model, actor, prefer_actor_visible=True)
|
||||
else:
|
||||
self._set_owner_stashed_for_animation(owner_model, True)
|
||||
owner_model.stash()
|
||||
return task.cont
|
||||
except Exception:
|
||||
self._animation_hide_tasks.pop(owner_model, None)
|
||||
return task.done
|
||||
|
||||
try:
|
||||
taskMgr.remove(task_name)
|
||||
except Exception:
|
||||
pass
|
||||
taskMgr.add(maintain_hidden, task_name)
|
||||
|
||||
def _anim_log(self, message):
|
||||
self._ensure_animation_runtime_state()
|
||||
if self._animation_debug_enabled:
|
||||
@ -1061,6 +1252,7 @@ class AnimationTools:
|
||||
|
||||
actor.reparentTo(self._get_owner_parent_node(owner_model))
|
||||
actor.hide()
|
||||
self._mark_runtime_animation_node(actor, owner_model)
|
||||
return actor
|
||||
except Exception as e:
|
||||
self._anim_log(f"[Actor加载] {source_desc} 失败: {e}")
|
||||
@ -1190,6 +1382,7 @@ class AnimationTools:
|
||||
if proxy:
|
||||
proxy.reparentTo(self._get_owner_parent_node(owner_model))
|
||||
proxy.hide()
|
||||
self._mark_runtime_animation_node(proxy, owner_model)
|
||||
succeeded = True
|
||||
return proxy
|
||||
except Exception as e:
|
||||
@ -1331,6 +1524,7 @@ class AnimationTools:
|
||||
clone_np.setName(f"{owner_model.getName()}__anim_runtime")
|
||||
mem_actor = _try_create_actor_from_source(clone_np, "内存模型副本")
|
||||
if mem_actor:
|
||||
self._mark_runtime_animation_node(mem_actor, owner_model)
|
||||
self._actor_cache[owner_model] = mem_actor
|
||||
owner_model.setTag("has_animations", "true")
|
||||
return mem_actor
|
||||
@ -1352,6 +1546,7 @@ class AnimationTools:
|
||||
owns_node=False
|
||||
)
|
||||
if mem_proxy:
|
||||
self._mark_runtime_animation_node(mem_proxy, owner_model)
|
||||
self._actor_cache[owner_model] = mem_proxy
|
||||
owner_model.setTag("has_animations", "true")
|
||||
if source_node != owner_model:
|
||||
@ -1439,6 +1634,7 @@ class AnimationTools:
|
||||
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
|
||||
|
||||
@ -1455,6 +1651,7 @@ class AnimationTools:
|
||||
loaded_model.hide()
|
||||
owner_model.setTag("model_path", p)
|
||||
owner_model.setTag("has_animations", "true")
|
||||
self._mark_runtime_animation_node(proxy, owner_model)
|
||||
self._actor_cache[owner_model] = proxy
|
||||
return proxy
|
||||
loaded_model.removeNode()
|
||||
@ -1489,14 +1686,36 @@ class AnimationTools:
|
||||
return format_name
|
||||
return "未知"
|
||||
|
||||
def _processAnimationNames(self, origin_model, anim_names):
|
||||
"""处理和分析动画名称,返回 [(显示名称, 原始名称), ...]"""
|
||||
def _processAnimationNames(self, origin_model, anim_names, actor=None):
|
||||
"""处理和分析动画名称,返回 [(显示名称, 原始名称), ...]。"""
|
||||
format_info = self._getModelFormat(origin_model)
|
||||
processed = []
|
||||
seen_original_names = set()
|
||||
|
||||
self._anim_log(f"[动画分析] 格式: {format_info}, 原始动画名称: {anim_names}")
|
||||
|
||||
for name in anim_names:
|
||||
if not name or name in seen_original_names:
|
||||
continue
|
||||
|
||||
control = None
|
||||
frame_count = -1
|
||||
if actor:
|
||||
try:
|
||||
control = actor.getAnimControl(name)
|
||||
except Exception:
|
||||
control = None
|
||||
if control:
|
||||
try:
|
||||
frame_count = control.getNumFrames()
|
||||
except Exception:
|
||||
frame_count = -1
|
||||
|
||||
# 过滤掉无法实际播放的“伪动画名”,避免把骨骼/Bundle 名字暴露到下拉框里。
|
||||
if actor and (control is None or frame_count <= 1):
|
||||
self._anim_log(f"[动画分析] 跳过无效动画名: {name} (control={bool(control)}, frames={frame_count})")
|
||||
continue
|
||||
|
||||
display_name = name
|
||||
original_name = name
|
||||
|
||||
@ -1535,8 +1754,17 @@ class AnimationTools:
|
||||
display_name = name
|
||||
|
||||
processed.append((display_name, original_name))
|
||||
seen_original_names.add(original_name)
|
||||
self._anim_log(f"[动画分析] {original_name} → {display_name}")
|
||||
|
||||
# 某些导入链路拿不到 AnimControl 时,退回原始列表,避免面板完全为空。
|
||||
if not processed and anim_names:
|
||||
for name in anim_names:
|
||||
if not name or name in seen_original_names:
|
||||
continue
|
||||
processed.append((name, name))
|
||||
seen_original_names.add(name)
|
||||
|
||||
return processed
|
||||
|
||||
def _isLikelyBoneGroup(self, name):
|
||||
@ -1591,12 +1819,43 @@ class AnimationTools:
|
||||
if not anim_names:
|
||||
return None
|
||||
|
||||
current_anim = origin_model.getPythonTag("selected_animation")
|
||||
if owner_model and owner_model != origin_model and current_anim not in anim_names:
|
||||
current_anim = owner_model.getPythonTag("selected_animation")
|
||||
preferred_names = []
|
||||
try:
|
||||
preferred_names.append(origin_model.getPythonTag("selected_animation"))
|
||||
except Exception:
|
||||
pass
|
||||
if owner_model and owner_model != origin_model:
|
||||
try:
|
||||
preferred_names.append(owner_model.getPythonTag("selected_animation"))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if current_anim not in anim_names:
|
||||
current_anim = anim_names[0]
|
||||
valid_names = []
|
||||
for anim_name in anim_names:
|
||||
try:
|
||||
control = actor.getAnimControl(anim_name)
|
||||
except Exception:
|
||||
control = None
|
||||
if not control:
|
||||
continue
|
||||
try:
|
||||
if control.getNumFrames() <= 1:
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
valid_names.append(anim_name)
|
||||
|
||||
if not valid_names:
|
||||
valid_names = list(anim_names)
|
||||
|
||||
current_anim = None
|
||||
for preferred in preferred_names:
|
||||
if preferred in valid_names:
|
||||
current_anim = preferred
|
||||
break
|
||||
|
||||
if current_anim not in valid_names:
|
||||
current_anim = valid_names[0]
|
||||
|
||||
try:
|
||||
origin_model.setPythonTag("selected_animation", current_anim)
|
||||
@ -1658,7 +1917,7 @@ class AnimationTools:
|
||||
|
||||
try:
|
||||
if self._should_swap_visibility_for_actor(cached_owner, actor):
|
||||
cached_owner.show()
|
||||
self._show_owner_model_if_allowed(cached_owner)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ -1721,6 +1980,16 @@ class AnimationTools:
|
||||
self._animation_sync_tasks.pop(owner_model, None)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
self._ensure_owner_hidden_lock_task(owner_model, enabled=False)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if self._is_owner_stashed_for_animation(owner_model):
|
||||
owner_model.unstash()
|
||||
self._set_owner_stashed_for_animation(owner_model, False)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
actor.hide()
|
||||
@ -1730,7 +1999,7 @@ class AnimationTools:
|
||||
if restore_owner:
|
||||
try:
|
||||
if owner_model and not owner_model.isEmpty():
|
||||
owner_model.show()
|
||||
self._show_owner_model_if_allowed(owner_model)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@ -1791,24 +2060,40 @@ class AnimationTools:
|
||||
if not actor:
|
||||
return
|
||||
|
||||
# 获取当前选中的动画
|
||||
current_anim = self._resolve_selected_animation_name(origin_model, actor, owner_model)
|
||||
if not current_anim:
|
||||
print("『动画播放』未找到可播放的有效动画,保留原模型显示")
|
||||
self._show_owner_model_if_allowed(owner_model)
|
||||
return
|
||||
|
||||
try:
|
||||
control = actor.getAnimControl(current_anim)
|
||||
except Exception:
|
||||
control = None
|
||||
if not control:
|
||||
print(f"『动画播放』动画控制器无效: {current_anim},保留原模型显示")
|
||||
self._show_owner_model_if_allowed(owner_model)
|
||||
return
|
||||
|
||||
is_scene_bound_proxy = self._sync_actor_transform_for_playback(owner_model, actor)
|
||||
|
||||
if self._should_swap_visibility_for_actor(owner_model, actor):
|
||||
owner_model.hide()
|
||||
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)
|
||||
else:
|
||||
owner_model.show()
|
||||
self._set_owner_animation_lock(owner_model, False)
|
||||
self._ensure_owner_hidden_lock_task(owner_model, enabled=False)
|
||||
self._show_owner_model_if_allowed(owner_model)
|
||||
actor.show()
|
||||
self._ensure_actor_sync_task(owner_model, actor, enabled=(not is_scene_bound_proxy))
|
||||
|
||||
# 获取当前选中的动画
|
||||
current_anim = self._resolve_selected_animation_name(origin_model, actor, owner_model)
|
||||
if current_anim:
|
||||
try:
|
||||
actor.stop()
|
||||
except Exception:
|
||||
pass
|
||||
actor.play(current_anim)
|
||||
print(f"『动画播放』:{current_anim}")
|
||||
try:
|
||||
actor.stop()
|
||||
except Exception:
|
||||
pass
|
||||
actor.play(current_anim)
|
||||
print(f"『动画播放』:{current_anim}")
|
||||
|
||||
def _pauseAnimation(self, origin_model):
|
||||
"""暂停动画"""
|
||||
@ -1839,15 +2124,15 @@ class AnimationTools:
|
||||
actor.setHpr(self.render, owner_model.getHpr(self.render))
|
||||
actor.setScale(self.render, owner_model.getScale(self.render))
|
||||
|
||||
# 隐藏原始模型,显示Actor
|
||||
if self._should_swap_visibility_for_actor(owner_model, actor):
|
||||
owner_model.hide()
|
||||
else:
|
||||
owner_model.show()
|
||||
actor.show()
|
||||
|
||||
# 停止动画(保持当前姿势)
|
||||
actor.stop()
|
||||
|
||||
# 显式暂停时解除隐藏锁并恢复本体显示
|
||||
self._ensure_owner_hidden_lock_task(owner_model, enabled=False)
|
||||
if self._can_swap_actor_owner_visibility(owner_model, actor):
|
||||
self._apply_actor_owner_visibility(owner_model, actor, prefer_actor_visible=False)
|
||||
self._show_owner_model_if_allowed(owner_model, force=True)
|
||||
|
||||
print("『动画』暂停")
|
||||
|
||||
def _stopAnimation(self, origin_model):
|
||||
@ -1872,9 +2157,10 @@ class AnimationTools:
|
||||
actor.getAnimControl(current_anim).pose(0)
|
||||
|
||||
# 隐藏Actor,显示原始模型
|
||||
if self._should_swap_visibility_for_actor(owner_model, actor):
|
||||
actor.hide()
|
||||
owner_model.show()
|
||||
self._ensure_owner_hidden_lock_task(owner_model, enabled=False)
|
||||
if self._can_swap_actor_owner_visibility(owner_model, actor):
|
||||
self._apply_actor_owner_visibility(owner_model, actor, prefer_actor_visible=False)
|
||||
self._show_owner_model_if_allowed(owner_model, force=True)
|
||||
|
||||
# 移除位置维护任务
|
||||
taskMgr.remove(f"maintain_anim_pos_{id(actor)}")
|
||||
@ -1895,24 +2181,40 @@ class AnimationTools:
|
||||
if not actor:
|
||||
return
|
||||
|
||||
# 获取当前选中的动画
|
||||
current_anim = self._resolve_selected_animation_name(origin_model, actor, owner_model)
|
||||
if not current_anim:
|
||||
print("[动画] 未找到可循环播放的有效动画,保留原模型显示")
|
||||
self._show_owner_model_if_allowed(owner_model)
|
||||
return
|
||||
|
||||
try:
|
||||
control = actor.getAnimControl(current_anim)
|
||||
except Exception:
|
||||
control = None
|
||||
if not control:
|
||||
print(f"[动画] 动画控制器无效: {current_anim},保留原模型显示")
|
||||
self._show_owner_model_if_allowed(owner_model)
|
||||
return
|
||||
|
||||
is_scene_bound_proxy = self._sync_actor_transform_for_playback(owner_model, actor)
|
||||
|
||||
if self._should_swap_visibility_for_actor(owner_model, actor):
|
||||
owner_model.hide()
|
||||
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)
|
||||
else:
|
||||
owner_model.show()
|
||||
self._set_owner_animation_lock(owner_model, False)
|
||||
self._ensure_owner_hidden_lock_task(owner_model, enabled=False)
|
||||
self._show_owner_model_if_allowed(owner_model)
|
||||
actor.show()
|
||||
self._ensure_actor_sync_task(owner_model, actor, enabled=(not is_scene_bound_proxy))
|
||||
|
||||
# 获取当前选中的动画
|
||||
current_anim = self._resolve_selected_animation_name(origin_model, actor, owner_model)
|
||||
if current_anim:
|
||||
try:
|
||||
actor.stop()
|
||||
except Exception:
|
||||
pass
|
||||
actor.loop(current_anim)
|
||||
print(f"[动画] 循环: {current_anim}")
|
||||
try:
|
||||
actor.stop()
|
||||
except Exception:
|
||||
pass
|
||||
actor.loop(current_anim)
|
||||
print(f"[动画] 循环: {current_anim}")
|
||||
|
||||
def _setAnimationSpeed(self, origin_model, speed):
|
||||
"""设置动画播放速度"""
|
||||
|
||||
@ -730,7 +730,7 @@ class EditorPanelsRightMixin(
|
||||
|
||||
# 获取和分析动画名称
|
||||
anim_names = actor.getAnimNames()
|
||||
processed_names = self._processAnimationNames(anim_node, anim_names)
|
||||
processed_names = self._processAnimationNames(anim_node, anim_names, actor=actor)
|
||||
|
||||
if not processed_names:
|
||||
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "未检测到动画序列")
|
||||
|
||||
@ -37,6 +37,12 @@ class PanelDelegates:
|
||||
node = selection.getSelectedNode()
|
||||
else:
|
||||
node = getattr(selection, "selectedNode", None) if selection else None
|
||||
|
||||
try:
|
||||
if selection and hasattr(selection, "_resolve_scene_model_owner"):
|
||||
node = selection._resolve_scene_model_owner(node)
|
||||
except Exception:
|
||||
pass
|
||||
return node if self._node_is_valid(node, require_attached=False) else None
|
||||
|
||||
def _get_ssbo_selection_summary(self):
|
||||
|
||||
Loading…
Reference in New Issue
Block a user