信息面板GUI完成,http数据连接成功,9.10合并
This commit is contained in:
parent
f27b4a0bbf
commit
605f2bbdcd
4
.idea/EG.iml
generated
4
.idea/EG.iml
generated
@ -1,9 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.10" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -116,6 +116,7 @@ class InternalLightManager(object):
|
||||
source.set_slot(slot)
|
||||
|
||||
def remove_light(self, light):
|
||||
print("111111111111111111111111111111111111111111111111")
|
||||
assert light is not None
|
||||
if not light.has_slot():
|
||||
print("ERROR: Could not detach light, light was not attached!")
|
||||
|
||||
1054
core/InfoPanelManager.py
Normal file
1054
core/InfoPanelManager.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -38,7 +38,6 @@ class CoreWorld(Panda3DWorld):
|
||||
self._setupLighting()
|
||||
self._setupGround()
|
||||
self._loadFont()
|
||||
#self.load_and_play_glb_model()
|
||||
|
||||
def _setupResourcePaths(self):
|
||||
"""设置Panda3D资源搜索路径,确保能正确找到Resources文件夹中的模型和贴图"""
|
||||
@ -100,33 +99,6 @@ class CoreWorld(Panda3DWorld):
|
||||
except Exception as e:
|
||||
print(f"⚠️ 设置资源路径失败: {e}")
|
||||
|
||||
def load_and_play_glb_model(self):
|
||||
"""加载 glTF 模型并播放动画"""
|
||||
try:
|
||||
from direct.actor.Actor import Actor
|
||||
|
||||
# 使用 Actor 类加载 glTF 模型
|
||||
self.model = Actor("/home/tiger/cube.glb")
|
||||
print("模型加载成功!")
|
||||
self.model.reparentTo(self.render)
|
||||
self.model.setPos(0, 10, 0)
|
||||
self.model.setScale(10)
|
||||
|
||||
# 找出所有 AnimBundleNode
|
||||
print(f"开始寻找动画AnimationBundleNode...")
|
||||
for np in self.model.findAllMatches("**/+AnimBundleNode"):
|
||||
print(f"找到AnimBundleNode: {np.getName()}")
|
||||
bundle = np.node().getBundle()
|
||||
for i in range(bundle.getNumAnimations()):
|
||||
anim_name = bundle.getAnimation(i).getName()
|
||||
print("动画名:", anim_name)
|
||||
# 这里不能直接 play,需要手动把 AnimControl 绑定到节点
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f"模型加载失败: {e}")
|
||||
return None
|
||||
|
||||
def diagnose_fbx_loading(self, fbx_path):
|
||||
"""诊断FBX加载状态"""
|
||||
print("=== FBX加载诊断 ===")
|
||||
|
||||
@ -683,7 +683,7 @@ class GUIManager:
|
||||
textNodePath.setPos(*pos)
|
||||
textNodePath.setScale(size)
|
||||
textNodePath.setColor(1, 1, 0, 1)
|
||||
textNodePath.setBillboardAxis() # 让文本总是面向相机
|
||||
#textNodePath.setBillboardAxis() # 让文本总是面向相机
|
||||
textNodePath.setName(text_name)
|
||||
|
||||
# 设置节点标签
|
||||
@ -3207,7 +3207,7 @@ class GUIManager:
|
||||
try:
|
||||
gui_type = gui_element.getTag("gui_type")
|
||||
|
||||
if gui_type in ["3d_text", "3d_image", "video_screen"]:
|
||||
if gui_type in ["3d_text", "3d_image", "video_screen","info_panel"]:
|
||||
current_pos = gui_element.getPos()
|
||||
|
||||
if axis == "x":
|
||||
@ -3241,7 +3241,7 @@ class GUIManager:
|
||||
if value == 0:
|
||||
value = 0.01
|
||||
|
||||
if gui_type in ["3d_text", "3d_image","video_screen","virtual_screen"]:
|
||||
if gui_type in ["3d_text", "3d_image","video_screen","virtual_screen","info_panel"]:
|
||||
# 3D元素处理
|
||||
if axis == "x":
|
||||
new_scale = (value, current_scale.getY(), current_scale.getZ())
|
||||
|
||||
11
main.py
11
main.py
@ -1,5 +1,6 @@
|
||||
import warnings
|
||||
|
||||
from core.InfoPanelManager import InfoPanelManager
|
||||
from demo.video_integration import VideoManager
|
||||
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
||||
@ -39,7 +40,7 @@ from direct.task import Task
|
||||
from direct.task.TaskManagerGlobal import taskMgr
|
||||
from direct.showbase.ShowBase import ShowBase
|
||||
from direct.showbase.DirectObject import DirectObject
|
||||
from direct.showbase.ShowBaseGlobal import globalClock
|
||||
from direct.showbase.ShowBaseGlobal import globalClock, aspect2d
|
||||
import os
|
||||
import json
|
||||
import datetime
|
||||
@ -97,6 +98,8 @@ class MyWorld(CoreWorld):
|
||||
self.terrain_edit_strength=0.3
|
||||
self.terrain_edit_operation = "add"
|
||||
|
||||
self.info_panel_manager = InfoPanelManager(self)
|
||||
|
||||
# 初始化碰撞管理器
|
||||
from core.collision_manager import CollisionManager
|
||||
self.collision_manager = CollisionManager(self)
|
||||
@ -489,9 +492,9 @@ class MyWorld(CoreWorld):
|
||||
"""异步导入模型"""
|
||||
return self.scene_manager.importModelAsync(filepath)
|
||||
|
||||
# def loadAnimatedModel(self, model_path, anims=None):
|
||||
# """加载带动画的模型"""
|
||||
# return self.scene_manager.loadAnimatedModel(model_path, anims)
|
||||
def loadAnimatedModel(self, model_path, anims=None):
|
||||
"""加载带动画的模型"""
|
||||
return self.scene_manager.loadAnimatedModel(model_path, anims)
|
||||
|
||||
# 材质和几何体处理方法 - 代理到scene_manager
|
||||
def processMaterials(self, model):
|
||||
|
||||
@ -89,34 +89,27 @@ class SceneManager:
|
||||
# ==================== 模型导入和处理 ====================
|
||||
|
||||
def importModel(self, filepath, apply_unit_conversion=False, normalize_scales=True, auto_convert_to_glb=True):
|
||||
"""导入模型到场景 - 增强错误处理"""
|
||||
"""导入模型到场景
|
||||
|
||||
Args:
|
||||
filepath: 模型文件路径
|
||||
apply_unit_conversion: 是否应用单位转换(主要针对FBX文件)
|
||||
normalize_scales: 是否标准化子节点缩放(推荐开启)
|
||||
auto_convert_to_glb: 是否自动将非GLB格式转换为GLB以获得更好的动画支持
|
||||
"""
|
||||
try:
|
||||
# 预处理文件路径和转换
|
||||
filepath = util.normalize_model_path(filepath)
|
||||
original_filepath = filepath
|
||||
print(f"\n💾 开始导入模型: {os.path.basename(filepath)}")
|
||||
print(f"\n=== 开始导入模型: {filepath} ===")
|
||||
print(f"单位转换: {'开启' if apply_unit_conversion else '关闭'}")
|
||||
print(f"缩放标准化: {'开启' if normalize_scales else '关闭'}")
|
||||
print(f"自动转换GLB: {'开启' if auto_convert_to_glb else '关闭'}")
|
||||
|
||||
# 检查文件是否存在
|
||||
if not os.path.exists(filepath):
|
||||
print(f"❌ 模型文件不存在: {filepath}")
|
||||
return None
|
||||
filepath = util.normalize_model_path(filepath)
|
||||
original_filepath = filepath
|
||||
|
||||
# 检查文件大小
|
||||
file_size = os.path.getsize(filepath)
|
||||
if file_size == 0:
|
||||
print(f"❌ 模型文件为空: {filepath}")
|
||||
return None
|
||||
|
||||
print(f"文件大小: {file_size} 字节")
|
||||
|
||||
# 检查是否需要转换为GLB
|
||||
# 检查是否需要转换为GLB以获得更好的动画支持
|
||||
if auto_convert_to_glb and self._shouldConvertToGLB(filepath):
|
||||
print(f"🔄 检测到需要转换的格式,尝试转换为GLB...")
|
||||
converted_path = self._convertToGLBWithProgress(filepath)
|
||||
if converted_path and os.path.exists(converted_path) and os.path.getsize(converted_path) > 0:
|
||||
if converted_path:
|
||||
print(f"✅ 转换成功: {converted_path}")
|
||||
filepath = converted_path
|
||||
# 显示成功消息
|
||||
@ -128,250 +121,73 @@ class SceneManager:
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
print(f"⚠️ 转换失败或文件无效,使用原始文件")
|
||||
# 验证原始文件是否有效
|
||||
if not os.path.exists(original_filepath) or os.path.getsize(original_filepath) == 0:
|
||||
print(f"❌ 原始文件也无效: {original_filepath}")
|
||||
return None
|
||||
filepath = original_filepath
|
||||
print(f"⚠️ 转换失败,使用原始文件")
|
||||
|
||||
# 直接在render根节点下创建模型
|
||||
print("--- 在根节点下创建模型实例 ---")
|
||||
|
||||
# 添加模型加载前的安全检查
|
||||
print("准备加载模型...")
|
||||
|
||||
# 使用try-except包装模型加载过程
|
||||
model = None
|
||||
max_retry_attempts = 3 # 最大重试次数
|
||||
|
||||
for attempt in range(max_retry_attempts):
|
||||
try:
|
||||
print(f"直接从文件加载模型... (尝试 {attempt + 1}/{max_retry_attempts})")
|
||||
# 总是重新加载模型以确保材质信息完整
|
||||
# 不使用ModelPool缓存,避免材质信息丢失问题
|
||||
print("直接从文件加载模型...")
|
||||
model = self.world.loader.loadModel(filepath)
|
||||
|
||||
if model:
|
||||
print(f"✅ 模型加载成功: {model}")
|
||||
break
|
||||
else:
|
||||
print(f"⚠️ 加载模型返回空对象 (尝试 {attempt + 1})")
|
||||
if attempt < max_retry_attempts - 1:
|
||||
import time
|
||||
time.sleep(0.1) # 短暂延迟后重试
|
||||
|
||||
except AssertionError as ae:
|
||||
error_msg = str(ae)
|
||||
if "mismatched number of frames" in error_msg:
|
||||
print(f"⚠️ 动画帧数不匹配错误 (尝试 {attempt + 1}): {error_msg}")
|
||||
# 清理模型池并重试
|
||||
if attempt < max_retry_attempts - 1:
|
||||
self._clearModelCache(filepath)
|
||||
import time
|
||||
time.sleep(0.2) # 增加延迟后重试
|
||||
else:
|
||||
# 最后一次尝试失败,尝试无动画加载
|
||||
print("🔄 尝试无动画加载...")
|
||||
model = self._loadModelWithoutAnimations(filepath)
|
||||
else:
|
||||
raise ae # 其他断言错误直接抛出
|
||||
|
||||
except Exception as load_error:
|
||||
print(f"❌ 模型加载过程出错 (尝试 {attempt + 1}): {str(load_error)}")
|
||||
if attempt < max_retry_attempts - 1:
|
||||
import time
|
||||
time.sleep(0.1) # 短暂延迟后重试
|
||||
else:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
if not model:
|
||||
print("❌ 所有加载尝试都失败了")
|
||||
print("加载模型失败")
|
||||
return None
|
||||
|
||||
# 设置模型名称
|
||||
model_name = os.path.basename(filepath)
|
||||
model.setName(model_name)
|
||||
|
||||
# 将模型挂载到render根节点
|
||||
# 将模型添加到场景
|
||||
model.reparentTo(self.world.render)
|
||||
|
||||
# 设置标签和路径信息
|
||||
# 保存原始路径和转换后的路径
|
||||
model.setTag("model_path", filepath)
|
||||
model.setTag("original_path", original_filepath)
|
||||
model.setTag("file", model.getName())
|
||||
model.setTag("is_model_root", "1")
|
||||
model.setTag("is_scene_element", "1")
|
||||
model.setTag("created_by_user", "1")
|
||||
|
||||
if filepath != original_filepath:
|
||||
model.setTag("converted_from", os.path.splitext(original_filepath)[1])
|
||||
model.setTag("converted_to_glb", "true")
|
||||
|
||||
# 应用处理选项
|
||||
# 对于GLB文件,通常不需要单位转换,因为它们已经是标准单位
|
||||
if apply_unit_conversion and filepath.lower().endswith(
|
||||
('.fbx', '.obj')) and not filepath.lower().endswith('.glb'):
|
||||
print("应用单位转换(厘米到米)...")
|
||||
# 可选的单位转换(主要针对FBX)
|
||||
if apply_unit_conversion and filepath.lower().endswith('.fbx'):
|
||||
print("应用FBX单位转换(厘米到米)...")
|
||||
self._applyUnitConversion(model, 0.01)
|
||||
model.setTag("unit_conversion_applied", "true")
|
||||
|
||||
if normalize_scales and filepath.lower().endswith(('.fbx', '.obj')):
|
||||
print("标准化模型缩放层级...")
|
||||
# 智能缩放标准化(处理FBX子节点的大缩放值)
|
||||
if normalize_scales and filepath.lower().endswith('.fbx'):
|
||||
print("标准化FBX模型缩放层级...")
|
||||
self._normalizeModelScales(model)
|
||||
model.setTag("scale_normalization_applied", "true")
|
||||
|
||||
# 调整模型位置到地面
|
||||
self._adjustModelToGround(model)
|
||||
|
||||
# 创建并设置基础材质
|
||||
print("设置材质...")
|
||||
print("\n=== 开始设置材质 ===")
|
||||
self._applyMaterialsToModel(model)
|
||||
|
||||
# 设置碰撞检测(重要!用于选择功能)
|
||||
print("设置碰撞检测...")
|
||||
print("\n=== 设置碰撞检测 ===")
|
||||
self.setupCollision(model)
|
||||
|
||||
# 添加文件标签用于保存/加载
|
||||
model.setTag("file", model_name)
|
||||
model.setTag("is_model_root", "1")
|
||||
|
||||
# 记录应用的处理选项
|
||||
if apply_unit_conversion:
|
||||
model.setTag("unit_conversion_applied", "true")
|
||||
if normalize_scales:
|
||||
model.setTag("scale_normalization_applied", "true")
|
||||
|
||||
# 添加到模型列表
|
||||
self.models.append(model)
|
||||
|
||||
print(f"✅ 创建模型成功: {model.getName()}")
|
||||
# 更新场景树
|
||||
self.updateSceneTree()
|
||||
|
||||
# 获取树形控件并添加到Qt树中
|
||||
tree_widget = self._get_tree_widget()
|
||||
if tree_widget:
|
||||
# 找到根节点项
|
||||
root_item = None
|
||||
for i in range(tree_widget.topLevelItemCount()):
|
||||
item = tree_widget.topLevelItem(i)
|
||||
if item.text(0) == "render" or item.data(0, Qt.UserRole) == self.world.render:
|
||||
root_item = item
|
||||
break
|
||||
|
||||
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.update_selection_and_properties(model, qt_item)
|
||||
print("✅ Qt树节点添加成功")
|
||||
else:
|
||||
print("⚠️ Qt树节点添加失败,但Panda3D对象已创建")
|
||||
else:
|
||||
print("⚠️ 未找到根节点项,无法添加到Qt树")
|
||||
else:
|
||||
print("⚠️ 无法访问树形控件")
|
||||
|
||||
print(f"🎉 模型导入完成")
|
||||
print(f"=== 模型导入成功: {model_name} ===\n")
|
||||
return model
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 导入模型过程失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
print(f"导入模型失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def _clearModelCache(self, filepath):
|
||||
"""清理模型缓存以解决加载问题"""
|
||||
try:
|
||||
# 清理特定文件的缓存
|
||||
filename = Filename.fromOsSpecific(filepath)
|
||||
if ModelPool.hasModel(filename):
|
||||
ModelPool.releaseModel(filename)
|
||||
print(f"🧹 已清理模型缓存: {filepath}")
|
||||
|
||||
# 清理所有动画缓存
|
||||
from panda3d.core import AnimBundleNode
|
||||
AnimBundleNode.clearAllBundleCache()
|
||||
print("🧹 已清理动画缓存")
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ 清理缓存时出错: {e}")
|
||||
|
||||
def _loadModelWithoutAnimations(self, filepath):
|
||||
"""尝试无动画加载模型"""
|
||||
try:
|
||||
from panda3d.core import LoaderOptions
|
||||
|
||||
print("🔄 尝试无动画加载模型...")
|
||||
|
||||
# 创建不加载动画的加载选项
|
||||
loader_options = LoaderOptions()
|
||||
loader_options.setFlags(LoaderOptions.LF_no_cache) # 不使用缓存
|
||||
loader_options.setAllowInstance(False) # 不允许实例化
|
||||
|
||||
# 尝试加载不带动画的模型
|
||||
model = self.world.loader.loadModel(filepath, loaderOptions=loader_options)
|
||||
|
||||
if model:
|
||||
print("✅ 无动画加载成功")
|
||||
return model
|
||||
else:
|
||||
print("❌ 无动画加载也失败了")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 无动画加载失败: {e}")
|
||||
return None
|
||||
|
||||
# def importAnimatedFBX(self, filepath, scale=0.01, auto_play=True):
|
||||
# """导入带动画的FBX模型(使用assimp)
|
||||
#
|
||||
# Args:
|
||||
# filepath: FBX文件路径
|
||||
# scale: 缩放比例(默认0.01,从厘米转换到米)
|
||||
# auto_play: 是否自动播放第一个动画
|
||||
#
|
||||
# Returns:
|
||||
# Actor对象,如果加载失败返回None
|
||||
# """
|
||||
# try:
|
||||
# print(f"\n=== 导入动画FBX模型: {filepath} ===")
|
||||
# filepath = util.normalize_model_path(filepath)
|
||||
#
|
||||
# # 使用动画管理器加载FBX
|
||||
# actor = self.animation_manager.load_fbx_with_animations(filepath, scale)
|
||||
#
|
||||
# if actor:
|
||||
# # 设置模型名称
|
||||
# model_name = os.path.basename(filepath)
|
||||
# actor.setName(model_name)
|
||||
#
|
||||
# # 调整模型位置到地面
|
||||
# self._adjustModelToGround(actor)
|
||||
#
|
||||
# # 设置碰撞检测
|
||||
# self.setupCollision(actor)
|
||||
#
|
||||
# # 添加文件标签
|
||||
# actor.setTag("file", model_name)
|
||||
# actor.setTag("is_animated_model", "1")
|
||||
#
|
||||
# # 添加到模型列表
|
||||
# self.models.append(actor)
|
||||
#
|
||||
# # 自动播放第一个动画
|
||||
# if auto_play:
|
||||
# available_anims = self.animation_manager.get_available_animations(actor)
|
||||
# if available_anims:
|
||||
# first_anim = available_anims[0]
|
||||
# self.animation_manager.play_animation(actor, first_anim, loop=True)
|
||||
# print(f"🎬 自动播放动画: {first_anim}")
|
||||
#
|
||||
# # 更新场景树
|
||||
# self.updateSceneTree()
|
||||
#
|
||||
# print(f"=== 动画FBX模型导入成功: {model_name} ===\n")
|
||||
# return actor
|
||||
# else:
|
||||
# print("❌ 动画FBX模型导入失败")
|
||||
# return None
|
||||
#
|
||||
# except Exception as e:
|
||||
# print(f"导入动画FBX模型失败: {str(e)}")
|
||||
# import traceback
|
||||
# traceback.print_exc()
|
||||
# return None
|
||||
|
||||
def _applyMaterialsToModel(self, model):
|
||||
"""递归应用材质到模型的所有GeomNode"""
|
||||
|
||||
@ -946,6 +762,9 @@ class SceneManager:
|
||||
model.setTag("color", str(color_attrib.getColor()))
|
||||
|
||||
try:
|
||||
print("--- 打印当前场景图 (render) ---")
|
||||
self.world.render.ls()
|
||||
print("---------------------------------")
|
||||
# 保存场景
|
||||
success = self.world.render.writeBamFile(filename)
|
||||
|
||||
|
||||
@ -17,6 +17,8 @@ from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QMenu, QAction
|
||||
QComboBox, QGroupBox, QInputDialog, QFileDialog, QMessageBox, QDesktopWidget, QDialog,
|
||||
QSpinBox, QFrame)
|
||||
from PyQt5.QtCore import Qt, QDir, QTimer, QSize, QPoint
|
||||
from direct.showbase.ShowBaseGlobal import aspect2d
|
||||
|
||||
from ui.widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget,CustomAssetsTreeWidget, CustomConsoleDockWidget
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
@ -377,6 +379,26 @@ class MainWindow(QMainWindow):
|
||||
self.addModelToCesiumAction = self.cesiumMenu.addAction('添加模型到地球')
|
||||
self.addModelToCesiumAction.triggered.connect(self.onAddModelClicked)
|
||||
|
||||
self.infoPanelMenu = menubar.addMenu('信息面板')
|
||||
# 创建示例面板动作
|
||||
self.createSamplePanelAction = self.infoPanelMenu.addAction('创建示例面板')
|
||||
self.createSamplePanelAction.triggered.connect(self.onCreateSampleInfoPanel)
|
||||
# 添加更多面板创建选项
|
||||
self.createSystemStatusPanelAction = self.infoPanelMenu.addAction('创建系统状态面板')
|
||||
self.createSystemStatusPanelAction.triggered.connect(self.onCreateSystemStatusPanel)
|
||||
|
||||
self.createSensorDataPanelAction = self.infoPanelMenu.addAction('创建传感器数据面板')
|
||||
self.createSensorDataPanelAction.triggered.connect(self.onCreateSensorDataPanel)
|
||||
|
||||
self.createSceneInfoPanelAction = self.infoPanelMenu.addAction('创建场景信息面板')
|
||||
self.createSceneInfoPanelAction.triggered.connect(self.onCreateSceneInfoPanel)
|
||||
|
||||
# 添加分隔符和批量创建选项
|
||||
self.infoPanelMenu.addSeparator()
|
||||
self.createAllPanelsAction = self.infoPanelMenu.addAction('创建所有面板')
|
||||
self.createAllPanelsAction.triggered.connect(self.onCreateAllInfoPanels)
|
||||
|
||||
|
||||
|
||||
#资源菜单
|
||||
self.assetsMenu = menubar.addMenu('资源')
|
||||
@ -584,10 +606,10 @@ class MainWindow(QMainWindow):
|
||||
self.addDockWidget(Qt.BottomDockWidgetArea, self.bottomDock)
|
||||
|
||||
# 创建底部停靠控制台
|
||||
self.consoleDock = QDockWidget("控制台", self)
|
||||
self.consoleView = CustomConsoleDockWidget(self.world)
|
||||
self.consoleDock.setWidget(self.consoleView)
|
||||
self.addDockWidget(Qt.BottomDockWidgetArea, self.consoleDock)
|
||||
# self.consoleDock = QDockWidget("控制台", self)
|
||||
# self.consoleView = CustomConsoleDockWidget(self.world)
|
||||
# self.consoleDock.setWidget(self.consoleView)
|
||||
# self.addDockWidget(Qt.BottomDockWidgetArea, self.consoleDock)
|
||||
|
||||
def setupToolbar(self):
|
||||
"""创建工具栏"""
|
||||
@ -1071,6 +1093,396 @@ class MainWindow(QMainWindow):
|
||||
self.world.setCurrentTool(None)
|
||||
print("工具栏: 取消选择工具")
|
||||
|
||||
# 在 MainWindow 类中添加以下方法
|
||||
|
||||
def onCreateSampleInfoPanel(self):
|
||||
"""创建示例天气信息面板(模拟数据)"""
|
||||
try:
|
||||
# 获取中文字体
|
||||
from panda3d.core import TextNode
|
||||
font = self.world.getChineseFont() if self.world.getChineseFont() else None
|
||||
|
||||
# 创建面板
|
||||
info_manager = self.world.info_panel_manager
|
||||
info_manager.setParent(aspect2d)
|
||||
|
||||
# 使用唯一的面板ID
|
||||
import time
|
||||
unique_id = f"weather_info_{int(time.time())}"
|
||||
|
||||
# 创建示例面板
|
||||
weather_panel = info_manager.createInfoPanel(
|
||||
panel_id=unique_id, # 使用唯一ID
|
||||
position=(0, 0),
|
||||
size=(1, 1),
|
||||
bg_color=(0.15, 0.25, 0.35, 0), # 蓝色背景
|
||||
border_color=(0.3, 0.5, 0.7, 0), # 蓝色边框
|
||||
title_color=(0.7, 0.9, 1.0, 1.0), # 浅蓝色标题
|
||||
content_color=(0.95, 0.95, 0.95, 1.0),
|
||||
font=font
|
||||
)
|
||||
|
||||
# 更新面板标题
|
||||
info_manager.updatePanelContent(unique_id, title="北京天气")
|
||||
|
||||
# 添加到场景树
|
||||
self.addInfoPanelToTree(weather_panel, "天气信息面板")
|
||||
|
||||
# 立即显示加载中信息
|
||||
info_manager.updatePanelContent(unique_id, content="正在获取天气数据...")
|
||||
|
||||
info_manager.registerDataSource(unique_id, self.getRealWeatherData, update_interval=5.0) # 每10分钟更新一次
|
||||
|
||||
# # 立即显示示例数据
|
||||
# sample_data = self.getSampleWeatherData()
|
||||
# info_manager.updatePanelContent(unique_id, content=sample_data)
|
||||
#
|
||||
# # 注册数据源,定期更新示例数据
|
||||
# info_manager.registerDataSource(unique_id, self.getSampleWeatherData, update_interval=2.0)
|
||||
|
||||
print("✓ 示例天气信息面板已创建")
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 创建示例天气信息面板失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
QMessageBox.critical(self, "错误", f"创建示例天气信息面板时出错: {str(e)}")
|
||||
|
||||
def getRealWeatherData(self):
|
||||
"""获取真实天气数据"""
|
||||
try:
|
||||
import requests
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
# 请求天气数据
|
||||
url = "https://wttr.in/Beijing?format=j1"
|
||||
response = requests.get(url, timeout=10)
|
||||
response.raise_for_status()
|
||||
|
||||
# 解析JSON数据
|
||||
weather_data = response.json()
|
||||
|
||||
# 提取当前天气信息
|
||||
current_condition = weather_data['current_condition'][0]
|
||||
weather_desc = current_condition['weatherDesc'][0]['value']
|
||||
temp_c = current_condition['temp_C']
|
||||
feels_like = current_condition['FeelsLikeC']
|
||||
humidity = current_condition['humidity']
|
||||
pressure = current_condition['pressure']
|
||||
visibility = current_condition['visibility']
|
||||
wind_speed = current_condition['windspeedKmph']
|
||||
wind_dir = current_condition['winddir16Point']
|
||||
|
||||
# 提取空气质量(如果可用)
|
||||
air_quality = "N/A"
|
||||
if 'air_quality' in weather_data and weather_data['air_quality']:
|
||||
if 'us-epa-index' in current_condition:
|
||||
air_quality_index = current_condition['air_quality_index']
|
||||
air_quality = f"指数: {air_quality_index}"
|
||||
|
||||
# 获取更新时间
|
||||
update_time = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
# 格式化显示内容
|
||||
content = f"天气状况: {weather_desc}\n温度: {temp_c}°C (体感 {feels_like}°C)\n湿度: {humidity}%\n气压: {pressure} hPa\n能见度: {visibility} km\n风速: {wind_speed} km/h ({wind_dir})\n空气质量: {air_quality}\n更新时间: {update_time}"
|
||||
|
||||
return content
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
return "错误: 获取天气数据超时"
|
||||
except requests.exceptions.ConnectionError:
|
||||
return "错误: 网络连接失败"
|
||||
except requests.exceptions.HTTPError as e:
|
||||
return f"HTTP错误: {e}"
|
||||
except json.JSONDecodeError:
|
||||
return "错误: 无法解析天气数据"
|
||||
except KeyError as e:
|
||||
return f"错误: 天气数据格式不正确 (缺少字段: {e})"
|
||||
except Exception as e:
|
||||
return f"获取天气数据失败: {str(e)}"
|
||||
|
||||
def getSampleWeatherData(self):
|
||||
"""获取示例天气数据"""
|
||||
try:
|
||||
from datetime import datetime
|
||||
import random
|
||||
|
||||
# 模拟天气数据
|
||||
cities = ["北京", "上海", "广州", "深圳", "杭州", "成都", "武汉", "西安"]
|
||||
conditions = ["晴天", "多云", "阴天", "小雨", "雷阵雨", "雪", "雾"]
|
||||
|
||||
city = random.choice(cities)
|
||||
condition = random.choice(conditions)
|
||||
temp = random.randint(-5, 35)
|
||||
humidity = random.randint(30, 90)
|
||||
wind_speed = round(random.uniform(0, 20), 1)
|
||||
pressure = random.randint(980, 1030)
|
||||
|
||||
current_time = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
return f"城市: {city}\n天气状况: {condition}\n温度: {temp}°C\n湿度: {humidity}%\n风速: {wind_speed} m/s\n气压: {pressure} hPa\n更新时间: {current_time}"
|
||||
except Exception as e:
|
||||
return f"获取示例数据失败: {str(e)}"
|
||||
|
||||
def onCreateSystemStatusPanel(self):
|
||||
"""创建系统状态信息面板"""
|
||||
try:
|
||||
# 获取中文字体
|
||||
from panda3d.core import TextNode
|
||||
font = self.world.getChineseFont() if self.world.getChineseFont() else None
|
||||
|
||||
# 创建面板
|
||||
info_manager = self.world.info_panel_manager
|
||||
info_manager.setParent(aspect2d)
|
||||
|
||||
|
||||
panel = info_manager.createInfoPanel(
|
||||
panel_id="system_status",
|
||||
position=(1.4, 0.2),
|
||||
size=(0.8, 1.2),
|
||||
bg_color=(0.25, 0.15, 0.15, 0.95), # 红色背景
|
||||
border_color=(0.7, 0.3, 0.3, 1.0), # 红色边框
|
||||
title_color=(1.0, 0.5, 0.5, 1.0), # 浅红色标题
|
||||
content_color=(0.95, 0.95, 0.95, 1.0),
|
||||
font=font
|
||||
)
|
||||
|
||||
# 添加到场景树
|
||||
self.addInfoPanelToTree(panel, "系统状态信息面板")
|
||||
|
||||
# 立即显示初始数据
|
||||
initial_data = self.getSystemStatusData()
|
||||
info_manager.updatePanelContent("system_status", content=initial_data)
|
||||
|
||||
# 注册数据源,每5秒更新一次
|
||||
info_manager.registerDataSource("system_status", self.getSystemStatusData, update_interval=5.0)
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 创建系统状态信息面板失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
QMessageBox.critical(self, "错误", f"创建系统状态信息面板时出错: {str(e)}")
|
||||
|
||||
def getSystemStatusData(self):
|
||||
"""
|
||||
获取系统状态数据的回调函数
|
||||
"""
|
||||
try:
|
||||
import psutil
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
# 获取系统信息
|
||||
cpu_percent = psutil.cpu_percent(interval=0.1)
|
||||
memory = psutil.virtual_memory()
|
||||
memory_mb = round(memory.used / (1024 * 1024), 1)
|
||||
memory_total_mb = round(memory.total / (1024 * 1024), 1)
|
||||
memory_percent = memory.percent
|
||||
|
||||
# 网络状态
|
||||
net_io = psutil.net_io_counters()
|
||||
bytes_sent = round(net_io.bytes_sent / (1024 * 1024), 2) # MB
|
||||
bytes_recv = round(net_io.bytes_recv / (1024 * 1024), 2) # MB
|
||||
|
||||
# 磁盘使用情况
|
||||
disk = psutil.disk_usage('/')
|
||||
disk_used_gb = round(disk.used / (1024 ** 3), 2)
|
||||
disk_total_gb = round(disk.total / (1024 ** 3), 2)
|
||||
disk_percent = round((disk.used / disk.total) * 100, 1)
|
||||
|
||||
# 时间戳
|
||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||
|
||||
return f"CPU使用率: {cpu_percent}%\n内存使用: {memory_mb}MB / {memory_total_mb}MB ({memory_percent}%)\n磁盘使用: {disk_used_gb}GB / {disk_total_gb}GB ({disk_percent}%)\n网络发送: {bytes_sent}MB\n网络接收: {bytes_recv}MB\n更新时间: {timestamp}"
|
||||
except Exception as e:
|
||||
return f"获取系统状态失败: {str(e)}"
|
||||
|
||||
def onCreateSensorDataPanel(self):
|
||||
"""创建传感器数据信息面板"""
|
||||
try:
|
||||
# 获取中文字体
|
||||
from panda3d.core import TextNode
|
||||
font = self.world.getChineseFont() if self.world.getChineseFont() else None
|
||||
|
||||
# 创建面板
|
||||
info_manager = self.world.info_panel_manager
|
||||
info_manager.setParent(aspect2d)
|
||||
|
||||
panel = info_manager.createInfoPanel(
|
||||
panel_id="sensor_data",
|
||||
position=(0.8, -0.2),
|
||||
size=(0.8, 0.6),
|
||||
bg_color=(0.15, 0.25, 0.15, 0.95), # 绿色背景
|
||||
border_color=(0.3, 0.7, 0.3, 1.0), # 绿色边框
|
||||
title_color=(0.5, 1.0, 0.5, 1.0), # 浅绿色标题
|
||||
content_color=(0.95, 0.95, 0.95, 1.0),
|
||||
font=font
|
||||
)
|
||||
|
||||
# 添加到场景树
|
||||
self.addInfoPanelToTree(panel, "传感器数据信息面板")
|
||||
|
||||
# 立即显示初始数据
|
||||
initial_data = self.getSensorData()
|
||||
info_manager.updatePanelContent("sensor_data", content=initial_data)
|
||||
|
||||
# 注册数据源,每2秒更新一次
|
||||
info_manager.registerDataSource("sensor_data", self.getSensorData, update_interval=2.0)
|
||||
|
||||
# 绑定键盘事件
|
||||
info_manager.accept("F3", info_manager.togglePanel, ["sensor_data"])
|
||||
|
||||
print("✓ 传感器数据信息面板已创建(按 F3 切换显示)")
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 创建传感器数据信息面板失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
QMessageBox.critical(self, "错误", f"创建传感器数据信息面板时出错: {str(e)}")
|
||||
|
||||
def getSensorData(self):
|
||||
"""
|
||||
获取传感器数据的回调函数(模拟数据)
|
||||
"""
|
||||
try:
|
||||
import random
|
||||
from datetime import datetime
|
||||
|
||||
# 模拟传感器数据
|
||||
temperature = round(random.uniform(20, 35), 1)
|
||||
humidity = round(random.uniform(30, 70), 1)
|
||||
pressure = round(random.uniform(990, 1030), 1)
|
||||
light_level = round(random.uniform(0, 1000), 1)
|
||||
|
||||
# 时间戳
|
||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
||||
|
||||
return f"温度: {temperature}°C\n湿度: {humidity}%\n气压: {pressure} hPa\n光照: {light_level} lux\n更新时间: {timestamp}"
|
||||
except Exception as e:
|
||||
return f"获取传感器数据失败: {str(e)}"
|
||||
|
||||
def onCreateSceneInfoPanel(self):
|
||||
"""创建场景信息面板"""
|
||||
try:
|
||||
# 获取中文字体
|
||||
from panda3d.core import TextNode
|
||||
font = self.world.getChineseFont() if self.world.getChineseFont() else None
|
||||
|
||||
# 创建面板
|
||||
info_manager = self.world.info_panel_manager
|
||||
info_manager.setParent(aspect2d)
|
||||
|
||||
panel = info_manager.createInfoPanel(
|
||||
panel_id="scene_info",
|
||||
position=(-0.8, 0.5),
|
||||
size=(0.8, 0.6),
|
||||
bg_color=(0.12, 0.12, 0.12, 0.95), # 深灰色背景
|
||||
border_color=(0.4, 0.4, 0.4, 1.0), # 灰色边框
|
||||
title_color=(0.2, 0.8, 1.0, 1.0), # 蓝色标题
|
||||
content_color=(0.9, 0.9, 0.9, 1.0),
|
||||
font=font
|
||||
)
|
||||
|
||||
# 添加到场景树
|
||||
self.addInfoPanelToTree(panel, "场景信息面板")
|
||||
|
||||
# 立即显示初始数据
|
||||
initial_data = self.getSceneInfoData()
|
||||
info_manager.updatePanelContent("scene_info", content=initial_data)
|
||||
|
||||
# 注册数据源,每3秒更新一次
|
||||
info_manager.registerDataSource("scene_info", self.getSceneInfoData, update_interval=3.0)
|
||||
|
||||
# 绑定键盘事件
|
||||
info_manager.accept("F4", info_manager.togglePanel, ["scene_info"])
|
||||
|
||||
print("✓ 场景信息面板已创建(按 F4 切换显示)")
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 创建场景信息面板失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
QMessageBox.critical(self, "错误", f"创建场景信息面板时出错: {str(e)}")
|
||||
|
||||
def getSceneInfoData(self):
|
||||
"""
|
||||
获取场景信息数据的回调函数
|
||||
"""
|
||||
try:
|
||||
# 获取场景信息
|
||||
node_count = 0
|
||||
texture_count = 0
|
||||
light_count = 0
|
||||
|
||||
# 如果有场景管理器,获取实际数据
|
||||
if hasattr(self.world, 'scene_graph'):
|
||||
# 这里可以根据实际的场景结构来统计节点数
|
||||
node_count = len([node for node in self.world.scene_graph.nodes]) if hasattr(self.world.scene_graph,
|
||||
'nodes') else 0
|
||||
|
||||
# 统计光源数量
|
||||
if hasattr(self.world, 'lights'):
|
||||
light_count = len(self.world.lights)
|
||||
|
||||
# 统计纹理数量
|
||||
if hasattr(self.world, 'textures'):
|
||||
texture_count = len(self.world.textures)
|
||||
|
||||
# 当前时间
|
||||
from datetime import datetime
|
||||
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
return f"场景节点数: {node_count}\n纹理数量: {texture_count}\n光源数量: {light_count}\nFPS: {self.world.clock.getAverageFrameRate():.1f}\n更新时间: {current_time}"
|
||||
except Exception as e:
|
||||
return f"获取场景信息失败: {str(e)}"
|
||||
|
||||
def onCreateAllInfoPanels(self):
|
||||
"""创建所有信息面板"""
|
||||
try:
|
||||
self.onCreateSampleInfoPanel()
|
||||
self.onCreateSystemStatusPanel()
|
||||
self.onCreateSensorDataPanel()
|
||||
self.onCreateSceneInfoPanel()
|
||||
QMessageBox.information(self, "成功",
|
||||
"所有信息面板已创建完成!\n快捷键:\nF1 - 示例面板\nF2 - 系统状态面板\nF3 - 传感器数据面板\nF4 - 场景信息面板")
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"创建信息面板时出错: {str(e)}")
|
||||
|
||||
def addInfoPanelToTree(self, panel, panel_name):
|
||||
"""
|
||||
将信息面板添加到场景树控件中
|
||||
"""
|
||||
if panel and self.treeWidget:
|
||||
# 找到场景根节点
|
||||
scene_root = None
|
||||
for i in range(self.treeWidget.topLevelItemCount()):
|
||||
item = self.treeWidget.topLevelItem(i)
|
||||
if item.text(0) == "render":
|
||||
scene_root = item
|
||||
break
|
||||
|
||||
# 如果找不到场景根节点,使用第一个顶级节点
|
||||
if not scene_root and self.treeWidget.topLevelItemCount() > 0:
|
||||
scene_root = self.treeWidget.topLevelItem(0)
|
||||
|
||||
if scene_root:
|
||||
tree_item = self.treeWidget.add_node_to_tree_widget(
|
||||
node=panel,
|
||||
parent_item=scene_root,
|
||||
node_type="INFO_PANEL"
|
||||
)
|
||||
if tree_item:
|
||||
self.treeWidget.setCurrentItem(tree_item)
|
||||
self.treeWidget.update_selection_and_properties(panel, tree_item)
|
||||
print(f"✓ {panel_name}节点已添加到场景树")
|
||||
return True
|
||||
else:
|
||||
print(f"⚠️ {panel_name}节点添加到场景树失败")
|
||||
else:
|
||||
print("❌ 未找到场景根节点")
|
||||
return False
|
||||
|
||||
# ==================== 脚本管理事件处理 ====================
|
||||
|
||||
def refreshScriptsList(self):
|
||||
|
||||
1369
ui/property_panel.py
1369
ui/property_panel.py
File diff suppressed because it is too large
Load Diff
109
ui/widgets.py
109
ui/widgets.py
@ -1375,6 +1375,8 @@ class CustomTreeWidget(QTreeWidget):
|
||||
|
||||
self.setupDragDrop() # 设置拖拽功能
|
||||
|
||||
self.original_scales={}
|
||||
|
||||
def initData(self):
|
||||
"""初始化变量"""
|
||||
# 定义2D GUI元素类型
|
||||
@ -1493,55 +1495,54 @@ class CustomTreeWidget(QTreeWidget):
|
||||
else:
|
||||
print("用户取消了菜单选择")
|
||||
|
||||
# 在 CustomTreeWidget 类的 dropEvent 方法中替换缩放处理部分
|
||||
def dropEvent(self, event):
|
||||
dragged_item = self.currentItem()
|
||||
target_item = self.itemAt(event.pos())
|
||||
if not dragged_item or not target_item:
|
||||
# 1. 获取所有被拖拽的项
|
||||
dragged_items = self.selectedItems()
|
||||
if not dragged_items:
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
if not self.isValidParentChild(dragged_item, target_item):
|
||||
event.ignore()
|
||||
return
|
||||
# 2. 在执行Qt的默认拖拽前,记录所有拖拽项的原始状态
|
||||
drag_info = []
|
||||
for item in dragged_items:
|
||||
panda_node = item.data(0, Qt.UserRole)
|
||||
if not panda_node or panda_node.is_empty():
|
||||
continue # 跳过无效节点
|
||||
|
||||
dragged_node = dragged_item.data(0, Qt.UserRole)
|
||||
target_node = target_item.data(0, Qt.UserRole)
|
||||
drag_info.append({
|
||||
"item": item,
|
||||
"node": panda_node,
|
||||
"old_parent_node": item.parent().data(0, Qt.UserRole) if item.parent() else None
|
||||
})
|
||||
|
||||
if not dragged_node or not target_node:
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
print(f"dragged_node: {dragged_node}, target_node: {target_node}")
|
||||
|
||||
# 记录拖拽前的父节点
|
||||
old_parent_item = dragged_item.parent()
|
||||
old_parent_node = old_parent_item.data(0, Qt.UserRole) if old_parent_item else None
|
||||
|
||||
# 获取拖拽前的缩放值(2D GUI节点需要特别处理)
|
||||
is_2d_gui = dragged_item.data(0, Qt.UserRole + 1) in self.gui_2d_types
|
||||
if is_2d_gui:
|
||||
# 对于2D GUI,直接记录节点自身的缩放(不考虑父节点缩放)
|
||||
original_scale = dragged_node.getScale()
|
||||
else:
|
||||
# 对于3D节点,记录世界缩放
|
||||
original_scale = dragged_node.getScale(self.world.render)
|
||||
|
||||
# 执行Qt默认拖拽
|
||||
# 3. 执行Qt的默认拖拽,让UI树先行更新
|
||||
# 这一步会自动处理移动或复制,并将项目从旧父节点移除,添加到新父节点
|
||||
super().dropEvent(event)
|
||||
|
||||
# 检查拖拽后的父节点
|
||||
# 4. 遍历记录下的信息,同步每一个Panda3D节点的状态
|
||||
try:
|
||||
for info in drag_info:
|
||||
dragged_item = info["item"]
|
||||
dragged_node = info["node"]
|
||||
old_parent_node = info["old_parent_node"]
|
||||
|
||||
# 获取拖拽后的新父节点
|
||||
new_parent_item = dragged_item.parent()
|
||||
new_parent_node = new_parent_item.data(0, Qt.UserRole) if new_parent_item else None
|
||||
|
||||
# 同步Panda3D场景图的父子关系
|
||||
try:
|
||||
# 检查是否是跨层级拖拽(父节点发生变化)
|
||||
# 仅当父节点实际发生变化时才执行重新父化
|
||||
if old_parent_node != new_parent_node:
|
||||
print(f"跨层级拖拽:从 {old_parent_node} 移动到 {new_parent_node}")
|
||||
|
||||
# 保存世界坐标位置
|
||||
world_pos = dragged_node.getPos(self.world.render)
|
||||
world_hpr = dragged_node.getHpr(self.world.render)
|
||||
# # 保存世界坐标位置
|
||||
# world_pos = dragged_node.getPos(self.world.render)
|
||||
# world_hpr = dragged_node.getHpr(self.world.render)
|
||||
# world_scale = dragged_node.getScale(self.world.render)
|
||||
|
||||
# 检查是否是2D GUI元素
|
||||
dragged_type = dragged_item.data(0, Qt.UserRole + 1)
|
||||
is_2d_gui = dragged_type in self.gui_2d_types
|
||||
|
||||
# 重新父化到新的父节点
|
||||
if new_parent_node:
|
||||
@ -1549,36 +1550,34 @@ class CustomTreeWidget(QTreeWidget):
|
||||
# 2D GUI元素需要特殊处理
|
||||
if hasattr(new_parent_node, 'getTag') and new_parent_node.getTag("is_gui_element") == "1":
|
||||
# 目标是GUI元素,直接重新父化
|
||||
dragged_node.reparentTo(new_parent_node)
|
||||
dragged_node.wrtReparentTo(new_parent_node)
|
||||
else:
|
||||
# 目标是3D节点,保持GUI特性,重新父化到aspect2d
|
||||
from direct.showbase.ShowBase import aspect2d
|
||||
dragged_node.reparentTo(aspect2d)
|
||||
# from direct.showbase.ShowBase import aspect2d
|
||||
dragged_node.wrtReparentTo(self.world.aspect2d)
|
||||
print(f"2D GUI元素 {dragged_item.text(0)} 保持在aspect2d下")
|
||||
else:
|
||||
# 非GUI元素正常重新父化
|
||||
dragged_node.reparentTo(new_parent_node)
|
||||
dragged_node.wrtReparentTo(new_parent_node)
|
||||
else:
|
||||
# 如果新父节点为None,根据元素类型决定父节点
|
||||
if is_2d_gui:
|
||||
from direct.showbase.ShowBase import aspect2d
|
||||
dragged_node.reparentTo(aspect2d)
|
||||
# from direct.showbase.ShowBase import aspect2d
|
||||
dragged_node.wrtReparentTo(self.world.aspect2d)
|
||||
print(f"2D GUI元素 {dragged_item.text(0)} 重新父化到aspect2d")
|
||||
else:
|
||||
dragged_node.reparentTo(self.world.render)
|
||||
dragged_node.wrtReparentTo(self.world.render)
|
||||
|
||||
# 恢复世界坐标位置和方向
|
||||
dragged_node.setPos(self.world.render, world_pos)
|
||||
dragged_node.setHpr(self.world.render, world_hpr)
|
||||
|
||||
# 恢复缩放值
|
||||
if is_2d_gui:
|
||||
# 对于2D GUI,直接使用原始缩放值(不考虑父节点缩放)
|
||||
dragged_node.setScale(original_scale)
|
||||
print(f"✅ 2D GUI {dragged_item.text(0)} 缩放已恢复为原始值: {original_scale}")
|
||||
else:
|
||||
# 对于3D节点,使用世界缩放恢复
|
||||
dragged_node.setScale(self.world.render, original_scale)
|
||||
# # 恢复世界坐标位置(对于2D GUI可能需要调整)
|
||||
# if is_2d_gui:
|
||||
# # 2D GUI元素使用屏幕坐标系,可能需要特殊处理
|
||||
# dragged_node.setPos(world_pos)
|
||||
# dragged_node.setHpr(world_hpr)
|
||||
# dragged_node.setScale(world_scale)
|
||||
# else:
|
||||
# dragged_node.setPos(self.world.render, world_pos)
|
||||
# dragged_node.setHpr(self.world.render, world_hpr)
|
||||
# dragged_node.setScale(self.world.render, world_scale)
|
||||
|
||||
print(f"✅ Panda3D父子关系已更新")
|
||||
else:
|
||||
@ -2176,7 +2175,7 @@ class CustomTreeWidget(QTreeWidget):
|
||||
|
||||
# 检查是否有GUI标签
|
||||
if hasattr(node, 'getTag'):
|
||||
return node.getTag("is_gui_element") == "1"
|
||||
return node.getTag("is_gui_element") == "1" or node.getTag("gui_type") in ["info_panel", "button", "label", "entry", "3d_text", "virtual_screen"]
|
||||
|
||||
# 检查是否是DirectGUI对象
|
||||
try:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user