聚焦功能
This commit is contained in:
parent
a8082bd656
commit
d3c6665163
@ -364,7 +364,7 @@ class CollisionManager:
|
||||
collision_poly = CollisionPolygon(*[Point3(*v) for v in vertices])
|
||||
return collision_poly
|
||||
else:
|
||||
print("⚠️ 多边形至少需要3个顶点,回退到球体")
|
||||
#print("⚠️ 多边形至少需要3个顶点,回退到球体")
|
||||
return CollisionSphere(center, radius)
|
||||
|
||||
def _determineOptimalShape(self, model, bounds):
|
||||
|
||||
@ -167,7 +167,7 @@ class EventHandler:
|
||||
picker.addCollider(pickerNP, queue)
|
||||
picker.traverse(self.world.render)
|
||||
|
||||
print(f"碰撞检测结果数量: {queue.getNumEntries()}")
|
||||
#print(f"碰撞检测结果数量: {queue.getNumEntries()}")
|
||||
|
||||
# 射线检测结果处理
|
||||
hitPos = None
|
||||
@ -184,13 +184,13 @@ class EventHandler:
|
||||
self.showClickRay(worldNearPoint, worldFarPoint, hitPos)
|
||||
|
||||
# 优先检查是否点击了坐标轴
|
||||
print(f"检查坐标轴点击: 坐标轴存在={bool(self.world.selection.gizmo)}")
|
||||
#print(f"检查坐标轴点击: 坐标轴存在={bool(self.world.selection.gizmo)}")
|
||||
if self.world.selection.gizmo:
|
||||
print("准备检查坐标轴点击...")
|
||||
#print("准备检查坐标轴点击...")
|
||||
try:
|
||||
gizmoAxis = self.world.selection.checkGizmoClick(x, y)
|
||||
if gizmoAxis:
|
||||
print(f"✓ 检测到坐标轴点击: {gizmoAxis}")
|
||||
#print(f"✓ 检测到坐标轴点击: {gizmoAxis}")
|
||||
# 开始坐标轴拖拽
|
||||
self.world.selection.startGizmoDrag(gizmoAxis, x, y)
|
||||
pickerNP.removeNode()
|
||||
@ -203,16 +203,16 @@ class EventHandler:
|
||||
traceback.print_exc()
|
||||
print("继续处理模型选择...")
|
||||
|
||||
print("继续处理碰撞结果...")
|
||||
#print("继续处理碰撞结果...")
|
||||
|
||||
if hitPos and hitNode:
|
||||
print(f"✓ 检测到碰撞,开始处理点击事件")
|
||||
print(f"GUI编辑模式: {self.world.guiEditMode}")
|
||||
print(f"当前工具: {self.world.currentTool}")
|
||||
#print(f"✓ 检测到碰撞,开始处理点击事件")
|
||||
#print(f"GUI编辑模式: {self.world.guiEditMode}")
|
||||
#print(f"当前工具: {self.world.currentTool}")
|
||||
|
||||
# 处理GUI编辑模式
|
||||
if self.world.guiEditMode:
|
||||
print("处理GUI编辑模式点击")
|
||||
#print("处理GUI编辑模式点击")
|
||||
# 检查是否点击了GUI元素
|
||||
clickedGUI = self.world.gui_manager.findClickedGUI(hitNode)
|
||||
if clickedGUI:
|
||||
@ -411,103 +411,6 @@ class EventHandler:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
def mousePressEventRight(self,evt):
|
||||
"""处理鼠标右键按下事件"""
|
||||
print(f"当前工具: {self.world.currentTool}")
|
||||
|
||||
# 检查是否是地形编辑模式
|
||||
if self.world.currentTool == "地形编辑":
|
||||
self._handleTerrainEdit(evt, "subtract") # 降低地形
|
||||
return
|
||||
|
||||
# 其他右键处理逻辑可以在这里添加
|
||||
print("鼠标右键事件处理")
|
||||
|
||||
def _handleTerrainEdit(self,evt,operation):
|
||||
try:
|
||||
x = evt.get('x',0)
|
||||
y = evt.get('y',0)
|
||||
|
||||
winWidth,winHeight = self.world.getWindowSize()
|
||||
|
||||
mx = 2.0 * x/float(winWidth) - 1.0
|
||||
my = 1.0 -2.0*y/float(winHeight)
|
||||
|
||||
nearPoint = Point3()
|
||||
farPoint = Point3()
|
||||
self.world.cam.node().getLens().extrude(Point2(mx, my), nearPoint, farPoint)
|
||||
|
||||
worldNearPoint = self.world.render.getRelativePoint(self.world.cam, nearPoint)
|
||||
worldFarPoint = self.world.render.getRelativePoint(self.world.cam, farPoint)
|
||||
|
||||
picker = CollisionTraverser()
|
||||
queue = CollisionHandlerQueue()
|
||||
|
||||
pickerNode = CollisionNode('terrain_edit_ray')
|
||||
pickerNP = self.world.cam.attachNewNode(pickerNode)
|
||||
|
||||
from panda3d.core import BitMask32
|
||||
pickerNode.setFromCollideMask(BitMask32.allOn()) # 检查所有碰撞
|
||||
|
||||
# 使用相机坐标系的点创建射线
|
||||
direction = farPoint - nearPoint
|
||||
direction.normalize()
|
||||
pickerNode.addSolid(CollisionRay(nearPoint, direction))
|
||||
|
||||
picker.addCollider(pickerNP, queue)
|
||||
picker.traverse(self.world.render)
|
||||
print(f"地形碰撞检测结果数量: {queue.getNumEntries()}")
|
||||
|
||||
# 射线检测结果处理
|
||||
hitPos = None
|
||||
hitNode = None
|
||||
|
||||
if queue.getNumEntries() > 0:
|
||||
# 遍历所有碰撞结果,找到地形节点
|
||||
for i in range(queue.getNumEntries()):
|
||||
entry = queue.getEntry(i)
|
||||
collided_node = entry.getIntoNodePath()
|
||||
print(f"碰撞到节点: {collided_node.getName()}")
|
||||
|
||||
# 检查是否是地形节点
|
||||
for terrain_info in self.world.terrain_manager.terrains:
|
||||
terrain_node = terrain_info['node']
|
||||
if collided_node == terrain_node or terrain_node.isAncestorOf(collided_node):
|
||||
hitPos = entry.getSurfacePoint(self.world.render)
|
||||
hitNode = collided_node
|
||||
print(f"找到地形节点: {terrain_node.getName()}")
|
||||
|
||||
# 修改地形高度
|
||||
x_pos, y_pos = hitPos.getX(), hitPos.getY()
|
||||
success = self.world.modifyTerrainHeight(
|
||||
terrain_info, x_pos, y_pos, radius=3.0, strength=0.3, operation=operation)
|
||||
|
||||
if success:
|
||||
print(f"✓ 地形编辑成功: {operation} at ({x_pos:.2f}, {y_pos:.2f})")
|
||||
# 显示射线
|
||||
self.showClickRay(worldNearPoint, worldFarPoint, hitPos)
|
||||
else:
|
||||
print("✗ 地形编辑失败")
|
||||
break
|
||||
|
||||
if hitPos:
|
||||
break
|
||||
|
||||
if not hitPos:
|
||||
print("没有检测到地形碰撞")
|
||||
# 显示射线(无碰撞)
|
||||
self.showClickRay(worldNearPoint, worldFarPoint)
|
||||
|
||||
# 清理碰撞检测节点
|
||||
pickerNP.removeNode()
|
||||
print("地形编辑处理完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f"地形编辑处理出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def _handleSelectionClick(self, hitNode):
|
||||
"""处理选择工具的点击事件"""
|
||||
print(f"开始处理选择点击,碰撞节点: {hitNode.getName()}")
|
||||
@ -541,28 +444,28 @@ class EventHandler:
|
||||
selectedModel = model
|
||||
print(f"找到父模型: {selectedModel.getName()}")
|
||||
break
|
||||
|
||||
|
||||
if selectedModel:
|
||||
break
|
||||
|
||||
current = current.getParent()
|
||||
|
||||
if selectedModel:
|
||||
print(f"✓ 最终选中模型: {selectedModel.getName()}")
|
||||
#print(f"✓ 最终选中模型: {selectedModel.getName()}")
|
||||
|
||||
# 更新选择状态并显示选择框和坐标轴
|
||||
self.world.selection.updateSelection(selectedModel)
|
||||
|
||||
# 在树形控件中查找并选中对应的项
|
||||
if self.world.interface_manager.treeWidget:
|
||||
print("查找树形控件中的对应项...")
|
||||
#print("查找树形控件中的对应项...")
|
||||
root = self.world.interface_manager.treeWidget.invisibleRootItem()
|
||||
foundItem = None
|
||||
|
||||
for i in range(root.childCount()):
|
||||
sceneItem = root.child(i)
|
||||
if sceneItem.text(0) == "场景":
|
||||
print(f"在场景节点下查找...")
|
||||
#print(f"在场景节点下查找...")
|
||||
foundItem = self.world.interface_manager.findTreeItem(selectedModel, sceneItem)
|
||||
if foundItem:
|
||||
print(f"✓ 在树形控件中找到对应项: {foundItem.text(0)}")
|
||||
@ -578,6 +481,7 @@ class EventHandler:
|
||||
print("× 树形控件不存在")
|
||||
else:
|
||||
print("× 没有找到可选择的模型节点")
|
||||
self.world.selection.updateSelection(None)
|
||||
|
||||
def mouseReleaseEventLeft(self, evt):
|
||||
"""处理鼠标左键释放事件"""
|
||||
|
||||
495
core/patrol_system.py
Normal file
495
core/patrol_system.py
Normal file
@ -0,0 +1,495 @@
|
||||
from direct.showbase.ShowBaseGlobal import globalClock
|
||||
from direct.task.TaskManagerGlobal import taskMgr
|
||||
from panda3d.core import Point3, Vec3
|
||||
import math
|
||||
|
||||
|
||||
class PatrolSystem:
|
||||
"""巡检系统类"""
|
||||
|
||||
def __init__(self, world):
|
||||
"""初始化巡检系统
|
||||
|
||||
Args:
|
||||
world: 核心世界对象引用
|
||||
"""
|
||||
self.world = world
|
||||
|
||||
# 巡检状态
|
||||
self.is_patrolling = False
|
||||
self.patrol_points = [] # 巡检点列表 [(pos, hpr, wait_time), ...]
|
||||
self.current_patrol_index = 0
|
||||
self.patrol_task = None
|
||||
|
||||
# 巡检参数
|
||||
self.patrol_speed = 5.0 # 巡检移动速度(单位/秒)
|
||||
self.patrol_turn_speed = 90.0 # 转向速度(度/秒)
|
||||
self.patrol_wait_timer = 0.0
|
||||
self.patrol_state = "moving" # "moving", "turning_to_target", "waiting", "turning_back"
|
||||
|
||||
# 相机状态保存
|
||||
self.original_cam_pos = None
|
||||
self.original_cam_hpr = None
|
||||
|
||||
print("✓ 巡检系统初始化完成")
|
||||
|
||||
def add_patrol_point(self, position, heading=None, wait_time=3.0):
|
||||
if heading is None:
|
||||
if self.patrol_points:
|
||||
last_pos = self.patrol_points[-1][0]
|
||||
direction_x = position[0] - last_pos.x
|
||||
direction_y = position[1] - last_pos.y
|
||||
direction_z = position[2] - last_pos.z
|
||||
|
||||
import math
|
||||
h=math.degrees(math.atan2(-direction_x,-direction_y))
|
||||
|
||||
distance_xy = math.sqrt(direction_x**2+direction_y**2)
|
||||
p = math.degrees(math.atan2(direction_z,distance_xy))
|
||||
p = max(-89,min(89,p))
|
||||
|
||||
r=0
|
||||
|
||||
heading = (h,p,r)
|
||||
|
||||
else:
|
||||
# 使用当前相机朝向
|
||||
current_hpr = self.world.cam.getHpr()
|
||||
heading = (current_hpr.x, current_hpr.y, current_hpr.z)
|
||||
|
||||
pos = Point3(position[0], position[1], position[2])
|
||||
hpr = Vec3(heading[0], heading[1], heading[2])
|
||||
|
||||
self.patrol_points.append((pos, hpr, wait_time))
|
||||
print(f"✓ 添加巡检点 {len(self.patrol_points)}: 位置{position}, 朝向{heading}, 停留{wait_time}秒")
|
||||
|
||||
# 在 PatrolSystem 类中添加以下方法
|
||||
|
||||
def add_auto_heading_patrol_point(self, position, wait_time=3.0):
|
||||
"""添加自动计算朝向的巡检点(朝向路径前进方向)
|
||||
|
||||
Args:
|
||||
position: 相机位置 (x, y, z)
|
||||
wait_time: 在该点停留时间(秒)
|
||||
"""
|
||||
heading = None # 将自动计算朝向
|
||||
|
||||
# 复用原有的 add_patrol_point 方法
|
||||
self.add_patrol_point(position, heading, wait_time)
|
||||
|
||||
def add_patrol_point_looking_at(self, position, look_at_position, wait_time=3.0):
|
||||
"""添加朝向指定位置的巡检点
|
||||
|
||||
Args:
|
||||
position: 相机位置 (x, y, z)
|
||||
look_at_position: 相机朝向的目标位置 (x, y, z)
|
||||
wait_time: 在该点停留时间(秒)
|
||||
"""
|
||||
import math
|
||||
|
||||
# 计算从当前位置到目标位置的方向向量
|
||||
direction_x = look_at_position[0] - position[0]
|
||||
direction_y = look_at_position[1] - position[1]
|
||||
direction_z = look_at_position[2] - position[2]
|
||||
|
||||
# 计算HPR朝向
|
||||
h = math.degrees(math.atan2(-direction_x, -direction_y))
|
||||
|
||||
distance_xy = math.sqrt(direction_x ** 2 + direction_y ** 2)
|
||||
p = math.degrees(math.atan2(direction_z, distance_xy))
|
||||
p = max(-89, min(89, p)) # 限制pitch角度在合理范围内
|
||||
|
||||
r = 0 # roll通常为0
|
||||
|
||||
heading = (h, p, r)
|
||||
self.add_patrol_point(position, heading, wait_time)
|
||||
|
||||
def clear_patrol_points(self):
|
||||
"""清空所有巡检点"""
|
||||
self.patrol_points = []
|
||||
print("✓ 巡检点已清空")
|
||||
|
||||
def set_patrol_speed(self, move_speed, turn_speed=None):
|
||||
"""设置巡检速度
|
||||
|
||||
Args:
|
||||
move_speed: 移动速度(单位/秒)
|
||||
turn_speed: 转向速度(度/秒),如果为None则保持当前值
|
||||
"""
|
||||
self.patrol_speed = move_speed
|
||||
if turn_speed is not None:
|
||||
self.patrol_turn_speed = turn_speed
|
||||
print(f"✓ 巡检速度已设置: 移动{move_speed}, 转向{turn_speed or self.patrol_turn_speed}")
|
||||
|
||||
def start_patrol(self):
|
||||
"""开始巡检"""
|
||||
if not self.patrol_points:
|
||||
print("✗ 没有设置巡检点,无法开始巡检")
|
||||
return False
|
||||
|
||||
if self.is_patrolling:
|
||||
print("⚠ 巡检已在进行中")
|
||||
return True
|
||||
|
||||
# 保存当前相机状态
|
||||
self.original_cam_pos = Point3(self.world.cam.getPos())
|
||||
self.original_cam_hpr = Vec3(self.world.cam.getHpr())
|
||||
|
||||
# 重置巡检状态
|
||||
self.current_patrol_index = 0
|
||||
self.patrol_state = "moving"
|
||||
self.patrol_wait_timer = 0.0
|
||||
self.is_patrolling = True
|
||||
|
||||
# 启动巡检任务
|
||||
if self.patrol_task:
|
||||
taskMgr.remove(self.patrol_task)
|
||||
self.patrol_task = taskMgr.add(self._patrol_task, "patrol_task")
|
||||
|
||||
print(f"✓ 开始巡检,共{len(self.patrol_points)}个巡检点")
|
||||
return True
|
||||
|
||||
def stop_patrol(self):
|
||||
"""停止巡检"""
|
||||
if not self.is_patrolling:
|
||||
print("⚠ 巡检未在进行中")
|
||||
return False
|
||||
|
||||
# 停止巡检任务
|
||||
if self.patrol_task:
|
||||
taskMgr.remove(self.patrol_task)
|
||||
self.patrol_task = None
|
||||
|
||||
self.is_patrolling = False
|
||||
self.patrol_state = "moving"
|
||||
self.patrol_wait_timer = 0.0
|
||||
|
||||
print("✓ 巡检已停止")
|
||||
return True
|
||||
|
||||
def pause_patrol(self):
|
||||
"""暂停巡检"""
|
||||
if not self.is_patrolling:
|
||||
print("⚠ 巡检未在进行中")
|
||||
return False
|
||||
|
||||
if self.patrol_task:
|
||||
taskMgr.remove(self.patrol_task)
|
||||
self.patrol_task = None
|
||||
|
||||
print("✓ 巡检已暂停")
|
||||
return True
|
||||
|
||||
def resume_patrol(self):
|
||||
"""恢复巡检"""
|
||||
if self.is_patrolling:
|
||||
print("⚠ 巡检已在进行中")
|
||||
return False
|
||||
|
||||
if not self.patrol_points:
|
||||
print("✗ 没有设置巡检点")
|
||||
return False
|
||||
|
||||
self.is_patrolling = True
|
||||
self.patrol_task = taskMgr.add(self._patrol_task, "patrol_task")
|
||||
|
||||
print("✓ 巡检已恢复")
|
||||
return True
|
||||
|
||||
def reset_to_original_position(self):
|
||||
"""重置相机到原始位置"""
|
||||
if self.original_cam_pos and self.original_cam_hpr:
|
||||
self.world.cam.setPos(self.original_cam_pos)
|
||||
self.world.cam.setHpr(self.original_cam_hpr)
|
||||
print("✓ 相机已重置到原始位置")
|
||||
return True
|
||||
else:
|
||||
print("✗ 没有保存的原始位置")
|
||||
return False
|
||||
|
||||
def _patrol_task(self, task):
|
||||
"""巡检主任务"""
|
||||
try:
|
||||
if not self.is_patrolling or not self.patrol_points:
|
||||
return task.done
|
||||
|
||||
# 获取当前巡检点
|
||||
current_point = self.patrol_points[self.current_patrol_index]
|
||||
target_pos, target_hpr, wait_time = current_point
|
||||
|
||||
# 根据当前状态执行不同操作
|
||||
if self.patrol_state == "moving":
|
||||
self._handle_moving_state(target_pos)
|
||||
elif self.patrol_state == "turning_to_target":
|
||||
self._handle_turning_to_target_state(target_hpr)
|
||||
elif self.patrol_state == "waiting":
|
||||
self._handle_waiting_state(wait_time)
|
||||
elif self.patrol_state == "turning_back":
|
||||
self._handle_turning_back_state()
|
||||
|
||||
return task.cont
|
||||
|
||||
except Exception as e:
|
||||
print(f"巡检任务出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return task.done
|
||||
|
||||
def _handle_moving_state(self, target_pos):
|
||||
"""处理移动状态"""
|
||||
current_pos = self.world.cam.getPos()
|
||||
distance = (target_pos - current_pos).length()
|
||||
|
||||
if distance < 0.1: # 到达目标点
|
||||
print(f"✓ 到达巡检点 {self.current_patrol_index + 1}")
|
||||
self.patrol_state = "turning_to_target"
|
||||
return
|
||||
|
||||
# 计算移动方向和距离
|
||||
direction = target_pos - current_pos
|
||||
direction.normalize()
|
||||
|
||||
# 计算目标朝向(看向目标点)
|
||||
target_hpr = self._look_at_to_hpr(direction)
|
||||
current_hpr = self.world.cam.getHpr()
|
||||
|
||||
# 平滑转向到目标朝向
|
||||
h_diff = self._normalize_angle(target_hpr.x - current_hpr.x)
|
||||
p_diff = self._normalize_angle(target_hpr.y - current_hpr.y)
|
||||
r_diff = self._normalize_angle(target_hpr.z - current_hpr.z)
|
||||
|
||||
# 计算本帧应转动的角度
|
||||
dt = globalClock.getDt()
|
||||
turn_amount = self.patrol_turn_speed * dt
|
||||
|
||||
# 逐步转向目标角度
|
||||
new_hpr = Vec3(current_hpr)
|
||||
|
||||
if abs(h_diff) > turn_amount:
|
||||
new_hpr.x += turn_amount if h_diff > 0 else -turn_amount
|
||||
else:
|
||||
new_hpr.x = target_hpr.x
|
||||
|
||||
if abs(p_diff) > turn_amount:
|
||||
new_hpr.y += turn_amount if p_diff > 0 else -turn_amount
|
||||
else:
|
||||
new_hpr.y = target_hpr.y
|
||||
|
||||
if abs(r_diff) > turn_amount:
|
||||
new_hpr.z += turn_amount if r_diff > 0 else -turn_amount
|
||||
else:
|
||||
new_hpr.z = target_hpr.z
|
||||
|
||||
self.world.cam.setHpr(new_hpr)
|
||||
|
||||
# 计算本帧应移动的距离
|
||||
move_distance = self.patrol_speed * dt
|
||||
|
||||
# 如果移动距离大于剩余距离,则直接移动到目标点
|
||||
if move_distance >= distance:
|
||||
self.world.cam.setPos(target_pos)
|
||||
else:
|
||||
# 否则按方向移动
|
||||
new_pos = current_pos + direction * move_distance
|
||||
self.world.cam.setPos(new_pos)
|
||||
|
||||
def _handle_turning_to_target_state(self, target_hpr):
|
||||
"""处理转向目标状态"""
|
||||
# 检查是否需要朝向下一个点
|
||||
if target_hpr == "look_next":
|
||||
# 计算朝向下一个点的方向
|
||||
next_index = (self.current_patrol_index + 1) % len(self.patrol_points)
|
||||
next_point_pos = self.patrol_points[next_index][0]
|
||||
|
||||
current_pos = self.world.cam.getPos()
|
||||
direction = next_point_pos - current_pos
|
||||
direction.normalize()
|
||||
|
||||
# 计算目标朝向
|
||||
target_hpr = self._look_at_to_hpr(direction)
|
||||
|
||||
current_hpr = self.world.cam.getHpr()
|
||||
|
||||
# 计算角度差
|
||||
h_diff = self._normalize_angle(target_hpr.x - current_hpr.x)
|
||||
p_diff = self._normalize_angle(target_hpr.y - current_hpr.y)
|
||||
r_diff = self._normalize_angle(target_hpr.z - current_hpr.z)
|
||||
|
||||
# 检查是否已完成转向
|
||||
if abs(h_diff) < 1.0 and abs(p_diff) < 1.0 and abs(r_diff) < 1.0:
|
||||
print(f"✓ 完成转向,开始停留")
|
||||
self.patrol_state = "waiting"
|
||||
self.patrol_wait_timer = 0.0
|
||||
return
|
||||
|
||||
# 计算本帧应转动的角度
|
||||
dt = globalClock.getDt()
|
||||
turn_amount = self.patrol_turn_speed * dt
|
||||
|
||||
# 逐步转向目标角度
|
||||
new_hpr = Vec3(current_hpr)
|
||||
|
||||
if abs(h_diff) > turn_amount:
|
||||
new_hpr.x += turn_amount if h_diff > 0 else -turn_amount
|
||||
else:
|
||||
new_hpr.x = target_hpr.x
|
||||
|
||||
if abs(p_diff) > turn_amount:
|
||||
new_hpr.y += turn_amount if p_diff > 0 else -turn_amount
|
||||
else:
|
||||
new_hpr.y = target_hpr.y
|
||||
|
||||
if abs(r_diff) > turn_amount:
|
||||
new_hpr.z += turn_amount if r_diff > 0 else -turn_amount
|
||||
else:
|
||||
new_hpr.z = target_hpr.z
|
||||
|
||||
self.world.cam.setHpr(new_hpr)
|
||||
|
||||
def _handle_waiting_state(self, wait_time):
|
||||
"""处理等待状态"""
|
||||
self.patrol_wait_timer += globalClock.getDt()
|
||||
|
||||
if self.patrol_wait_timer >= wait_time:
|
||||
print(f"✓ 停留结束,准备转回原朝向")
|
||||
self.patrol_state = "turning_back"
|
||||
|
||||
# 修改 core/patrol_system.py 中的 _handle_turning_back_state 方法
|
||||
|
||||
def _handle_turning_back_state(self):
|
||||
"""处理转回原朝向状态"""
|
||||
# 直接完成转向状态,进入移动状态
|
||||
print(f"✓ 停留结束,开始移动到下一个点")
|
||||
# 移动到下一个巡检点
|
||||
next_index = (self.current_patrol_index + 1) % len(self.patrol_points)
|
||||
self.current_patrol_index = next_index
|
||||
self.patrol_state = "moving"
|
||||
return
|
||||
|
||||
def _normalize_angle(self, angle):
|
||||
"""规范化角度到-180到180度之间"""
|
||||
while angle > 180:
|
||||
angle -= 360
|
||||
while angle < -180:
|
||||
angle += 360
|
||||
return angle
|
||||
|
||||
def _look_at_to_hpr(self, direction):
|
||||
"""将方向向量转换为HPR角度"""
|
||||
# 简化的转换,实际应用中可能需要更精确的计算
|
||||
h = math.degrees(math.atan2(-direction.x, -direction.y))
|
||||
p = math.degrees(math.asin(direction.z))
|
||||
return Vec3(h, p, 0)
|
||||
|
||||
def get_patrol_status(self):
|
||||
"""获取巡检状态信息"""
|
||||
return {
|
||||
"is_patrolling": self.is_patrolling,
|
||||
"current_point": self.current_patrol_index,
|
||||
"total_points": len(self.patrol_points),
|
||||
"state": self.patrol_state,
|
||||
"wait_timer": self.patrol_wait_timer
|
||||
}
|
||||
|
||||
def list_patrol_points(self):
|
||||
"""列出所有巡检点"""
|
||||
if not self.patrol_points:
|
||||
print("没有设置巡检点")
|
||||
return
|
||||
|
||||
print(f"巡检点列表 (共{len(self.patrol_points)}个):")
|
||||
for i, (pos, hpr, wait_time) in enumerate(self.patrol_points):
|
||||
current_marker = " >>>" if i == self.current_patrol_index and self.is_patrolling else ""
|
||||
print(f" {i + 1}. 位置:({pos.x:.1f}, {pos.y:.1f}, {pos.z:.1f}) "
|
||||
f"朝向:({hpr.x:.1f}, {hpr.y:.1f}, {hpr.z:.1f}) "
|
||||
f"停留:{wait_time}秒{current_marker}")
|
||||
|
||||
def remove_patrol_point(self, index):
|
||||
"""移除指定索引的巡检点"""
|
||||
if 0 <= index < len(self.patrol_points):
|
||||
removed_point = self.patrol_points.pop(index)
|
||||
print(
|
||||
f"✓ 移除巡检点 {index + 1}: 位置({removed_point[0].x:.1f}, {removed_point[0].y:.1f}, {removed_point[0].z:.1f})")
|
||||
|
||||
# 调整当前索引
|
||||
if self.current_patrol_index >= len(self.patrol_points) and self.patrol_points:
|
||||
self.current_patrol_index = len(self.patrol_points) - 1
|
||||
elif self.current_patrol_index >= len(self.patrol_points):
|
||||
self.current_patrol_index = 0
|
||||
else:
|
||||
print(f"✗ 无效的巡检点索引: {index}")
|
||||
|
||||
def insert_patrol_point(self, index, position, heading=None, wait_time=3.0):
|
||||
"""在指定位置插入巡检点"""
|
||||
if index < 0 or index > len(self.patrol_points):
|
||||
print(f"✗ 无效的插入位置: {index}")
|
||||
return
|
||||
|
||||
if heading is None:
|
||||
# 使用当前相机朝向
|
||||
current_hpr = self.world.cam.getHpr()
|
||||
heading = (current_hpr.x, current_hpr.y, current_hpr.z)
|
||||
|
||||
pos = Point3(position[0], position[1], position[2])
|
||||
hpr = Vec3(heading[0], heading[1], heading[2])
|
||||
|
||||
self.patrol_points.insert(index, (pos, hpr, wait_time))
|
||||
print(f"✓ 在位置 {index + 1} 插入巡检点: 位置{position}, 朝向{heading}, 停留{wait_time}秒")
|
||||
|
||||
def update_patrol_point(self, index, position=None, heading=None, wait_time=None):
|
||||
"""更新指定巡检点的信息"""
|
||||
if 0 <= index < len(self.patrol_points):
|
||||
pos, hpr, wt = self.patrol_points[index]
|
||||
|
||||
if position is not None:
|
||||
pos = Point3(position[0], position[1], position[2])
|
||||
if heading is not None:
|
||||
hpr = Vec3(heading[0], heading[1], heading[2])
|
||||
if wait_time is not None:
|
||||
wt = wait_time
|
||||
|
||||
self.patrol_points[index] = (pos, hpr, wt)
|
||||
print(f"✓ 更新巡检点 {index + 1}")
|
||||
else:
|
||||
print(f"✗ 无效的巡检点索引: {index}")
|
||||
|
||||
def goto_patrol_point(self, index):
|
||||
"""直接跳转到指定巡检点"""
|
||||
if not self.patrol_points:
|
||||
print("✗ 没有设置巡检点")
|
||||
return False
|
||||
|
||||
if 0 <= index < len(self.patrol_points):
|
||||
pos, hpr, _ = self.patrol_points[index]
|
||||
self.world.cam.setPos(pos)
|
||||
self.world.cam.setHpr(hpr)
|
||||
self.current_patrol_index = index
|
||||
print(f"✓ 跳转到巡检点 {index + 1}")
|
||||
return True
|
||||
else:
|
||||
print(f"✗ 无效的巡检点索引: {index}")
|
||||
return False
|
||||
|
||||
def cleanup(self):
|
||||
"""清理巡检系统资源"""
|
||||
self.stop_patrol()
|
||||
self.clear_patrol_points()
|
||||
self.original_cam_pos = None
|
||||
self.original_cam_hpr = None
|
||||
print("✓ 巡检系统资源已清理")
|
||||
|
||||
|
||||
# 使用示例和便捷函数
|
||||
def create_default_patrol_route(patrol_system):
|
||||
"""创建默认的巡检路线(示例)"""
|
||||
# 清空现有巡检点
|
||||
patrol_system.clear_patrol_points()
|
||||
|
||||
# 添加一些示例巡检点
|
||||
patrol_system.add_patrol_point((0, -20, 5), (0, -15, 0), 2.0) # 点1:前方低位置
|
||||
patrol_system.add_patrol_point((0, 0, 10), (0, -30, 0), 3.0) # 点2:中央高位置
|
||||
patrol_system.add_patrol_point((15, 10, 5), (-45, -10, 0), 2.5) # 点3:右侧位置
|
||||
patrol_system.add_patrol_point((-15, 10, 5), (45, -10, 0), 2.5) # 点4:左侧位置
|
||||
|
||||
print("✓ 默认巡检路线已创建")
|
||||
|
||||
@ -769,6 +769,36 @@ class {class_name}(ScriptBase):
|
||||
|
||||
print("==================\n")
|
||||
|
||||
def save_object_scripts(self,game_object,node_data:dict):
|
||||
try:
|
||||
if game_object in self.object_scripts:
|
||||
scripts_data = []
|
||||
for script_commponent in self.object_scripts[game_object]:
|
||||
script_info = {
|
||||
'script_name':script_commponent.script_name,
|
||||
'enabled':script_commponent.enabled,
|
||||
'script_class':script_commponent.script_instance.__class__.__name__
|
||||
}
|
||||
scripts_data.append(script_info)
|
||||
if scripts_data:
|
||||
node_data['scripts'] = scripts_data
|
||||
print(f"✓ 保存了 {len(scripts_data)} 个脚本到对象 {game_object.getName()}")
|
||||
except Exception as e:
|
||||
print(f"保存对象脚本信息失败: {e}")
|
||||
traceback.print_exc()
|
||||
|
||||
# def restore_object_scripts(self,game_object,node_data:dict):
|
||||
# try:
|
||||
# if 'scripts' in node_data:
|
||||
# scripts_data = node_data['scripts']
|
||||
# restored_count = 0
|
||||
# for script_info in scripts_data:
|
||||
# script_name = script_info.get('script_name')
|
||||
# enabled = script_info.get('enabled',True)
|
||||
#
|
||||
# #检查脚本是否可用
|
||||
# if script_name in self.loader.script_classes:
|
||||
#为
|
||||
|
||||
# 添加全局便捷函数,让脚本更容易使用API
|
||||
def get_script_api():
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
- 射线检测和碰撞检测
|
||||
"""
|
||||
from PIL.ImageChops import lighter
|
||||
from direct.showbase.ShowBaseGlobal import globalClock
|
||||
from panda3d.core import (Vec3, Point3, Point2, LineSegs, ColorAttrib, RenderState,
|
||||
DepthTestAttrib, CollisionTraverser, CollisionHandlerQueue,
|
||||
CollisionNode, CollisionRay, GeomNode, BitMask32, Material, LColor, DepthWriteAttrib,
|
||||
@ -66,6 +67,11 @@ class SelectionSystem:
|
||||
self._current_cursor = None
|
||||
self._default_cursor = None
|
||||
|
||||
self._last_click_time = 0
|
||||
self._double_click_threshold = 0.3
|
||||
self._last_clicked_node = None
|
||||
self._double_click_task = None
|
||||
|
||||
print("✓ 选择和变换系统初始化完成")
|
||||
# ==================== 光标设置 ====================
|
||||
def _setCursor(self,cursor_type):
|
||||
@ -112,7 +118,7 @@ class SelectionSystem:
|
||||
def createSelectionBox(self, nodePath):
|
||||
"""为选中的节点创建选择框"""
|
||||
try:
|
||||
print(f" 开始创建选择框,目标节点: {nodePath.getName()}")
|
||||
#print(f" 开始创建选择框,目标节点: {nodePath.getName()}")
|
||||
|
||||
# 如果已有选择框,先移除
|
||||
if self.selectionBox:
|
||||
@ -127,17 +133,17 @@ class SelectionSystem:
|
||||
# 创建选择框作为render的子节点,但会实时跟踪目标节点
|
||||
self.selectionBox = self.world.render.attachNewNode("selectionBox")
|
||||
self.selectionBoxTarget = nodePath # 保存目标节点引用
|
||||
print(f" 选择框节点创建完成: {self.selectionBox}")
|
||||
#print(f" 选择框节点创建完成: {self.selectionBox}")
|
||||
|
||||
# 启动选择框更新任务
|
||||
taskMgr.add(self.updateSelectionBoxTask, "updateSelectionBox")
|
||||
print(" 选择框更新任务已启动")
|
||||
#print(" 选择框更新任务已启动")
|
||||
|
||||
# 初始更新选择框
|
||||
print(" 开始初始化选择框几何体...")
|
||||
#print(" 开始初始化选择框几何体...")
|
||||
self.updateSelectionBoxGeometry()
|
||||
|
||||
print(f" ✓ 为节点 {nodePath.getName()} 创建了选择框")
|
||||
#print(f" ✓ 为节点 {nodePath.getName()} 创建了选择框")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ✗ 创建选择框失败: {str(e)}")
|
||||
@ -335,7 +341,7 @@ class SelectionSystem:
|
||||
def createGizmo(self, nodePath):
|
||||
"""为选中的节点创建坐标轴工具 - 保留箭头版本"""
|
||||
try:
|
||||
print(f" 开始创建坐标轴,目标节点: {nodePath.getName()}")
|
||||
#print(f" 开始创建坐标轴,目标节点: {nodePath.getName()}")
|
||||
|
||||
# 如果已有坐标轴,先移除
|
||||
if self.gizmo:
|
||||
@ -350,6 +356,22 @@ class SelectionSystem:
|
||||
self.gizmo = self.world.render.attachNewNode("gizmo")
|
||||
self.gizmoTarget = nodePath
|
||||
|
||||
# 添加标识标签,便于识别
|
||||
self.gizmo.setTag("is_gizmo", "1")
|
||||
if hasattr(nodePath, 'getName'):
|
||||
self.gizmo.setTag("gizmo_target", nodePath.getName())
|
||||
|
||||
# 为各轴添加标签
|
||||
if hasattr(self, 'gizmoXAxis') and self.gizmoXAxis:
|
||||
self.gizmoXAxis.setTag("is_gizmo", "1")
|
||||
self.gizmoXAxis.setTag("gizmo_axis", "x")
|
||||
if hasattr(self, 'gizmoYAxis') and self.gizmoYAxis:
|
||||
self.gizmoYAxis.setTag("is_gizmo", "1")
|
||||
self.gizmoYAxis.setTag("gizmo_axis", "y")
|
||||
if hasattr(self, 'gizmoZAxis') and self.gizmoZAxis:
|
||||
self.gizmoZAxis.setTag("is_gizmo", "1")
|
||||
self.gizmoZAxis.setTag("gizmo_axis", "z")
|
||||
|
||||
# 设置位置和朝向
|
||||
minPoint = Point3()
|
||||
maxPoint = Point3()
|
||||
@ -385,7 +407,7 @@ class SelectionSystem:
|
||||
# 只启动一次更新任务
|
||||
taskMgr.add(self.updateGizmoTask, "updateGizmo")
|
||||
|
||||
print(f" ✓ 为节点 {nodePath.getName()} 创建了坐标轴")
|
||||
#print(f" ✓ 为节点 {nodePath.getName()} 创建了坐标轴")
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建坐标轴失败: {str(e)}")
|
||||
@ -431,7 +453,7 @@ class SelectionSystem:
|
||||
else:
|
||||
gizmo_model = self.world.loader.loadModel(path)
|
||||
if gizmo_model:
|
||||
print(f"成功加载模型: {path}")
|
||||
#print(f"成功加载模型: {path}")
|
||||
break
|
||||
except:
|
||||
continue
|
||||
@ -1177,7 +1199,7 @@ class SelectionSystem:
|
||||
return self.checkGizmoClickFallback(mouseX, mouseY)
|
||||
|
||||
# 计算点击阈值
|
||||
click_threshold = 30 # 增大检测范围
|
||||
click_threshold = 15 # 增大检测范围
|
||||
|
||||
# 检测各个轴,对于端点在屏幕外的轴提供回退方案
|
||||
def getClickDetectionPoint(axis_name, original_screen_pos):
|
||||
@ -1657,14 +1679,12 @@ class SelectionSystem:
|
||||
|
||||
if parent_node and parent_node != self.world.render:
|
||||
try:
|
||||
if parent_node.getTransform().hasMat():
|
||||
transform_mat = parent_node.getMat(self.world.render)
|
||||
if not transform_mat.isSingular():
|
||||
world_axis_vector = transform_mat.xformVec(local_axis_vector)
|
||||
else:
|
||||
print("警告: 检测到奇异变换矩阵,使用默认轴向量")
|
||||
#获取变换矩阵
|
||||
transfrom_mat = parent_node.getMat(self.world.render)
|
||||
if transfrom_mat.is_identity() or self._isMatrixValid(transfrom_mat):
|
||||
world_axis_vector = transfrom_mat.xformVec(local_axis_vector)
|
||||
else:
|
||||
print("警告: 父节点没有有效的变换矩阵,使用默认轴向量")
|
||||
print("警告: 检测到无效变换矩阵,使用默认轴向量")
|
||||
except Exception as e:
|
||||
print(f"变换计算出错: {e},使用默认轴向量")
|
||||
else:
|
||||
@ -1812,6 +1832,23 @@ class SelectionSystem:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def _isMatrixValid(self, matrix):
|
||||
"""检查矩阵是否有效,替代 isSingular 方法"""
|
||||
try:
|
||||
# 检查矩阵元素是否为有效数字
|
||||
for i in range(4):
|
||||
for j in range(4):
|
||||
element = matrix.getCell(i, j)
|
||||
# 检查是否为 NaN 或无穷大
|
||||
if str(element) == 'nan' or str(element) == 'inf' or str(element) == '-inf':
|
||||
return False
|
||||
# 检查是否过大
|
||||
if abs(element) > 1e10:
|
||||
return False
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def _safeUpdatePropertyPanel(self):
|
||||
"""安全地更新属性面板"""
|
||||
try:
|
||||
@ -1871,58 +1908,80 @@ class SelectionSystem:
|
||||
# ==================== 选择管理 ====================
|
||||
|
||||
def updateSelection(self, nodePath):
|
||||
"""更新选择状态"""
|
||||
print(f"\n=== 更新选择状态 ===")
|
||||
try:
|
||||
# 检查是否要选择的对象已经是当前选中的对象
|
||||
if self.selectedNode == nodePath:
|
||||
#print("要选择的对象已经是当前选中的对象,跳过重复更新")
|
||||
return
|
||||
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("✓ 取消选择")
|
||||
|
||||
#当取消选择时,同步清空树形控件的选中状态
|
||||
if (hasattr(self.world,'interface_manager')and
|
||||
self.world.interface_manager and
|
||||
self.world.interface_manager.treeWidget):
|
||||
self.world.interface_manager.treeWidget.setCurrentItem(None)
|
||||
print("✓ 树形控件选中状态已清空")
|
||||
|
||||
# 如果正在删除节点,避免更新选择
|
||||
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}")
|
||||
|
||||
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")
|
||||
except Exception as e:
|
||||
print(f"更新选择状态失败{str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def getSelectedNode(self):
|
||||
"""获取当前选中的节点"""
|
||||
@ -2010,7 +2069,7 @@ class SelectionSystem:
|
||||
|
||||
collision_np.hide() # 隐藏碰撞体,只用于检测
|
||||
|
||||
print(f"✓ 成功创建 {axis_name} 轴碰撞体")
|
||||
#print(f"✓ 成功创建 {axis_name} 轴碰撞体")
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建{axis_name}轴碰撞体失败: {e}")
|
||||
@ -2098,4 +2157,628 @@ class SelectionSystem:
|
||||
if not collision_node.isEmpty():
|
||||
print(f" - 碰撞体标签: {collision_node.getTag('gizmo_axis')}")
|
||||
else:
|
||||
print(f"{axis_name.upper()}轴节点不存在")
|
||||
print(f"{axis_name.upper()}轴节点不存在")
|
||||
|
||||
def focusCameraOnSelectedNodeAdvanced(self):
|
||||
"""高级版的摄像机聚焦功能,包含平滑动画效果"""
|
||||
try:
|
||||
if not self.selectedNode or self.selectedNode.isEmpty():
|
||||
print("没有选中的节点,无法聚焦")
|
||||
return False
|
||||
|
||||
# 获取选中节点的边界框
|
||||
minPoint = Point3()
|
||||
maxPoint = Point3()
|
||||
|
||||
if not self.selectedNode.calcTightBounds(minPoint, maxPoint, self.world.render):
|
||||
print("无法计算选中节点的边界框")
|
||||
return False
|
||||
|
||||
# 计算节点中心点和大小
|
||||
center = Point3(
|
||||
(minPoint.x + maxPoint.x) * 0.5,
|
||||
(minPoint.y + maxPoint.y) * 0.5,
|
||||
(minPoint.z + maxPoint.z) * 0.5
|
||||
)
|
||||
|
||||
# 计算节点的对角线长度
|
||||
size = (maxPoint - minPoint).length()
|
||||
|
||||
# 如果节点太小,使用默认大小
|
||||
if size < 0.01:
|
||||
size = 1.0
|
||||
|
||||
# 获取当前摄像机位置和朝向
|
||||
current_cam_pos = Point3(self.world.cam.getPos())
|
||||
current_cam_hpr = Vec3(self.world.cam.getHpr())
|
||||
|
||||
# 计算观察方向
|
||||
view_direction = current_cam_pos - center
|
||||
if view_direction.length() < 0.001:
|
||||
view_direction = Vec3(5, -5, 2)
|
||||
|
||||
view_direction.normalize()
|
||||
|
||||
# 计算合适的观察距离
|
||||
optimal_distance = max(size * 2.0, 5.0)
|
||||
|
||||
# 计算目标摄像机位置
|
||||
target_cam_pos = center + (view_direction * optimal_distance)
|
||||
|
||||
# 计算目标朝向(不直接使用lookAt,而是计算目标HPR)
|
||||
# 创建临时节点用于计算目标朝向
|
||||
temp_node = self.world.render.attachNewNode("temp_lookat_target")
|
||||
temp_node.setPos(center)
|
||||
|
||||
# 创建另一个临时节点用于计算朝向
|
||||
dummy_cam = self.world.render.attachNewNode("dummy_camera")
|
||||
dummy_cam.setPos(target_cam_pos)
|
||||
dummy_cam.lookAt(temp_node)
|
||||
target_cam_hpr = Vec3(dummy_cam.getHpr())
|
||||
|
||||
# 清理临时节点
|
||||
temp_node.removeNode()
|
||||
dummy_cam.removeNode()
|
||||
|
||||
# 使用任务来实现平滑移动动画
|
||||
self._startCameraFocusAnimation(current_cam_pos, target_cam_pos,
|
||||
current_cam_hpr, target_cam_hpr)
|
||||
|
||||
print(f"开始聚焦到节点: {self.selectedNode.getName()}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"高级聚焦功能失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def _startCameraFocusAnimation(self, start_pos, end_pos, start_hpr, end_hpr):
|
||||
"""启动摄像机聚焦动画"""
|
||||
try:
|
||||
# 创建动画任务
|
||||
class CameraFocusData:
|
||||
def __init__(self, start_pos, end_pos, start_hpr, end_hpr):
|
||||
self.start_pos = Point3(start_pos) # 确保是Point3类型
|
||||
self.end_pos = Point3(end_pos) # 确保是Point3类型
|
||||
self.start_hpr = Vec3(start_hpr) # 确保是Vec3类型
|
||||
self.end_hpr = Vec3(end_hpr) # 确保是Vec3类型
|
||||
self.elapsed_time = 0.0
|
||||
self.duration = 0.8 # 增加动画持续时间到0.8秒,让动画更平滑
|
||||
|
||||
self._camera_focus_data = CameraFocusData(start_pos, end_pos, start_hpr, end_hpr)
|
||||
|
||||
# 移除之前的任务(如果存在)
|
||||
taskMgr.remove("cameraFocusTask")
|
||||
|
||||
# 添加新任务
|
||||
taskMgr.add(self._cameraFocusTask, "cameraFocusTask")
|
||||
|
||||
except Exception as e:
|
||||
print(f"启动摄像机聚焦动画失败: {e}")
|
||||
|
||||
def _normalizeAngle(self, angle):
|
||||
"""规范化角度到-180到180度之间"""
|
||||
while angle > 180:
|
||||
angle -= 360
|
||||
while angle < -180:
|
||||
angle += 360
|
||||
return angle
|
||||
|
||||
def _cameraFocusTask(self, task):
|
||||
"""摄像机聚焦动画任务"""
|
||||
try:
|
||||
if not hasattr(self, '_camera_focus_data'):
|
||||
return task.done
|
||||
|
||||
data = self._camera_focus_data
|
||||
data.elapsed_time += globalClock.getDt()
|
||||
|
||||
# 计算插值因子
|
||||
t = min(1.0, data.elapsed_time / data.duration)
|
||||
|
||||
# 使用更平滑的插值函数
|
||||
smooth_t = t * t * (3 - 2 * t) # 平滑步进插值
|
||||
|
||||
# 手动实现lerp功能
|
||||
def lerp_point3(start, end, factor):
|
||||
return Point3(
|
||||
start.x + (end.x - start.x) * factor,
|
||||
start.y + (end.y - start.y) * factor,
|
||||
start.z + (end.z - start.z) * factor
|
||||
)
|
||||
|
||||
# 角度插值需要特殊处理,确保选择最短路径
|
||||
def lerp_hpr(start, end, factor):
|
||||
# 规范化角度
|
||||
start_x = self._normalizeAngle(start.x)
|
||||
start_y = self._normalizeAngle(start.y)
|
||||
start_z = self._normalizeAngle(start.z)
|
||||
|
||||
end_x = self._normalizeAngle(end.x)
|
||||
end_y = self._normalizeAngle(end.y)
|
||||
end_z = self._normalizeAngle(end.z)
|
||||
|
||||
# 计算最短旋转路径
|
||||
diff_x = self._normalizeAngle(end_x - start_x)
|
||||
diff_y = self._normalizeAngle(end_y - start_y)
|
||||
diff_z = self._normalizeAngle(end_z - start_z)
|
||||
|
||||
# 插值
|
||||
result_x = start_x + diff_x * factor
|
||||
result_y = start_y + diff_y * factor
|
||||
result_z = start_z + diff_z * factor
|
||||
|
||||
# 再次规范化
|
||||
result_x = self._normalizeAngle(result_x)
|
||||
result_y = self._normalizeAngle(result_y)
|
||||
result_z = self._normalizeAngle(result_z)
|
||||
|
||||
return Vec3(result_x, result_y, result_z)
|
||||
|
||||
# 计算当前位置和朝向
|
||||
current_pos = lerp_point3(data.start_pos, data.end_pos, smooth_t)
|
||||
current_hpr = lerp_hpr(data.start_hpr, data.end_hpr, smooth_t)
|
||||
|
||||
# 应用到摄像机
|
||||
self.world.cam.setPos(current_pos)
|
||||
self.world.cam.setHpr(current_hpr)
|
||||
|
||||
# 检查是否完成
|
||||
if t >= 1.0:
|
||||
if hasattr(self, '_camera_focus_data'):
|
||||
delattr(self, '_camera_focus_data')
|
||||
print("摄像机聚焦动画完成")
|
||||
return task.done
|
||||
|
||||
return task.cont
|
||||
|
||||
except Exception as e:
|
||||
print(f"摄像机聚焦任务出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return task.done
|
||||
|
||||
def focusCameraOnSelectedNode(self):
|
||||
"""将摄像机聚焦到选中的节点(无动画版本,但仍保持平滑转向)"""
|
||||
try:
|
||||
if not self.selectedNode or self.selectedNode.isEmpty():
|
||||
print("没有选中的节点,无法聚焦")
|
||||
return False
|
||||
|
||||
# 获取选中节点的边界框
|
||||
minPoint = Point3()
|
||||
maxPoint = Point3()
|
||||
|
||||
if not self.selectedNode.calcTightBounds(minPoint, maxPoint, self.world.render):
|
||||
print("无法计算选中节点的边界框")
|
||||
return False
|
||||
|
||||
# 计算节点中心点
|
||||
center = Point3(
|
||||
(minPoint.x + maxPoint.x) * 0.5,
|
||||
(minPoint.y + maxPoint.y) * 0.5,
|
||||
(minPoint.z + maxPoint.z) * 0.5
|
||||
)
|
||||
|
||||
# 计算节点的大小(直径)
|
||||
size = (maxPoint - minPoint).length()
|
||||
|
||||
# 如果节点太小,使用默认大小
|
||||
if size < 0.1:
|
||||
size = 5.0
|
||||
|
||||
# 获取当前摄像机位置
|
||||
current_cam_pos = Point3(self.world.cam.getPos())
|
||||
|
||||
# 计算观察方向
|
||||
view_direction = current_cam_pos - center
|
||||
if view_direction.length() < 0.001:
|
||||
# 如果摄像机正好在中心点,使用默认方向
|
||||
view_direction = Vec3(5, -5, 2) # 默认观察方向
|
||||
|
||||
# 标准化方向向量
|
||||
view_direction.normalize()
|
||||
|
||||
# 计算合适的距离(基于节点大小)
|
||||
optimal_distance = max(size * 3.0, 10.0) # 距离节点的距离是节点大小的3倍或至少10个单位
|
||||
|
||||
# 计算新的摄像机位置
|
||||
new_cam_pos = center + (view_direction * optimal_distance)
|
||||
|
||||
# 平滑地设置摄像机位置和朝向
|
||||
# 创建临时节点用于计算目标朝向
|
||||
temp_lookat = self.world.render.attachNewNode("temp_lookat")
|
||||
temp_lookat.setPos(center)
|
||||
|
||||
# 获取当前朝向和目标朝向
|
||||
current_hpr = Vec3(self.world.cam.getHpr())
|
||||
|
||||
# 设置摄像机到目标位置
|
||||
self.world.cam.setPos(new_cam_pos)
|
||||
self.world.cam.lookAt(temp_lookat)
|
||||
target_hpr = Vec3(self.world.cam.getHpr())
|
||||
|
||||
# 恢复当前位置
|
||||
self.world.cam.setPos(current_cam_pos)
|
||||
self.world.cam.setHpr(current_hpr)
|
||||
|
||||
# 清理临时节点
|
||||
temp_lookat.removeNode()
|
||||
|
||||
# 使用一个简单的任务来平滑过渡
|
||||
class SmoothCameraMoveData:
|
||||
def __init__(self, start_pos, end_pos, start_hpr, end_hpr):
|
||||
self.start_pos = Point3(start_pos)
|
||||
self.end_pos = Point3(end_pos)
|
||||
self.start_hpr = Vec3(start_hpr)
|
||||
self.end_hpr = Vec3(end_hpr)
|
||||
self.elapsed_time = 0.0
|
||||
self.duration = 0.5 # 0.5秒的平滑过渡
|
||||
|
||||
self._smooth_camera_move_data = SmoothCameraMoveData(
|
||||
current_cam_pos, new_cam_pos, current_hpr, target_hpr
|
||||
)
|
||||
|
||||
taskMgr.remove("smoothCameraMoveTask")
|
||||
taskMgr.add(self._smoothCameraMoveTask, "smoothCameraMoveTask")
|
||||
|
||||
print(f"摄像机开始聚焦到节点: {self.selectedNode.getName()}")
|
||||
print(f"节点中心: {center}, 大小: {size:.2f}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"聚焦摄像机到选中节点失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def _smoothCameraMoveTask(self, task):
|
||||
"""平滑摄像机移动任务"""
|
||||
try:
|
||||
if not hasattr(self, '_smooth_camera_move_data'):
|
||||
return task.done
|
||||
|
||||
data = self._smooth_camera_move_data
|
||||
data.elapsed_time += globalClock.getDt()
|
||||
|
||||
# 计算插值因子
|
||||
t = min(1.0, data.elapsed_time / data.duration)
|
||||
|
||||
# 使用平滑插值
|
||||
smooth_t = t * t * (3 - 2 * t)
|
||||
|
||||
# 角度插值需要特殊处理
|
||||
def lerp_hpr(start, end, factor):
|
||||
# 规范化角度
|
||||
start_x = self._normalizeAngle(start.x)
|
||||
start_y = self._normalizeAngle(start.y)
|
||||
start_z = self._normalizeAngle(start.z)
|
||||
|
||||
end_x = self._normalizeAngle(end.x)
|
||||
end_y = self._normalizeAngle(end.y)
|
||||
end_z = self._normalizeAngle(end.z)
|
||||
|
||||
# 计算最短旋转路径
|
||||
diff_x = self._normalizeAngle(end_x - start_x)
|
||||
diff_y = self._normalizeAngle(end_y - start_y)
|
||||
diff_z = self._normalizeAngle(end_z - start_z)
|
||||
|
||||
# 插值
|
||||
result_x = start_x + diff_x * factor
|
||||
result_y = start_y + diff_y * factor
|
||||
result_z = start_z + diff_z * factor
|
||||
|
||||
return Vec3(result_x, result_y, result_z)
|
||||
|
||||
# 计算当前位置和朝向
|
||||
current_pos = Point3(
|
||||
data.start_pos.x + (data.end_pos.x - data.start_pos.x) * smooth_t,
|
||||
data.start_pos.y + (data.end_pos.y - data.start_pos.y) * smooth_t,
|
||||
data.start_pos.z + (data.end_pos.z - data.start_pos.z) * smooth_t
|
||||
)
|
||||
|
||||
current_hpr = lerp_hpr(data.start_hpr, data.end_hpr, smooth_t)
|
||||
|
||||
# 应用到摄像机
|
||||
self.world.cam.setPos(current_pos)
|
||||
self.world.cam.setHpr(current_hpr)
|
||||
|
||||
# 检查是否完成
|
||||
if t >= 1.0:
|
||||
if hasattr(self, '_smooth_camera_move_data'):
|
||||
delattr(self, '_smooth_camera_move_data')
|
||||
print("摄像机平滑移动完成")
|
||||
return task.done
|
||||
|
||||
return task.cont
|
||||
|
||||
except Exception as e:
|
||||
print(f"平滑摄像机移动任务出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return task.done
|
||||
|
||||
def handleMouseClick(self, nodePath, mouseX=None, mouseY=None):
|
||||
"""处理鼠标点击事件 - 支持坐标轴双击聚焦"""
|
||||
try:
|
||||
# 如果正在删除节点,忽略鼠标点击
|
||||
if hasattr(self, '_deleting_node') and self._deleting_node:
|
||||
print("正在删除节点,忽略鼠标点击")
|
||||
return
|
||||
|
||||
import time
|
||||
current_time = time.time()
|
||||
|
||||
# 检查是否点击了坐标轴
|
||||
is_gizmo_click = False
|
||||
target_node = nodePath
|
||||
|
||||
# 判断是否点击了坐标轴
|
||||
if (nodePath and hasattr(nodePath, 'getName') and
|
||||
(nodePath.getName().startswith("gizmo") or
|
||||
"gizmo" in nodePath.getName().lower() or
|
||||
(hasattr(nodePath, 'hasTag') and nodePath.hasTag("is_gizmo")))):
|
||||
is_gizmo_click = True
|
||||
# 如果有选中的模型,使用选中的模型作为聚焦目标
|
||||
if self.selectedNode and not self.selectedNode.isEmpty():
|
||||
target_node = self.selectedNode
|
||||
print(f"检测到坐标轴点击,使用目标节点: {target_node.getName() if target_node else 'None'}")
|
||||
|
||||
# 检查是否为双击(同一节点且在时间阈值内)
|
||||
is_double_click = (self._last_clicked_node == target_node and
|
||||
current_time - self._last_click_time < self._double_click_threshold)
|
||||
|
||||
if is_double_click:
|
||||
# 双击 detected
|
||||
node_name = target_node.getName() if target_node else "None"
|
||||
print(f"检测到双击节点: {node_name}")
|
||||
|
||||
# 无论是点击模型还是坐标轴,都执行聚焦
|
||||
if target_node and not target_node.isEmpty():
|
||||
print(f"双击聚焦到节点: {target_node.getName()}")
|
||||
# 执行聚焦
|
||||
self.focusCameraOnSelectedNodeAdvanced()
|
||||
|
||||
# 重置状态以避免三击等误触发
|
||||
self._last_click_time = 0
|
||||
self._last_clicked_node = None
|
||||
else:
|
||||
# 单击,更新状态
|
||||
self._last_click_time = current_time
|
||||
self._last_clicked_node = target_node
|
||||
|
||||
# 如果点击的是坐标轴,保持当前选择不变
|
||||
if is_gizmo_click:
|
||||
print("坐标轴单击,保持当前选择")
|
||||
else:
|
||||
# 正常的单击选择
|
||||
self.updateSelection(nodePath)
|
||||
|
||||
except Exception as e:
|
||||
print(f"处理鼠标点击事件失败: {e}")
|
||||
|
||||
def _onDoubleClick(self, nodePath):
|
||||
"""双击事件处理"""
|
||||
try:
|
||||
# 获取实际要聚焦的目标节点
|
||||
target_node = nodePath
|
||||
|
||||
# 如果是坐标轴,确保使用关联的模型作为目标
|
||||
if (nodePath and hasattr(nodePath, 'hasTag') and
|
||||
nodePath.hasTag("is_gizmo")):
|
||||
if self.selectedNode and not self.selectedNode.isEmpty():
|
||||
target_node = self.selectedNode
|
||||
print(f"坐标轴双击,聚焦到关联模型: {target_node.getName()}")
|
||||
else:
|
||||
print("坐标轴双击,但没有关联的选中模型")
|
||||
return
|
||||
|
||||
if target_node and not target_node.isEmpty():
|
||||
print(f"双击聚焦到节点: {target_node.getName()}")
|
||||
# 更新选择(如果需要)
|
||||
if self.selectedNode != target_node:
|
||||
self.updateSelection(target_node)
|
||||
|
||||
# 执行聚焦
|
||||
self.focusCameraOnSelectedNodeAdvanced()
|
||||
else:
|
||||
print("双击事件:没有有效的目标节点")
|
||||
|
||||
except Exception as e:
|
||||
print(f"双击事件处理失败: {e}")
|
||||
|
||||
# 添加一个更精确的双击检测方法
|
||||
|
||||
def checkDoubleClick(self, nodePath):
|
||||
"""检查是否为双击,返回布尔值"""
|
||||
try:
|
||||
import time
|
||||
current_time = time.time()
|
||||
|
||||
is_double_click = (self._last_clicked_node == nodePath and
|
||||
current_time - self._last_click_time < self._double_click_threshold)
|
||||
|
||||
if is_double_click:
|
||||
# 双击 detected,重置状态
|
||||
self._last_click_time = 0
|
||||
self._last_clicked_node = None
|
||||
else:
|
||||
# 更新状态为单击
|
||||
self._last_click_time = current_time
|
||||
self._last_clicked_node = nodePath
|
||||
|
||||
return is_double_click
|
||||
|
||||
except Exception as e:
|
||||
print(f"双击检测失败: {e}")
|
||||
return False
|
||||
|
||||
# 添加一个定时重置方法,用于清除长时间未完成的双击状态
|
||||
|
||||
def _resetDoubleClickState(self):
|
||||
"""重置双击状态"""
|
||||
self._last_click_time = 0
|
||||
self._last_clicked_node = None
|
||||
|
||||
# 添加一个任务来自动重置双击状态
|
||||
|
||||
def _startDoubleClickResetTask(self):
|
||||
"""启动双击状态重置任务"""
|
||||
if self._double_click_task:
|
||||
taskMgr.remove(self._double_click_task)
|
||||
|
||||
self._double_click_task = taskMgr.doMethodLater(
|
||||
self._double_click_threshold * 2, # 等待2倍阈值时间
|
||||
self._resetDoubleClickStateTask,
|
||||
"resetDoubleClickState"
|
||||
)
|
||||
|
||||
def _resetDoubleClickStateTask(self, task):
|
||||
"""任务:重置双击状态"""
|
||||
self._resetDoubleClickState()
|
||||
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:
|
||||
self.focusCameraOnSelectedNodeAdvanced()
|
||||
except Exception as e:
|
||||
print(f"延迟聚焦任务失败: {e}")
|
||||
return task.done
|
||||
|
||||
# 添加一个更智能的双击检测方法,考虑鼠标位置
|
||||
|
||||
def checkDoubleClickWithPosition(self, nodePath, mouse_x=None, mouse_y=None):
|
||||
"""检查是否为双击,同时考虑鼠标位置"""
|
||||
try:
|
||||
import time
|
||||
current_time = time.time()
|
||||
|
||||
# 如果没有提供鼠标位置,直接使用基本双击检测
|
||||
if mouse_x is None or mouse_y is None:
|
||||
return self.checkDoubleClick(nodePath)
|
||||
|
||||
# 检查节点和时间
|
||||
time_diff = current_time - self._last_click_time
|
||||
is_same_node = (self._last_clicked_node == nodePath)
|
||||
|
||||
# 如果是同一节点且在时间阈值内,认为是双击
|
||||
if is_same_node and time_diff < self._double_click_threshold:
|
||||
# 重置状态
|
||||
self._last_click_time = 0
|
||||
self._last_clicked_node = None
|
||||
return True
|
||||
else:
|
||||
# 更新状态
|
||||
self._last_click_time = current_time
|
||||
self._last_clicked_node = nodePath
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"带位置的双击检测失败: {e}")
|
||||
return False
|
||||
|
||||
# 添加一个公共方法来设置双击阈值
|
||||
|
||||
def setDoubleClickThreshold(self, threshold_seconds):
|
||||
"""设置双击时间阈值"""
|
||||
if threshold_seconds > 0:
|
||||
self._double_click_threshold = threshold_seconds
|
||||
print(f"双击阈值已设置为: {threshold_seconds} 秒")
|
||||
else:
|
||||
print("无效的双击阈值")
|
||||
|
||||
# 添加一个方法来手动触发双击聚焦(可用于测试或其他触发方式)
|
||||
|
||||
def triggerDoubleClickFocus(self, nodePath=None):
|
||||
"""手动触发双击聚焦"""
|
||||
try:
|
||||
target_node = nodePath if nodePath else self.selectedNode
|
||||
if target_node and not target_node.isEmpty():
|
||||
print(f"手动触发聚焦到节点: {target_node.getName()}")
|
||||
if self.selectedNode != target_node:
|
||||
self.updateSelection(target_node)
|
||||
self.focusCameraOnSelectedNodeAdvanced()
|
||||
return True
|
||||
else:
|
||||
print("没有有效的目标节点进行聚焦")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"手动触发聚焦失败: {e}")
|
||||
return False
|
||||
|
||||
def cleanup(self):
|
||||
"""清理选择系统资源"""
|
||||
# 清理双击任务
|
||||
if self._double_click_task:
|
||||
taskMgr.remove(self._double_click_task)
|
||||
self._double_click_task = None
|
||||
|
||||
# 清理其他资源
|
||||
self.clearSelectionBox()
|
||||
self.clearGizmo()
|
||||
109
core/world.py
109
core/world.py
@ -329,10 +329,51 @@ class CoreWorld(Panda3DWorld):
|
||||
mat.setName("GroundMaterial")
|
||||
color = LColor(1, 1, 1, 0.8)
|
||||
mat.set_base_color(color)
|
||||
mat.set_roughness(0.5) # 设置合适的初始粗糙度
|
||||
mat.set_roughness(1) # 设置合适的初始粗糙度
|
||||
mat.set_metallic(0.5) # 设置较低的初始金属性
|
||||
self.ground.set_material(mat)
|
||||
|
||||
#创建第二个相同的地面,位置稍有偏移
|
||||
self.ground2 = self.render.attachNewNode(cm.generate())
|
||||
self.ground2.setH(-90)
|
||||
self.ground2.setZ(-1.0)
|
||||
self.ground2.setX(50) # 在X轴方向偏移
|
||||
self.ground2.setZ(49) # 在X轴方向偏移
|
||||
self.ground2.setColor(0.8, 0.8, 0.8, 1)
|
||||
self.ground2.set_material(mat)
|
||||
|
||||
# 创建第三个相同的地面,位置在另一个方向
|
||||
self.ground3 = self.render.attachNewNode(cm.generate())
|
||||
self.ground3.setH(90)
|
||||
self.ground3.setZ(-1.0)
|
||||
self.ground3.setX(-50) # 在X轴负方向偏移
|
||||
self.ground3.setZ(49) # 在X轴负方向偏移
|
||||
self.ground3.setColor(0.8, 0.8, 0.8, 1)
|
||||
self.ground3.set_material(mat)
|
||||
|
||||
self.ground4 = self.render.attachNewNode(cm.generate())
|
||||
# self.ground3.setR(90)
|
||||
self.ground4.setZ(-1.0)
|
||||
self.ground4.setY(50) # 在X轴负方向偏移
|
||||
self.ground4.setZ(49) # 在X轴负方向偏移
|
||||
self.ground4.setColor(0.8, 0.8, 0.8, 1)
|
||||
self.ground4.set_material(mat)
|
||||
|
||||
self.ground5 = self.render.attachNewNode(cm.generate())
|
||||
self.ground5.setP(180)
|
||||
self.ground5.setZ(-1)
|
||||
self.ground5.setY(-50) # 在X轴负方向偏移
|
||||
self.ground5.setZ(49) # 在X轴负方向偏移
|
||||
self.ground5.setColor(0.8, 0.8, 0.8, 1)
|
||||
self.ground5.set_material(mat)
|
||||
|
||||
self.ground6 = self.render.attachNewNode(cm.generate())
|
||||
self.ground6.setP(90)
|
||||
self.ground6.setZ(-1)
|
||||
self.ground6.setZ(99) # 在X轴负方向偏移
|
||||
self.ground6.setColor(0.8, 0.8, 0.8, 1)
|
||||
self.ground6.set_material(mat)
|
||||
|
||||
# 应用默认PBR效果,确保支持贴图
|
||||
try:
|
||||
if hasattr(self, 'render_pipeline') and self.render_pipeline:
|
||||
@ -349,6 +390,72 @@ class CoreWorld(Panda3DWorld):
|
||||
},
|
||||
50
|
||||
)
|
||||
# 为其他两个地面也应用相同的效果
|
||||
self.render_pipeline.set_effect(
|
||||
self.ground2,
|
||||
"effects/default.yaml",
|
||||
{
|
||||
"normal_mapping": True,
|
||||
"render_gbuffer": True,
|
||||
"alpha_testing": False,
|
||||
"parallax_mapping": False,
|
||||
"render_shadow": True,
|
||||
"render_envmap": True
|
||||
},
|
||||
50
|
||||
)
|
||||
self.render_pipeline.set_effect(
|
||||
self.ground3,
|
||||
"effects/default.yaml",
|
||||
{
|
||||
"normal_mapping": True,
|
||||
"render_gbuffer": True,
|
||||
"alpha_testing": False,
|
||||
"parallax_mapping": False,
|
||||
"render_shadow": True,
|
||||
"render_envmap": True
|
||||
},
|
||||
50
|
||||
)
|
||||
self.render_pipeline.set_effect(
|
||||
self.ground4,
|
||||
"effects/default.yaml",
|
||||
{
|
||||
"normal_mapping": True,
|
||||
"render_gbuffer": True,
|
||||
"alpha_testing": False,
|
||||
"parallax_mapping": False,
|
||||
"render_shadow": True,
|
||||
"render_envmap": True
|
||||
},
|
||||
50
|
||||
)
|
||||
self.render_pipeline.set_effect(
|
||||
self.ground5,
|
||||
"effects/default.yaml",
|
||||
{
|
||||
"normal_mapping": True,
|
||||
"render_gbuffer": True,
|
||||
"alpha_testing": False,
|
||||
"parallax_mapping": False,
|
||||
"render_shadow": True,
|
||||
"render_envmap": True
|
||||
},
|
||||
50
|
||||
)
|
||||
self.render_pipeline.set_effect(
|
||||
self.ground6,
|
||||
"effects/default.yaml",
|
||||
{
|
||||
"normal_mapping": True,
|
||||
"render_gbuffer": True,
|
||||
"alpha_testing": False,
|
||||
"parallax_mapping": False,
|
||||
"render_shadow": True,
|
||||
"render_envmap": True
|
||||
},
|
||||
50
|
||||
)
|
||||
print("✓ 地板PBR效果已应用")
|
||||
else:
|
||||
print("⚠️ RenderPipeline未初始化,地板将使用基础渲染")
|
||||
|
||||
@ -359,7 +359,7 @@ class GizmoDragTestWorld(Panda3DWorld):
|
||||
distance = distance_to_line(
|
||||
(mouseX, mouseY), center_screen, axis_screen
|
||||
)
|
||||
print(f"{axis_label}距离: {distance:.2f}")
|
||||
#print(f"{axis_label}距离: {distance:.2f}")
|
||||
|
||||
if distance < click_threshold:
|
||||
print(f"✓ 点击了{axis_label}")
|
||||
|
||||
89
main.py
89
main.py
@ -22,6 +22,7 @@ from core.script_system import ScriptManager
|
||||
from core.vr_manager import VRManager
|
||||
from core.vr_input_handler import VRInputHandler
|
||||
from core.alvr_streamer import ALVRStreamer
|
||||
from core.patrol_system import PatrolSystem
|
||||
from gui.gui_manager import GUIManager
|
||||
from core.terrain_manager import TerrainManager
|
||||
from scene.scene_manager import SceneManager
|
||||
@ -55,7 +56,16 @@ class MyWorld(CoreWorld):
|
||||
|
||||
# 初始化选择和变换系统
|
||||
self.selection = SelectionSystem(self)
|
||||
|
||||
|
||||
# 绑定F键用于聚焦选中节点
|
||||
self.accept("f", self.onFocusKeyPressed)
|
||||
self.accept("F", self.onFocusKeyPressed) # 大写F
|
||||
|
||||
#初始化巡检系统
|
||||
self.patrol_system = PatrolSystem(self)
|
||||
self.accept("p",self.onPatrolKeyPressed)
|
||||
self.accept("P",self.onPatrolKeyPressed)
|
||||
|
||||
# 初始化事件处理系统
|
||||
self.event_handler = EventHandler(self)
|
||||
|
||||
@ -105,7 +115,7 @@ class MyWorld(CoreWorld):
|
||||
self.collision_manager = CollisionManager(self)
|
||||
|
||||
# 调试选项
|
||||
self.debug_collision = False # 是否显示碰撞体
|
||||
self.debug_collision = True # 是否显示碰撞体
|
||||
|
||||
# 默认启用模型间碰撞检测(可选)
|
||||
self.enableModelCollisionDetection(enable=True, frequency=0.1, threshold=0.5)
|
||||
@ -810,6 +820,81 @@ class MyWorld(CoreWorld):
|
||||
"""获取碰撞统计"""
|
||||
return self.collision_manager.getCollisionStatistics()
|
||||
|
||||
def setupKeyboardEvents(self):
|
||||
"""设置键盘事件"""
|
||||
try:
|
||||
# 绑定 F 键用于聚焦选中节点
|
||||
self.accept("f", self.onFocusKeyPressed)
|
||||
self.accept("F", self.onFocusKeyPressed) # 大写F
|
||||
|
||||
print("✓ 键盘事件绑定完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f"设置键盘事件失败: {e}")
|
||||
|
||||
def onFocusKeyPressed(self):
|
||||
"""处理 F 键按下事件"""
|
||||
try:
|
||||
#print("检测到 F 键按下")
|
||||
|
||||
# 检查是否有选中的节点
|
||||
if hasattr(self, 'selection') and self.selection.selectedNode:
|
||||
#print(f"当前选中节点: {self.selection.selectedNode.getName()}")
|
||||
# 调用选择系统的聚焦功能(可以选择带动画或不带动画的版本)
|
||||
# self.selection.focusCameraOnSelectedNode() # 无动画版本
|
||||
self.selection.focusCameraOnSelectedNodeAdvanced() # 带动画版本
|
||||
else:
|
||||
print("当前没有选中任何节点")
|
||||
|
||||
except Exception as e:
|
||||
print(f"处理 F 键事件失败: {e}")
|
||||
|
||||
def onPatrolKeyPressed(self):
|
||||
"""处理 P 键按下事件 - 控制巡检系统"""
|
||||
try:
|
||||
print("检测到 P 键按下")
|
||||
|
||||
if not self.patrol_system.is_patrolling:
|
||||
# 如果巡检系统没有点,创建默认巡检路线
|
||||
if not self.patrol_system.patrol_points:
|
||||
self.createDefaultPatrolRoute()
|
||||
|
||||
# 开始巡检
|
||||
if self.patrol_system.start_patrol():
|
||||
print("✓ 巡检已开始")
|
||||
else:
|
||||
print("✗ 巡检启动失败")
|
||||
else:
|
||||
# 停止巡检
|
||||
if self.patrol_system.stop_patrol():
|
||||
print("✓ 巡检已停止")
|
||||
else:
|
||||
print("✗ 巡检停止失败")
|
||||
|
||||
except Exception as e:
|
||||
print(f"处理 P 键事件失败: {e}")
|
||||
|
||||
def createDefaultPatrolRoute(self):
|
||||
"""创建默认巡检路线(使用自动朝向)"""
|
||||
try:
|
||||
# 清空现有巡检点
|
||||
self.patrol_system.clear_patrol_points()
|
||||
|
||||
# 添加巡检点,使用None表示朝向下一个点
|
||||
self.patrol_system.add_patrol_point((0, -10, 2), (0,0,0), 1.5)
|
||||
self.patrol_system.add_patrol_point((10, -10, 2), (0,0,0), 1.5)
|
||||
self.patrol_system.add_patrol_point((10, 5, 2), (0,0,0), 1.5)
|
||||
self.patrol_system.add_patrol_point((10, 0, 5), (0,0,0), 1.5)
|
||||
|
||||
# 最后一个点可以指定特定的朝向,或者也设为None继续循环
|
||||
self.patrol_system.add_patrol_point((0, -10, 2), None, 2.5)
|
||||
|
||||
print("✓ 默认自动朝向巡检路线已创建")
|
||||
self.patrol_system.list_patrol_points()
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建默认自动朝向巡检路线失败: {e}")
|
||||
|
||||
|
||||
# ==================== 项目管理功能代理 ====================
|
||||
# 以下函数代理到project_manager模块的对应功能
|
||||
|
||||
@ -101,8 +101,8 @@ class SceneManager:
|
||||
|
||||
try:
|
||||
print(f"\n=== 开始导入模型: {filepath} ===")
|
||||
print(f"单位转换: {'开启' if apply_unit_conversion else '关闭'}")
|
||||
print(f"自动转换GLB: {'开启' if auto_convert_to_glb else '关闭'}")
|
||||
#print(f"单位转换: {'开启' if apply_unit_conversion else '关闭'}")
|
||||
#print(f"自动转换GLB: {'开启' if auto_convert_to_glb else '关闭'}")
|
||||
|
||||
filepath = util.normalize_model_path(filepath)
|
||||
original_filepath = filepath
|
||||
@ -117,10 +117,10 @@ class SceneManager:
|
||||
|
||||
# 检查是否需要转换为GLB以获得更好的动画支持
|
||||
if auto_convert_to_glb and self._shouldConvertToGLB(filepath):
|
||||
print(f"🔄 检测到需要转换的格式,尝试转换为GLB...")
|
||||
#print(f"🔄 检测到需要转换的格式,尝试转换为GLB...")
|
||||
converted_path = self._convertToGLBWithProgress(filepath)
|
||||
if converted_path:
|
||||
print(f"✅ 转换成功: {converted_path}")
|
||||
#print(f"✅ 转换成功: {converted_path}")
|
||||
filepath = converted_path
|
||||
# 显示成功消息
|
||||
try:
|
||||
@ -135,7 +135,7 @@ class SceneManager:
|
||||
|
||||
# 总是重新加载模型以确保材质信息完整
|
||||
# 不使用ModelPool缓存,避免材质信息丢失问题
|
||||
print("直接从文件加载模型...")
|
||||
#print("直接从文件加载模型...")
|
||||
model = self.world.loader.loadModel(filepath)
|
||||
if not model:
|
||||
print("加载模型失败")
|
||||
@ -170,12 +170,12 @@ class SceneManager:
|
||||
|
||||
# 可选的单位转换(主要针对FBX)
|
||||
if apply_unit_conversion and filepath.lower().endswith('.fbx'):
|
||||
print("应用FBX单位转换(厘米到米)...")
|
||||
#print("应用FBX单位转换(厘米到米)...")
|
||||
self._applyUnitConversion(model, 0.01)
|
||||
|
||||
# 智能缩放标准化(处理FBX子节点的大缩放值)
|
||||
if normalize_scales and filepath.lower().endswith('.fbx'):
|
||||
print("标准化FBX模型缩放层级...")
|
||||
#print("标准化FBX模型缩放层级...")
|
||||
self._normalizeModelScales(model)
|
||||
|
||||
# 调整模型位置到地面
|
||||
@ -219,9 +219,9 @@ class SceneManager:
|
||||
if root_item:
|
||||
qt_item = tree_widget.add_node_to_tree_widget(model, root_item, "IMPORTED_MODEL_NODE")
|
||||
if qt_item:
|
||||
tree_widget.setCurrentItem(qt_item)
|
||||
#tree_widget.setCurrentItem(qt_item)
|
||||
# 更新选择和属性面板
|
||||
tree_widget.update_selection_and_properties(model, qt_item)
|
||||
#tree_widget.update_selection_and_properties(model, qt_item)
|
||||
print("✅ Qt树节点添加成功")
|
||||
else:
|
||||
print("⚠️ Qt树节点添加失败,但Panda3D对象已创建")
|
||||
@ -282,11 +282,23 @@ class SceneManager:
|
||||
node_path.clearTransform()
|
||||
return
|
||||
|
||||
# 方法2: 检查矩阵是否奇异
|
||||
# 方法2: 简单的矩阵有效性检查(移除 isSingular 调用)
|
||||
try:
|
||||
matrix = node_path.getMat()
|
||||
if matrix.isSingular():
|
||||
print(f"⚠️ 节点 {node_name} 有奇异矩阵")
|
||||
# 检查矩阵元素是否有效
|
||||
is_valid = True
|
||||
for i in range(4):
|
||||
for j in range(4):
|
||||
element = matrix.getCell(i, j)
|
||||
# 检查是否为 NaN 或无穷大
|
||||
if str(element) in ['nan', 'inf', '-inf'] or abs(element) > 1e10:
|
||||
is_valid = False
|
||||
break
|
||||
if not is_valid:
|
||||
break
|
||||
|
||||
if not is_valid:
|
||||
print(f"⚠️ 节点 {node_name} 有无效矩阵")
|
||||
node_path.clearTransform()
|
||||
return
|
||||
except Exception as e:
|
||||
@ -403,11 +415,11 @@ class SceneManager:
|
||||
def apply_material(node_path, depth=0):
|
||||
indent = " " * depth
|
||||
try:
|
||||
print(f"{indent}处理节点: {node_path.getName()}")
|
||||
print(f"{indent}节点类型: {node_path.node().__class__.__name__}")
|
||||
#print(f"{indent}处理节点: {node_path.getName()}")
|
||||
#print(f"{indent}节点类型: {node_path.node().__class__.__name__}")
|
||||
|
||||
if isinstance(node_path.node(), GeomNode):
|
||||
print(f"{indent}发现GeomNode,处理材质")
|
||||
#print(f"{indent}发现GeomNode,处理材质")
|
||||
geom_node = node_path.node()
|
||||
|
||||
# 检查所有几何体的状态
|
||||
@ -423,11 +435,11 @@ class SceneManager:
|
||||
if node_material.hasBaseColor():
|
||||
color = node_material.getBaseColor()
|
||||
has_color = True
|
||||
print(f"{indent}从节点材质获取基础颜色: {color}")
|
||||
#print(f"{indent}从节点材质获取基础颜色: {color}")
|
||||
elif node_material.hasDiffuse():
|
||||
color = node_material.getDiffuse()
|
||||
has_color = True
|
||||
print(f"{indent}从节点材质获取漫反射颜色: {color}")
|
||||
#print(f"{indent}从节点材质获取漫反射颜色: {color}")
|
||||
|
||||
# 检查几何体材质
|
||||
if not has_color:
|
||||
@ -444,12 +456,12 @@ class SceneManager:
|
||||
if orig_material.hasBaseColor():
|
||||
color = orig_material.getBaseColor()
|
||||
has_color = True
|
||||
print(f"{indent}从几何体材质获取基础颜色: {color}")
|
||||
#print(f"{indent}从几何体材质获取基础颜色: {color}")
|
||||
break
|
||||
elif orig_material.hasDiffuse():
|
||||
color = orig_material.getDiffuse()
|
||||
has_color = True
|
||||
print(f"{indent}从几何体材质获取漫反射颜色: {color}")
|
||||
#print(f"{indent}从几何体材质获取漫反射颜色: {color}")
|
||||
break
|
||||
|
||||
# 检查颜色属性
|
||||
@ -458,7 +470,7 @@ class SceneManager:
|
||||
if not color_attrib.isOff():
|
||||
color = color_attrib.getColor()
|
||||
has_color = True
|
||||
print(f"{indent}从颜色属性获取: {color}")
|
||||
#print(f"{indent}从颜色属性获取: {color}")
|
||||
break
|
||||
except Exception as geom_error:
|
||||
print(f"{indent}处理几何体 {i} 时出错: {geom_error}")
|
||||
@ -467,7 +479,7 @@ class SceneManager:
|
||||
# 创建新材质
|
||||
material = Material()
|
||||
if has_color and color:
|
||||
print(f"{indent}应用找到的颜色: {color}")
|
||||
#print(f"{indent}应用找到的颜色: {color}")
|
||||
try:
|
||||
# 确保颜色值有效
|
||||
if (color.getX() == color.getX() and color.getY() == color.getY() and
|
||||
@ -496,18 +508,18 @@ class SceneManager:
|
||||
# 应用材质
|
||||
try:
|
||||
node_path.setMaterial(material, 1) # 1表示强制应用
|
||||
print(f"{indent}材质应用成功")
|
||||
#print(f"{indent}材质应用成功")
|
||||
except Exception as mat_error:
|
||||
print(f"{indent}⚠️ 应用材质时出错: {mat_error}")
|
||||
|
||||
print(f"{indent}几何体数量: {geom_node.getNumGeoms()}")
|
||||
#print(f"{indent}几何体数量: {geom_node.getNumGeoms()}")
|
||||
|
||||
except Exception as node_error:
|
||||
print(f"{indent}处理节点 {node_path.getName()} 时出错: {node_error}")
|
||||
|
||||
# 递归处理子节点
|
||||
child_count = node_path.getNumChildren()
|
||||
print(f"{indent}子节点数量: {child_count}")
|
||||
#print(f"{indent}子节点数量: {child_count}")
|
||||
for i in range(child_count):
|
||||
try:
|
||||
child = node_path.getChild(i)
|
||||
@ -517,99 +529,13 @@ class SceneManager:
|
||||
continue
|
||||
|
||||
# 应用材质
|
||||
print("\n开始递归应用材质...")
|
||||
#print("\n开始递归应用材质...")
|
||||
try:
|
||||
apply_material(model)
|
||||
except Exception as e:
|
||||
print(f"应用材质时出错: {e}")
|
||||
print("=== 材质设置完成 ===\n")
|
||||
|
||||
def setupCollision(self, model):
|
||||
"""为模型设置碰撞检测(增强版本)"""
|
||||
try:
|
||||
if model.isEmpty():
|
||||
print("⚠️ 空模型无法设置碰撞检测")
|
||||
return None
|
||||
|
||||
# 验证模型变换
|
||||
self._fixNodeTransform(model)
|
||||
|
||||
# 创建碰撞节点
|
||||
cNode = CollisionNode(f'modelCollision_{model.getName()}')
|
||||
|
||||
# 设置碰撞掩码
|
||||
cNode.setIntoCollideMask(BitMask32.bit(2)) # 用于鼠标选择
|
||||
|
||||
# 如果启用了模型间碰撞检测,添加额外的掩码
|
||||
if (hasattr(self.world, 'collision_manager') and
|
||||
self.world.collision_manager.model_collision_enabled):
|
||||
# 同时设置模型间碰撞掩码
|
||||
current_mask = cNode.getIntoCollideMask()
|
||||
model_collision_mask = BitMask32.bit(6) # MODEL_COLLISION
|
||||
cNode.setIntoCollideMask(current_mask | model_collision_mask)
|
||||
print(f"为 {model.getName()} 启用模型间碰撞检测")
|
||||
|
||||
# 获取模型的边界
|
||||
bounds = model.getBounds()
|
||||
if bounds.isEmpty():
|
||||
print(f"⚠️ 模型 {model.getName()} 边界为空,使用默认碰撞体")
|
||||
# 使用默认的小球体
|
||||
cSphere = CollisionSphere(0, 0, 0, 1.0)
|
||||
else:
|
||||
try:
|
||||
center = bounds.getCenter()
|
||||
radius = bounds.getRadius()
|
||||
|
||||
# 确保半径不为零
|
||||
if radius <= 0 or radius != radius: # 检查NaN
|
||||
radius = 1.0
|
||||
print(f"⚠️ 模型 {model.getName()} 半径无效,使用默认半径 1.0")
|
||||
|
||||
# 确保中心点有效
|
||||
if (center.getX() != center.getX() or
|
||||
center.getY() != center.getY() or
|
||||
center.getZ() != center.getZ()):
|
||||
center = Point3(0, 0, 0)
|
||||
print(f"⚠️ 模型 {model.getName()} 中心点无效,使用默认中心点")
|
||||
|
||||
cSphere = CollisionSphere(center, radius)
|
||||
except Exception as e:
|
||||
print(f"⚠️ 创建碰撞体时出错: {e}")
|
||||
cSphere = CollisionSphere(0, 0, 0, 1.0)
|
||||
|
||||
cNode.addSolid(cSphere)
|
||||
|
||||
# 将碰撞节点附加到模型上
|
||||
try:
|
||||
cNodePath = model.attachNewNode(cNode)
|
||||
except Exception as e:
|
||||
print(f"⚠️ 附加碰撞节点时出错: {e}")
|
||||
# 创建一个新的节点来附加
|
||||
cNodePath = self.world.render.attachNewNode(cNode)
|
||||
cNodePath.reparentTo(model)
|
||||
|
||||
# 根据调试设置决定是否显示碰撞体
|
||||
if hasattr(self.world, 'debug_collision') and self.world.debug_collision:
|
||||
cNodePath.show()
|
||||
else:
|
||||
cNodePath.hide()
|
||||
|
||||
# 为模型添加碰撞相关标签
|
||||
model.setTag("has_collision", "true")
|
||||
try:
|
||||
model.setTag("collision_radius", str(bounds.getRadius() if not bounds.isEmpty() else 1.0))
|
||||
except:
|
||||
model.setTag("collision_radius", "1.0")
|
||||
|
||||
print(f"✅ 为模型 {model.getName()} 设置碰撞检测完成")
|
||||
|
||||
return cNodePath
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 为模型 {model.getName()} 设置碰撞检测失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def _applyModelScale(self, model, scale_factor):
|
||||
"""应用模型特定缩放
|
||||
@ -640,140 +566,11 @@ class SceneManager:
|
||||
except Exception as e:
|
||||
print(f"应用模型缩放失败: {str(e)}")
|
||||
|
||||
def _applyMaterialsToModel(self, model):
|
||||
"""递归应用材质到模型的所有GeomNode"""
|
||||
|
||||
def apply_material(node_path, depth=0):
|
||||
indent = " " * depth
|
||||
try:
|
||||
print(f"{indent}处理节点: {node_path.getName()}")
|
||||
print(f"{indent}节点类型: {node_path.node().__class__.__name__}")
|
||||
|
||||
if isinstance(node_path.node(), GeomNode):
|
||||
print(f"{indent}发现GeomNode,处理材质")
|
||||
geom_node = node_path.node()
|
||||
|
||||
# 检查所有几何体的状态
|
||||
has_color = False
|
||||
color = None
|
||||
|
||||
# 首先检查节点自身的状态
|
||||
node_state = node_path.getState()
|
||||
if node_state.hasAttrib(MaterialAttrib.getClassType()):
|
||||
mat_attrib = node_state.getAttrib(MaterialAttrib.getClassType())
|
||||
node_material = mat_attrib.getMaterial()
|
||||
if node_material and node_material.hasDiffuse():
|
||||
color = node_material.getDiffuse()
|
||||
has_color = True
|
||||
print(f"{indent}从节点材质获取颜色: {color}")
|
||||
|
||||
# 检查FBX特有的属性
|
||||
for tag_key in node_path.getTagKeys():
|
||||
print(f"{indent}发现标签: {tag_key}")
|
||||
if "color" in tag_key.lower() or "diffuse" in tag_key.lower():
|
||||
tag_value = node_path.getTag(tag_key)
|
||||
print(f"{indent}颜色相关标签: {tag_key} = {tag_value}")
|
||||
|
||||
# 如果还没找到颜色,检查几何体
|
||||
if not has_color:
|
||||
for i in range(geom_node.getNumGeoms()):
|
||||
try:
|
||||
geom = geom_node.getGeom(i)
|
||||
state = geom_node.getGeomState(i)
|
||||
|
||||
# 检查顶点颜色
|
||||
vdata = geom.getVertexData()
|
||||
if vdata:
|
||||
format = vdata.getFormat()
|
||||
if format:
|
||||
for j in range(format.getNumColumns()):
|
||||
try:
|
||||
column = format.getColumn(j)
|
||||
# InternalName对象需要使用getName()转换为字符串
|
||||
column_name = column.getName().getName()
|
||||
if "color" in column_name.lower():
|
||||
print(f"{indent}发现顶点颜色数据: {column_name}")
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
# 检查材质属性
|
||||
if state.hasAttrib(MaterialAttrib.getClassType()):
|
||||
mat_attrib = state.getAttrib(MaterialAttrib.getClassType())
|
||||
orig_material = mat_attrib.getMaterial()
|
||||
if orig_material:
|
||||
if orig_material.hasBaseColor():
|
||||
color = orig_material.getBaseColor()
|
||||
has_color = True
|
||||
print(f"{indent}从基础颜色获取: {color}")
|
||||
break
|
||||
elif orig_material.hasDiffuse():
|
||||
color = orig_material.getDiffuse()
|
||||
has_color = True
|
||||
print(f"{indent}从漫反射颜色获取: {color}")
|
||||
break
|
||||
|
||||
# 检查颜色属性
|
||||
if not has_color and state.hasAttrib(ColorAttrib.getClassType()):
|
||||
color_attrib = state.getAttrib(ColorAttrib.getClassType())
|
||||
if not color_attrib.isOff():
|
||||
color = color_attrib.getColor()
|
||||
has_color = True
|
||||
print(f"{indent}从颜色属性获取: {color}")
|
||||
break
|
||||
except Exception as geom_error:
|
||||
print(f"{indent}处理几何体 {i} 时出错: {geom_error}")
|
||||
continue
|
||||
|
||||
# 创建新材质
|
||||
material = Material()
|
||||
if has_color:
|
||||
print(f"{indent}应用找到的颜色: {color}")
|
||||
try:
|
||||
material.setDiffuse(color)
|
||||
material.setBaseColor(color) # 同时设置基础颜色
|
||||
node_path.setColor(color)
|
||||
except Exception as color_error:
|
||||
print(f"{indent}设置颜色时出错: {color_error}")
|
||||
material.setDiffuse((0.8, 0.8, 0.8, 1.0))
|
||||
else:
|
||||
print(f"{indent}使用默认颜色")
|
||||
material.setDiffuse((0.8, 0.8, 0.8, 1.0))
|
||||
|
||||
# 设置其他材质属性
|
||||
material.setAmbient((0.2, 0.2, 0.2, 1.0))
|
||||
material.setSpecular((0.5, 0.5, 0.5, 1.0))
|
||||
material.setShininess(32.0)
|
||||
|
||||
# 应用材质
|
||||
node_path.setMaterial(material)
|
||||
print(f"{indent}几何体数量: {geom_node.getNumGeoms()}")
|
||||
|
||||
except Exception as node_error:
|
||||
print(f"{indent}处理节点 {node_path.getName()} 时出错: {node_error}")
|
||||
|
||||
# 递归处理子节点
|
||||
child_count = node_path.getNumChildren()
|
||||
print(f"{indent}子节点数量: {child_count}")
|
||||
for i in range(child_count):
|
||||
try:
|
||||
child = node_path.getChild(i)
|
||||
apply_material(child, depth + 1)
|
||||
except Exception as child_error:
|
||||
print(f"{indent}处理子节点 {i} 时出错: {child_error}")
|
||||
continue
|
||||
|
||||
# 应用材质
|
||||
print("\n开始递归应用材质...")
|
||||
try:
|
||||
apply_material(model)
|
||||
except Exception as e:
|
||||
print(f"应用材质时出错: {e}")
|
||||
print("=== 材质设置完成 ===\n")
|
||||
|
||||
def _adjustModelToGround(self, model):
|
||||
"""智能调整模型到地面,但保持原有缩放结构"""
|
||||
try:
|
||||
print("调整模型位置到地面...")
|
||||
#print("调整模型位置到地面...")
|
||||
|
||||
# 获取模型的边界框
|
||||
bounds = model.getBounds()
|
||||
@ -793,9 +590,9 @@ class SceneManager:
|
||||
# 设置模型位置:X,Y居中,Z调整到地面
|
||||
model.setPos(0, 0, ground_offset)
|
||||
|
||||
print(f"模型边界: 最小点{min_point}, 中心{center}")
|
||||
print(f"地面偏移: {ground_offset}")
|
||||
print(f"最终位置: {model.getPos()}")
|
||||
#print(f"模型边界: 最小点{min_point}, 中心{center}")
|
||||
#print(f"地面偏移: {ground_offset}")
|
||||
#print(f"最终位置: {model.getPos()}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"调整模型位置失败: {str(e)}")
|
||||
@ -1071,7 +868,7 @@ class SceneManager:
|
||||
|
||||
# 根据调试设置决定是否显示碰撞体
|
||||
if hasattr(self.world, 'debug_collision') and self.world.debug_collision:
|
||||
cNodePath.hide()
|
||||
cNodePath.show()
|
||||
else:
|
||||
cNodePath.hide()
|
||||
|
||||
|
||||
@ -25,9 +25,9 @@ class CrossPlatformPathHandler:
|
||||
def normalize_model_path(self, filepath):
|
||||
"""标准化模型文件路径"""
|
||||
try:
|
||||
print(f"\n=== 路径标准化处理 ===")
|
||||
print(f"原始路径: {filepath}")
|
||||
print(f"当前系统: {self.system}")
|
||||
#print(f"\n=== 路径标准化处理 ===")
|
||||
#print(f"原始路径: {filepath}")
|
||||
#print(f"当前系统: {self.system}")
|
||||
|
||||
# 步骤1: 检查原始路径是否存在
|
||||
if self._check_file_exists(filepath):
|
||||
@ -54,10 +54,6 @@ class CrossPlatformPathHandler:
|
||||
def _check_file_exists(self, filepath):
|
||||
"""检查文件是否存在"""
|
||||
exists = os.path.exists(filepath)
|
||||
if exists:
|
||||
print(f"✓ 文件存在: {filepath}")
|
||||
else:
|
||||
print(f"⚠️ 文件不存在: {filepath}")
|
||||
return exists
|
||||
|
||||
def _panda3d_normalize(self, filepath):
|
||||
|
||||
@ -25,10 +25,39 @@ class InterfaceManager:
|
||||
# 更新场景树
|
||||
self.world.scene_manager.updateSceneTree()
|
||||
|
||||
def onTreeWidgetClicked(self, index):
|
||||
"""处理树形控件点击事件(包括空白区域)"""
|
||||
# 检查点击的是否是空白区域
|
||||
if not self.treeWidget.itemFromIndex(index): # 点击的是空白区域
|
||||
self.world.selection.updateSelection(None)
|
||||
self.world.property_panel.clearPropertyPanel()
|
||||
print("点击树形控件空白区域,清除选中状态")
|
||||
|
||||
def onTreeCurrentItemChanged(self, current, previous):
|
||||
"""处理树形控件当前选中项改变事件"""
|
||||
# 当 current 为 None 时,表示点击了空白区域
|
||||
if current is None:
|
||||
self.world.selection.updateSelection(None)
|
||||
print("点击空白区域,清除选中状态")
|
||||
# 当 current 不为 None 时,表示选中了某个项目
|
||||
else:
|
||||
# 更新选择状态
|
||||
nodePath = current.data(0, Qt.UserRole)
|
||||
if nodePath:
|
||||
self.world.selected_np = nodePath
|
||||
self.world.selection.updateSelection(nodePath)
|
||||
self.world.property_panel.updatePropertyPanel(current)
|
||||
print(f"树形控件选中项改变: {current.text(0)}")
|
||||
|
||||
def onTreeItemClicked(self, item, column):
|
||||
"""处理树形控件项目点击事件"""
|
||||
if not item:
|
||||
print(f"树形控件点击事件触发,item: {item}, column: {column}")
|
||||
|
||||
# 检查是否点击了空白区域
|
||||
# 当点击空白区域时,item可能是一个空的QTreeWidgetItem对象
|
||||
if not item or (item.text(0) == "" and item.data(0, Qt.UserRole) is None):
|
||||
self.world.selection.updateSelection(None)
|
||||
print("点击空白区域,清除选中状态")
|
||||
return
|
||||
|
||||
self.world.property_panel.updatePropertyPanel(item)
|
||||
@ -39,15 +68,12 @@ class InterfaceManager:
|
||||
# 更新选择状态
|
||||
self.world.selected_np = nodePath
|
||||
self.world.selection.updateSelection(nodePath)
|
||||
|
||||
# 更新属性面板
|
||||
#self.world.property_panel.updatePropertyPanel(item)
|
||||
|
||||
print(f"树形控件点击: {item.text(0)}")
|
||||
else:
|
||||
# 如果没有节点对象,清除选择
|
||||
self.world.selection.updateSelection(None)
|
||||
#self.world.property_panel.clearPropertyPanel()
|
||||
self.world.property_panel.clearPropertyPanel()
|
||||
print("点击了无数据项,清除选中状态")
|
||||
|
||||
# def showTreeContextMenu(self, position):
|
||||
# """显示树形控件的右键菜单"""
|
||||
|
||||
@ -932,7 +932,6 @@ class MainWindow(QMainWindow):
|
||||
padding-top: 10px; /* 增加顶部内边距,使标题和内容分离 */
|
||||
}
|
||||
QGroupBox::title {
|
||||
subline-offset: -2px;
|
||||
padding: 0 8px;
|
||||
color: #c0c0e0;
|
||||
font-weight: 500;
|
||||
@ -1029,7 +1028,6 @@ class MainWindow(QMainWindow):
|
||||
padding-top: 10px; /* 增加顶部内边距,使标题和内容分离 */
|
||||
}
|
||||
QGroupBox::title {
|
||||
subline-offset: -2px;
|
||||
padding: 0 8px;
|
||||
color: #c0c0e0;
|
||||
font-weight: 500;
|
||||
@ -1139,44 +1137,44 @@ class MainWindow(QMainWindow):
|
||||
self.bottomDock.setWidget(self.fileView)
|
||||
self.addDockWidget(Qt.BottomDockWidgetArea, self.bottomDock)
|
||||
|
||||
# # 创建底部停靠控制台
|
||||
# self.consoleDock = QDockWidget("控制台", self)
|
||||
# self.consoleDock.setStyleSheet("""
|
||||
# QDockWidget {
|
||||
# background-color: #252538;
|
||||
# color: #e0e0ff;
|
||||
# border: 1px solid #3a3a4a;
|
||||
# }
|
||||
# QDockWidget::title {
|
||||
# background-color: #2d2d44;
|
||||
# padding: 0px 0px; /* 增加内边距,提供更多的垂直空间 */
|
||||
# border-bottom: 0px solid #3a3a4a;
|
||||
# }
|
||||
# QDockWidget::close-button, QDockWidget::float-button {
|
||||
# background-color: #8b5cf6;
|
||||
# border: none;
|
||||
# icon-size: 8px; /* 调整图标大小 */
|
||||
# border-radius: 4px; /* 增加圆角 */
|
||||
# }
|
||||
# QDockWidget::close-button:hover, QDockWidget::float-button:hover {
|
||||
# background-color: #7c3aed; /* 悬停时显示较亮的背景 */
|
||||
# }
|
||||
# QDockWidget::close-button:pressed, QDockWidget::float-button:pressed {
|
||||
# background-color: #8b5cf6; /* 按下时显示紫色高亮 */
|
||||
# }
|
||||
# """)
|
||||
# self.consoleView = CustomConsoleDockWidget(self.world)
|
||||
# # 为控制台添加样式
|
||||
# self.consoleView.setStyleSheet("""
|
||||
# QTextEdit {
|
||||
# background-color: #1e1e2e;
|
||||
# color: #e0e0ff;
|
||||
# border: 1px solid #3a3a4a;
|
||||
# font-family: 'Consolas', 'Monaco', monospace;
|
||||
# }
|
||||
# """)
|
||||
# self.consoleDock.setWidget(self.consoleView)
|
||||
# self.addDockWidget(Qt.BottomDockWidgetArea, self.consoleDock)
|
||||
# 创建底部停靠控制台
|
||||
self.consoleDock = QDockWidget("控制台", self)
|
||||
self.consoleDock.setStyleSheet("""
|
||||
QDockWidget {
|
||||
background-color: #252538;
|
||||
color: #e0e0ff;
|
||||
border: 1px solid #3a3a4a;
|
||||
}
|
||||
QDockWidget::title {
|
||||
background-color: #2d2d44;
|
||||
padding: 0px 0px; /* 增加内边距,提供更多的垂直空间 */
|
||||
border-bottom: 0px solid #3a3a4a;
|
||||
}
|
||||
QDockWidget::close-button, QDockWidget::float-button {
|
||||
background-color: #8b5cf6;
|
||||
border: none;
|
||||
icon-size: 8px; /* 调整图标大小 */
|
||||
border-radius: 4px; /* 增加圆角 */
|
||||
}
|
||||
QDockWidget::close-button:hover, QDockWidget::float-button:hover {
|
||||
background-color: #7c3aed; /* 悬停时显示较亮的背景 */
|
||||
}
|
||||
QDockWidget::close-button:pressed, QDockWidget::float-button:pressed {
|
||||
background-color: #8b5cf6; /* 按下时显示紫色高亮 */
|
||||
}
|
||||
""")
|
||||
self.consoleView = CustomConsoleDockWidget(self.world)
|
||||
# 为控制台添加样式
|
||||
self.consoleView.setStyleSheet("""
|
||||
QTextEdit {
|
||||
background-color: #1e1e2e;
|
||||
color: #e0e0ff;
|
||||
border: 1px solid #3a3a4a;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
}
|
||||
""")
|
||||
self.consoleDock.setWidget(self.consoleView)
|
||||
self.addDockWidget(Qt.BottomDockWidgetArea, self.consoleDock)
|
||||
|
||||
# 将右侧停靠窗口设为标签形式
|
||||
# self.tabifyDockWidget(self.rightDock, self.scriptDock)
|
||||
@ -1186,7 +1184,7 @@ class MainWindow(QMainWindow):
|
||||
self.bottomDock.raise_()
|
||||
self.rightDock.raise_()
|
||||
self.scriptDock.raise_()
|
||||
# self.consoleDock.raise_()
|
||||
self.consoleDock.raise_()
|
||||
self.leftDock.raise_()
|
||||
# =========================================================================
|
||||
# ↓↓↓ 新增代码:为停靠窗口的标签栏(QTabBar)设置统一样式 ↓↓↓
|
||||
|
||||
@ -4725,7 +4725,7 @@ class PropertyPanelManager:
|
||||
material_name = material.get_name() if hasattr(material,
|
||||
'get_name') and material.get_name() else f"材质{i + 1}"
|
||||
unique_name = f"{material_name}({model_name})"
|
||||
print(f"材质 {i}: 未找到几何节点,使用材质名称 '{material_name}'")
|
||||
#print(f"材质 {i}: 未找到几何节点,使用材质名称 '{material_name}'")
|
||||
|
||||
# 处理重复名称
|
||||
if unique_name in name_counter:
|
||||
@ -4764,7 +4764,7 @@ class PropertyPanelManager:
|
||||
# 基础颜色编辑
|
||||
base_color = self._getOrCreateMaterialBaseColor(material)
|
||||
if base_color is not None:
|
||||
print(f"材质基础颜色: {base_color}")
|
||||
#print(f"材质基础颜色: {base_color}")
|
||||
|
||||
# 基础颜色标题
|
||||
color_row = 2 if material_status != "标准PBR材质" else 1
|
||||
@ -5153,7 +5153,7 @@ class PropertyPanelManager:
|
||||
try:
|
||||
# 方法1: 尝试获取base_color属性
|
||||
if hasattr(material, 'base_color') and material.base_color is not None:
|
||||
print(f"✓ 找到base_color属性: {material.base_color}")
|
||||
#print(f"✓ 找到base_color属性: {material.base_color}")
|
||||
return material.base_color
|
||||
|
||||
# 方法2: 尝试调用get_base_color方法
|
||||
@ -6786,7 +6786,7 @@ class PropertyPanelManager:
|
||||
#print(f"找到匹配的几何节点: {geom_np.get_name()}")
|
||||
return geom_np
|
||||
|
||||
print("未找到匹配的几何节点")
|
||||
#print("未找到匹配的几何节点")
|
||||
return None
|
||||
|
||||
def _findSpecificGeomNodeForMaterial(self, target_material):
|
||||
@ -7755,19 +7755,19 @@ class PropertyPanelManager:
|
||||
# 方法1: 直接控制Day Time Editor中的sun_azimuth节点
|
||||
success = self._updateDayTimeEditorSetting("scattering", "sun_azimuth", azimuth_value)
|
||||
if success:
|
||||
print(f"✅ 通过Day Time Editor设置方位角: {azimuth_value}°")
|
||||
#print(f"✅ 通过Day Time Editor设置方位角: {azimuth_value}°")
|
||||
return
|
||||
|
||||
# 方法2: 使用我们的太阳控制器作为备用
|
||||
if hasattr(self.world, 'sun_controller'):
|
||||
self.world.sun_controller.set_sun_azimuth(azimuth_value)
|
||||
print(f"✅ 通过太阳控制器设置方位角: {azimuth_value}°")
|
||||
#print(f"✅ 通过太阳控制器设置方位角: {azimuth_value}°")
|
||||
return
|
||||
|
||||
# 方法3: 直接调用主程序的太阳控制方法
|
||||
if hasattr(self.world, 'setSunAzimuth'):
|
||||
self.world.setSunAzimuth(azimuth_value)
|
||||
print(f"✅ 通过主程序方法设置方位角: {azimuth_value}°")
|
||||
#print(f"✅ 通过主程序方法设置方位角: {azimuth_value}°")
|
||||
return
|
||||
|
||||
print("⚠️ 所有方位角设置方法都不可用")
|
||||
@ -7783,19 +7783,19 @@ class PropertyPanelManager:
|
||||
# 方法1: 直接控制Day Time Editor中的sun_altitude节点
|
||||
success = self._updateDayTimeEditorSetting("scattering", "sun_altitude", altitude_value)
|
||||
if success:
|
||||
print(f"✅ 通过Day Time Editor设置高度角: {altitude_value}°")
|
||||
#print(f"✅ 通过Day Time Editor设置高度角: {altitude_value}°")
|
||||
return
|
||||
|
||||
# 方法2: 使用我们的太阳控制器作为备用
|
||||
if hasattr(self.world, 'sun_controller'):
|
||||
self.world.sun_controller.set_sun_altitude(altitude_value)
|
||||
print(f"✅ 通过太阳控制器设置高度角: {altitude_value}°")
|
||||
#print(f"✅ 通过太阳控制器设置高度角: {altitude_value}°")
|
||||
return
|
||||
|
||||
# 方法3: 直接调用主程序的太阳控制方法
|
||||
if hasattr(self.world, 'setSunAltitude'):
|
||||
self.world.setSunAltitude(altitude_value)
|
||||
print(f"✅ 通过主程序方法设置高度角: {altitude_value}°")
|
||||
#print(f"✅ 通过主程序方法设置高度角: {altitude_value}°")
|
||||
return
|
||||
|
||||
print("⚠️ 所有高度角设置方法都不可用")
|
||||
@ -8072,7 +8072,7 @@ class PropertyPanelManager:
|
||||
format_info = self._getModelFormat(origin_model)
|
||||
processed = []
|
||||
|
||||
print(f"[动画分析] 格式: {format_info}, 原始动画名称: {anim_names}")
|
||||
#print(f"[动画分析] 格式: {format_info}, 原始动画名称: {anim_names}")
|
||||
|
||||
for name in anim_names:
|
||||
display_name = name
|
||||
@ -8106,7 +8106,7 @@ class PropertyPanelManager:
|
||||
display_name = name
|
||||
|
||||
processed.append((display_name, original_name))
|
||||
print(f"[动画分析] {original_name} → {display_name}")
|
||||
#print(f"[动画分析] {original_name} → {display_name}")
|
||||
|
||||
return processed
|
||||
|
||||
@ -8140,7 +8140,7 @@ class PropertyPanelManager:
|
||||
if frames > 1:
|
||||
valid_anims += 1
|
||||
total_frames += frames
|
||||
print(f"[动画分析] '{anim_name}': {frames} 帧")
|
||||
#print(f"[动画分析] '{anim_name}': {frames} 帧")
|
||||
else:
|
||||
print(f"[动画分析] '{anim_name}': 无有效帧数 ({frames})")
|
||||
except Exception as e:
|
||||
@ -8165,7 +8165,7 @@ class PropertyPanelManager:
|
||||
if not filepath:
|
||||
return None
|
||||
|
||||
print(f"[Actor加载] 尝试加载: {filepath}")
|
||||
#print(f"[Actor加载] 尝试加载: {filepath}")
|
||||
|
||||
# 检查是否是 FBX 文件,如果是,使用专门的 FBX 动画加载器
|
||||
if filepath.lower().endswith('.fbx'):
|
||||
@ -8174,13 +8174,13 @@ class PropertyPanelManager:
|
||||
# 其他格式使用标准 Actor 加载
|
||||
try:
|
||||
import gltf
|
||||
print(f"[GLTF加载] 尝试加载: {filepath}")
|
||||
#print(f"[GLTF加载] 尝试加载: {filepath}")
|
||||
# test_actor=Actor(NodePath(gltf._loader.GltfLoader.load_file(filepath,None)))
|
||||
test_actor=Actor(NodePath(gltf.load_model(filepath,None)))
|
||||
anims = test_actor.getAnimNames()
|
||||
test_actor.reparentTo(self.world.render)
|
||||
self._actor_cache[origin_model] = test_actor
|
||||
print(f"[Actor加载] 标准加载检测到动画: {anims}")
|
||||
#print(f"[Actor加载] 标准加载检测到动画: {anims}")
|
||||
if not anims:
|
||||
test_actor.cleanup()
|
||||
test_actor.removeNode()
|
||||
@ -8193,14 +8193,14 @@ class PropertyPanelManager:
|
||||
def _createFBXActor(self, origin_model, filepath):
|
||||
"""专门为 FBX 文件创建 Actor,使用转换方式获取真实动画"""
|
||||
try:
|
||||
print(f"[FBX动画] 开始处理 FBX 动画: {filepath}")
|
||||
#print(f"[FBX动画] 开始处理 FBX 动画: {filepath}")
|
||||
|
||||
# 方法1: 尝试转换 FBX 为包含动画的格式
|
||||
converted_actor = self._convertFBXToActor(filepath)
|
||||
if converted_actor:
|
||||
converted_actor.reparentTo(self.world.render)
|
||||
self._actor_cache[origin_model] = converted_actor
|
||||
print(f"[FBX动画] 转换成功,动画: {converted_actor.getAnimNames()}")
|
||||
#print(f"[FBX动画] 转换成功,动画: {converted_actor.getAnimNames()}")
|
||||
return converted_actor
|
||||
|
||||
# 方法2: 直接加载但进行动画数据修复
|
||||
@ -8210,7 +8210,7 @@ class PropertyPanelManager:
|
||||
if fixed_actor and fixed_actor.getAnimNames():
|
||||
fixed_actor.reparentTo(self.world.render)
|
||||
self._actor_cache[origin_model] = fixed_actor
|
||||
print(f"[FBX动画] 修复成功,动画: {fixed_actor.getAnimNames()}")
|
||||
#print(f"[FBX动画] 修复成功,动画: {fixed_actor.getAnimNames()}")
|
||||
return fixed_actor
|
||||
|
||||
print(f"[FBX动画] 无法获取有效动画数据")
|
||||
|
||||
@ -47,7 +47,6 @@ class NewProjectDialog(QDialog):
|
||||
padding-top: 10px; /* 增加顶部内边距,为标题留出空间 */
|
||||
}
|
||||
QGroupBox::title {
|
||||
subline-offset: -2px;
|
||||
padding: 0 8px;
|
||||
color: #c0c0e0;
|
||||
font-weight: 500;
|
||||
@ -217,7 +216,7 @@ class CustomPanda3DWidget(QPanda3DWidget):
|
||||
event.acceptProposedAction()
|
||||
else:
|
||||
event.ignore()
|
||||
|
||||
|
||||
def wheelEvent(self, event):
|
||||
"""处理滚轮事件"""
|
||||
if event.angleDelta().y() > 0:
|
||||
@ -1523,9 +1522,7 @@ class CustomTreeWidget(QTreeWidget):
|
||||
self.initData()
|
||||
self.setupUI() # 初始化界面
|
||||
self.setupContextMenu() # 初始化右键菜单
|
||||
|
||||
self.setupDragDrop() # 设置拖拽功能
|
||||
|
||||
self.original_scales={}
|
||||
|
||||
self.setStyleSheet("""
|
||||
@ -1960,7 +1957,7 @@ class CustomTreeWidget(QTreeWidget):
|
||||
elif indicator_pos == QAbstractItemView.DropIndicatorPosition.OnViewport:
|
||||
indicator_str = "OnViewport"
|
||||
|
||||
print(f'indicator pos: {indicator_str} (value: {int(indicator_pos)})')
|
||||
#print(f'indicator pos: {indicator_str} (value: {int(indicator_pos)})')
|
||||
|
||||
if event.source() != self:
|
||||
event.ignore()
|
||||
@ -2192,7 +2189,7 @@ class CustomTreeWidget(QTreeWidget):
|
||||
# 获取对应的Panda3D节点
|
||||
new_panda_node = new_selection_item.data(0, Qt.UserRole)
|
||||
# 调用您提供的函数来更新选择状态和属性面板
|
||||
self.update_selection_and_properties(new_panda_node, new_selection_item)
|
||||
#self.update_selection_and_properties(new_panda_node, new_selection_item)
|
||||
else:
|
||||
# 如果连根节点都没有了(例如清空场景),则清空选择
|
||||
self.update_selection_and_properties(None, None)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user