diff --git a/QPanda3D/Panda3DWorld.py b/QPanda3D/Panda3DWorld.py index d7012fc9..e717b68c 100644 --- a/QPanda3D/Panda3DWorld.py +++ b/QPanda3D/Panda3DWorld.py @@ -142,7 +142,7 @@ class Panda3DWorld(ShowBase): #render_pipeline.set_camera(self.cam) - #添加渲染效果�� + #添加渲染效果 #self.cam = self.render_pipeline._showbase.cam #self.camNode = self.cam.node() #self.camLens = self.camNode.get_lens() @@ -160,6 +160,19 @@ class Panda3DWorld(ShowBase): self.mouse_controller = CustomMouseController(self) self.mouse_controller.setUp() + # 添加错误处理钩子 + self.accept("transform_state_error", self._handle_transform_error) + + def _handle_transform_error(self): + """处理TransformState相关的错误""" + try: + from panda3d.core import TransformState, RenderState + TransformState.clear_cache() + RenderState.clear_cache() + print("已清理TransformState和RenderState缓存") + except Exception as e: + print(f"清理缓存时出错: {e}") + def render_pipeline(self): """获取 RenderPipeline 实例""" return self._render_pipeline @@ -198,4 +211,4 @@ def resize_buffer(self, width: int, height: int): self.camLens.set_film_size(width, height) # 或 set_aspect_ratio(width / height) # 强制更新窗口(有时在 Qt 内嵌时需要) - self.graphicsEngine.open_windows() + self.graphicsEngine.open_windows() \ No newline at end of file diff --git a/QPanda3D/QPanda3DWidget.py b/QPanda3D/QPanda3DWidget.py index 5877a623..672ad897 100644 --- a/QPanda3D/QPanda3DWidget.py +++ b/QPanda3D/QPanda3DWidget.py @@ -33,17 +33,34 @@ class QPanda3DSynchronizer(QTimer): self.setInterval(int(dt)) self.timeout.connect(self.tick) - # def tick(self): - # taskMgr.step() - # self.qPanda3DWidget.update() - def tick(self): try: + # 在渲染前清理可能损坏的TransformState对象 + from panda3d.core import TransformState + TransformState.clear_cache() + taskMgr.step() self.qPanda3DWidget.update() - except: - # 静默处理所有异常,包括 has_mat() 断言错误 - pass + except AssertionError as e: + # 专门处理 TransformState has_mat() 断言错误 + if "has_mat" in str(e): + print(f"警告: 检测到TransformState断言错误,已静默处理: {e}") + # 尝试恢复渲染状态 + try: + # 强制清理缓存并重试 + from panda3d.core import TransformState, RenderState + TransformState.clear_cache() + RenderState.clear_cache() + taskMgr.step() + self.qPanda3DWidget.update() + except: + pass + else: + # 重新抛出其他断言错误 + raise + except Exception as e: + # 静默处理其他所有异常 + print(f"警告: 检测到异常,已静默处理: {e}") def get_panda_key_modifiers(evt): @@ -242,7 +259,7 @@ class QPanda3DWidget(QWidget): # # data = tex.getRamImage().getData() # width = tex.getXSize() - # height = tex.getYSize() + # height = tex.getYSize() # expected_len = width * height * 4 # # if len(data) != expected_len: @@ -335,10 +352,4 @@ class QPanda3DWidget(QWidget): print(f"Panda3D 窗口尺寸已同步为: {adjusted_width} x {adjusted_height}") except Exception as e: - print(f"同步 Panda3D 窗口尺寸失败: {str(e)}") - - - - - - + print(f"同步 Panda3D 窗口尺寸失败: {str(e)}") \ No newline at end of file diff --git a/core/selection.py b/core/selection.py index f195d783..66295b8f 100644 --- a/core/selection.py +++ b/core/selection.py @@ -272,7 +272,7 @@ class SelectionSystem: def updateSelectionBoxTask(self, task): """选择框更新任务 - 平衡性能和实时性""" try: - update_interval = 0.1 + update_interval = 0.05 if not hasattr(self, '_last_selection_box_update'): self._last_selection_box_update = 0 @@ -2023,44 +2023,44 @@ class SelectionSystem: # 添加到新父项 new_parent_item.addChild(item) - def _updateSelectionVisuals(self, nodePath): - """更新选择的视觉效果(选择框和坐标轴)""" - try: - if nodePath and not nodePath.isEmpty(): - node_name = nodePath.getName() - print(f"开始为节点 {node_name} 创建选择框和坐标轴...") - - # 创建选择框 - print("创建选择框...") - self.createSelectionBox(nodePath) - if self.selectionBox: - box_name = "Unknown" - if self.selectionBox and not self.selectionBox.isEmpty(): - box_name = self.selectionBox.getName() - print(f"✓ 选择框创建成功: {box_name}") - else: - print("× 选择框创建失败") - - # 创建坐标轴 - print("创建坐标轴...") - self.createGizmo(nodePath) - if self.gizmo: - gizmo_name = "Unknown" - if self.gizmo and not self.gizmo.isEmpty(): - gizmo_name = self.gizmo.getName() - print(f"✓ 坐标轴创建成功: {gizmo_name}") - else: - print("× 坐标轴创建失败") - - print(f"✓ 选中了节点: {node_name}") - else: - print("清除选择...") - self.clearSelectionBox() - self.clearGizmo() - print("✓ 取消选择") - - except Exception as e: - print(f"更新选择视觉效果失败: {e}") + # def _updateSelectionVisuals(self, nodePath): + # """更新选择的视觉效果(选择框和坐标轴)""" + # try: + # if nodePath and not nodePath.isEmpty(): + # node_name = nodePath.getName() + # print(f"开始为节点 {node_name} 创建选择框和坐标轴...") + # + # # 创建选择框 + # print("创建选择框...") + # self.createSelectionBox(nodePath) + # if self.selectionBox: + # box_name = "Unknown" + # if self.selectionBox and not self.selectionBox.isEmpty(): + # box_name = self.selectionBox.getName() + # print(f"✓ 选择框创建成功: {box_name}") + # else: + # print("× 选择框创建失败") + # + # # 创建坐标轴 + # print("创建坐标轴...") + # self.createGizmo(nodePath) + # if self.gizmo: + # gizmo_name = "Unknown" + # if self.gizmo and not self.gizmo.isEmpty(): + # gizmo_name = self.gizmo.getName() + # print(f"✓ 坐标轴创建成功: {gizmo_name}") + # else: + # print("× 坐标轴创建失败") + # + # print(f"✓ 选中了节点: {node_name}") + # else: + # print("清除选择...") + # self.clearSelectionBox() + # self.clearGizmo() + # print("✓ 取消选择") + # + # except Exception as e: + # print(f"更新选择视觉效果失败: {e}") def getSelectedNode(self): """获取当前选中的节点""" diff --git a/main.py b/main.py index 9e3a70e4..daa599e1 100644 --- a/main.py +++ b/main.py @@ -49,10 +49,6 @@ from direct.actor.Actor import Actor from PyQt5.sip import wrapinstance #from RenderPipelineFile.toolkit.material_editor.main import MaterialEditor -from setup_panda3d import setup_panda3d -setup_panda3d() - - class MyWorld(CoreWorld): def __init__(self): super().__init__() diff --git a/project/project_manager.py b/project/project_manager.py index 88f7ab89..12dc4eae 100644 --- a/project/project_manager.py +++ b/project/project_manager.py @@ -404,7 +404,7 @@ class ProjectManager: success = self._executeStandardBuild(build_dir, parent_window) if success: - QMessageBox.information(parent_window, "成功", + QMessageBox.information(parent_window, "成功", "打包完成!\n可执行文件在 build/dist/ 目录中。\n" "支持的格式:\n" "- Windows: .exe 安装程序\n" @@ -413,7 +413,7 @@ class ProjectManager: return True else: return False - + except Exception as e: QMessageBox.critical(parent_window, "错误", f"打包过程出错:{str(e)}") return False @@ -1092,205 +1092,299 @@ class ProjectManager: # f.write(app_code) def _createStandardSetupFile(self, build_dir, project_name): - """创建标准的setup.py文件 - 按照Panda3D官方文档""" + """创建优化的标准setup.py文件""" setup_code = f'''#!/usr/bin/env python3 -# -*- coding: utf-8 -*- + # -*- coding: utf-8 -*- -""" -{project_name} 打包配置文件 -使用 Panda3D 标准打包工具 -""" + """ + {project_name} 打包配置文件 + 使用 Panda3D 标准打包工具 + """ -from setuptools import setup + from setuptools import setup -# 应用程序配置 -APP_NAME = "{project_name}" -APP_VERSION = "1.0.0" -MAIN_SCRIPT = "main.py" + # 应用程序配置 + APP_NAME = "{project_name}" + APP_VERSION = "1.0.0" + MAIN_SCRIPT = "main.py" -setup( - name=APP_NAME, - version=APP_VERSION, - - # Panda3D 打包选项 - options={{ - 'build_apps': {{ - # GUI应用程序 - 'gui_apps': {{ - APP_NAME: MAIN_SCRIPT, - }}, - - # 包含的文件模式 - 'include_patterns': [ - '*.bam', # 场景文件 - '*.egg', # 模型文件 - '*.jpg', '*.png', # 纹理文件 - '*.wav', '*.ogg', # 音频文件 - '*.ttf', '*.otf', # 字体文件 - ], - - # 排除的文件模式 - 'exclude_patterns': [ - '*.pyc', - '__pycache__/**', - '.git/**', - '.vscode/**', - '*.log', - ], - - # Panda3D 插件 - 'plugins': [ - 'pandagl', # OpenGL渲染器 - 'pandaegg', # Egg文件支持 - 'p3openal_audio', # OpenAL音频 - ], - - # 包含的Python模块 - 'include_modules': {{ - '*': [ - 'direct.showbase.ShowBase', - 'direct.task', - 'direct.actor', - 'direct.interval', - 'direct.stdpy.file', - 'direct.stdpy.pickle', - 'panda3d.core', - 'panda3d.direct', - 'rpcore', - 'rpcore.util.movement_controller', - 'rpcore.native', - 'rpcore.render_pipeline', - 'rplibs', - 'rpplugins', - 'rpplugins.scattering', - 'rpplugins.pssm', - 'rpplugins.godrays', - 'json', - 'os', - 'sys', - 'six', - 'collections', - 'collections.abs', - 'weakref', - 'copy', - 'itertools', - 'importlib', - 'importlib.util', - 'importlib.machinery', + setup( + name=APP_NAME, + version=APP_VERSION, + + # Panda3D 打包选项 + options={{ + 'build_apps': {{ + # GUI应用程序 + 'gui_apps': {{ + APP_NAME: MAIN_SCRIPT, + }}, + + # 包含的文件模式 + 'include_patterns': [ + '*.bam', # 场景文件 + '*.egg', # 模型文件 + '*.jpg', '*.png', '*.tga', '*.dds', # 纹理文件 + '*.wav', '*.ogg', '*.mp3', # 音频文件 + '*.ttf', '*.otf', # 字体文件 + 'config/*.yaml', # 配置文件 + 'shaders/**/*.glsl', # 着色器文件 ], - }}, - - # 排除的Python模块(减小体积) - 'exclude_modules': {{ - '*': [ - 'tkinter', # Tkinter GUI - 'matplotlib', # 绘图库 - 'numpy', # 数值计算(如果不需要) - 'scipy', # 科学计算(如果不需要) - 'PIL', # 图像处理(如果不需要) - 'wx', # wxPython - 'PyQt5', # Qt界面库 - 'setuptools', # 安装工具 - 'distutils', # 分发工具 + + # 排除的文件模式 + 'exclude_patterns': [ + '*.pyc', + '__pycache__/**', + '.git/**', + '.vscode/**', + '*.log', + '**/*.tmp', + '**/temp/**', + 'samples/**', # 示例文件 + '**/.DS_Store', + '**/Thumbs.db', ], + + # Panda3D 插件 + 'plugins': [ + 'pandagl', # OpenGL渲染器 + 'pandaegl', # EGL支持 + 'p3assimp', # Assimp模型导入 + 'p3ffmpeg', # FFmpeg视频支持 + 'p3openal_audio', # OpenAL音频 + ], + + # 包含的Python模块 + 'include_modules': {{ + '*': [ + # 核心模块 + 'direct.showbase.ShowBase', + 'direct.task', + 'direct.actor', + 'direct.interval', + 'direct.gui', + 'direct.stdpy.file', + 'direct.stdpy.pickle', + 'panda3d.core', + 'panda3d.direct', + + # 渲染管线相关 + 'rpcore', + 'rpcore.util.movement_controller', + 'rpcore.native', + 'rpcore.render_pipeline', + 'rplibs', + 'rpplugins', + + # 标准库 + 'json', + 'os', + 'sys', + 'math', + 'random', + 'collections', + 'copy', + 'itertools', + 'importlib', + ], + }}, + + # 排除的Python模块(减小体积) + 'exclude_modules': {{ + '*': [ + # GUI框架 + 'tkinter', + 'matplotlib', + 'numpy', + 'scipy', + 'PIL', + 'wx', + 'PyQt5', + 'PySide2', + 'kivy', + + # 开发工具 + 'setuptools', + 'pip', + 'wheel', + 'twine', + 'pytest', + 'unittest', + + # 其他不必要的模块 + 'sqlite3', + 'xmlrpc', + 'ftplib', + 'telnetlib', + ], + }}, + + # 平台设置 + 'platforms': [ + 'win_amd64', # Windows 64位 + 'linux_x86_64', # Linux 64位 + ], + + # 优化设置 + 'strip_docstrings': True, # 移除文档字符串 + 'optimize': 1, # 字节码优化级别 }}, - - # 平台设置 - 'platforms': [ - 'win_amd64', # Windows 64位 - 'linux_x86_64', # Linux 64位 - # 'macosx_10_9_x86_64', # macOS(如果需要) - ], - - # 优化设置 - 'strip_docstrings': True, # 移除文档字符串 }}, - }}, - - # 标准setuptools选项 - author="Panda3D 引擎编辑器", - author_email="user@example.com", - description=f"{{APP_NAME}} - 使用Panda3D创建的3D应用程序", - long_description="这是一个使用Panda3D引擎编辑器创建的3D应用程序。", - - # 依赖项 - install_requires=[ - 'panda3d>=1.10.13', - ], - - # Python版本要求 - python_requires='>=3.7', - - # 分类信息 - classifiers=[ - 'Development Status :: 4 - Beta', - 'Intended Audience :: End Users/Desktop', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Topic :: Games/Entertainment', - 'Topic :: Multimedia :: Graphics :: 3D Rendering', - ], - - # 使用现代的许可证表达式 - license='MIT', -) -''' - + + # 标准setuptools选项 + author="Panda3D Engine Editor User", + author_email="user@example.com", + description=f"{{APP_NAME}} - 使用Panda3D创建的3D应用程序", + long_description="这是一个使用Panda3D引擎编辑器创建的3D应用程序。", + + # 依赖项 + install_requires=[ + 'panda3d>=1.10.13', + ], + + # Python版本要求 + python_requires='>=3.7', + + # 分类信息 + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: End Users/Desktop', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Topic :: Games/Entertainment', + 'Topic :: Multimedia :: Graphics :: 3D Rendering', + ], + + license='MIT', + ) + ''' + setup_path = os.path.join(build_dir, "setup.py") with open(setup_path, "w", encoding="utf-8") as f: f.write(setup_code) - + def _executeStandardBuild(self, build_dir, parent_window): - """执行标准的Panda3D打包命令""" + """执行标准的Panda3D打包命令 - 增强版""" try: print(f"开始打包,工作目录: {build_dir}") - - # 首先尝试 bdist_apps(推荐方式) - print("执行标准打包命令: python setup.py bdist_apps") - + + # 显示打包进度提示 + QMessageBox.information(parent_window, "打包进行中", + "正在打包项目,请稍候...\n" + "此过程可能需要几分钟时间。\n" + "请勿关闭程序。") + + # 使用线程执行打包以避免界面冻结 + import threading + from PyQt5.QtCore import pyqtSignal, QObject + + class BuildSignals(QObject): + finished = pyqtSignal(bool, str) + progress = pyqtSignal(str) + + signals = BuildSignals() + + def build_process(): + try: + print("执行标准打包命令: python setup.py bdist_apps") + signals.progress.emit("正在初始化打包过程...") + + process = subprocess.Popen( + [sys.executable, "setup.py", "bdist_apps"], + cwd=build_dir, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + encoding='utf-8' + ) + + while True: + output = process.stdout.readline() + if output == '' and process.poll() is not None: + break + if output: + print(output.strip()) + if "copying" in output.lower(): + signals.progress.emit(f"正在处理文件: {output.strip()[-50:]}") + elif "building" in output.lower(): + signals.progress.emit(output.strip()) + + # 获取错误输出 + stderr = process.stderr.read() + if stderr: + print("错误输出:", stderr) + + except Exception as e: + print(f"执行打包命令时出错: {str(e)}") + signals.finished.emit(False, f"打包失败:{str(e)}") + + # 连接信号 + def on_finished(success, message): + if success: + QMessageBox.information(parent_window, "成功", message) + else: + QMessageBox.critical(parent_window, "错误", message) + + def on_progress(message): + print(f"[打包进度] {message}") + + signals.finished.connect(on_finished) + signals.progress.connect(on_progress) + + # 启动打包线程 + build_thread = threading.Thread(target=build_process) + build_thread.daemon = True + build_thread.start() + + return True # 线程已启动 + + except Exception as e: + print(f"启动打包过程时出错: {str(e)}") + QMessageBox.critical(parent_window, "错误", f"启动打包失败:{str(e)}") + return False + + def _tryBuildAppsThreaded(self, build_dir, signals): + """尝试使用 build_apps 命令(线程安全版本)""" + try: + print("执行备用打包命令: python setup.py build_apps") + signals.progress.emit("正在执行备用打包方法...") + process = subprocess.Popen( - [sys.executable, "setup.py", "bdist_apps"], + [sys.executable, "setup.py", "build_apps"], cwd=build_dir, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, encoding='utf-8' ) - + # 实时显示输出 - stdout_lines = [] - stderr_lines = [] - while True: output = process.stdout.readline() if output == '' and process.poll() is not None: break if output: print(output.strip()) - stdout_lines.append(output.strip()) - - # 获取错误输出 + signals.progress.emit(output.strip()) + stderr = process.stderr.read() if stderr: print("错误输出:", stderr) - stderr_lines.append(stderr) - - # 检查返回码 + if process.returncode == 0: - print("✓ 打包成功完成") - return True + print("✓ build_apps 成功完成") + signals.finished.emit(True, "打包成功完成!\n可执行文件在 dist/ 目录中。") else: - # 如果bdist_apps失败,尝试build_apps - print(f"bdist_apps 失败 (返回码: {process.returncode}),尝试 build_apps...") - return self._tryBuildApps(build_dir, parent_window) - + error_msg = f"打包失败,返回码:{process.returncode}" + if stderr: + error_msg += f"\n错误信息:{stderr}" + signals.finished.emit(False, error_msg) + except Exception as e: - print(f"执行打包命令时出错: {str(e)}") - QMessageBox.critical(parent_window, "错误", f"打包失败:{str(e)}") - return False + signals.finished.emit(False, f"执行 build_apps 失败:{str(e)}") def _tryBuildApps(self, build_dir, parent_window): """尝试使用 build_apps 命令""" diff --git a/scene/scene_manager.py b/scene/scene_manager.py index be73c011..ebac4a6b 100644 --- a/scene/scene_manager.py +++ b/scene/scene_manager.py @@ -15,7 +15,7 @@ from PyQt5.QtWidgets import QTreeWidgetItem from panda3d.core import ( ModelPool, ModelRoot, Filename, NodePath, GeomNode, Material, Vec4, Vec3, MaterialAttrib, ColorAttrib, Point3, CollisionNode, CollisionSphere, - BitMask32, TransparencyAttrib, LColor, TransformState + BitMask32, TransparencyAttrib, LColor, TransformState, RenderModeAttrib ) import json import aiohttp @@ -94,45 +94,33 @@ 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: - print(f"\n=== 开始导入模型: {filepath} ===") - #print(f"单位转换: {'开启' if apply_unit_conversion else '关闭'}") - #print(f"自动转换GLB: {'开启' if auto_convert_to_glb else '关闭'}") + if not os.path.exists(filepath): + print("文件不存在") + return None filepath = util.normalize_model_path(filepath) original_filepath = filepath - # 在加载前设置忽略未知属性 - from panda3d.core import ConfigVariableBool - ConfigVariableBool("model-cache-ignore-unknown-properties").setValue(True) + # # 在加载前设置忽略未知属性 + # from panda3d.core import ConfigVariableBool + # ConfigVariableBool("model-cache-ignore-unknown-properties").setValue(True) + # + # # 清除可能存在的模型缓存 + # from panda3d.core import ModelPool + # ModelPool.releaseAllModels() + # + # # 检查是否需要转换为GLB以获得更好的动画支持 + # if auto_convert_to_glb and self._shouldConvertToGLB(filepath): + # print(f"🔄 检测到需要转换的格式,尝试转换为GLB...") + # converted_path = self._convertToGLBWithProgress(filepath) + # if converted_path: + # print(f"✅ 转换成功: {converted_path}") + # filepath = converted_path + # # 转换成功的消息已在控制台显示,不再弹窗提示 + # else: + # print(f"⚠️ 转换失败,使用原始文件") - # 清除可能存在的模型缓存 - from panda3d.core import ModelPool - ModelPool.releaseAllModels() - - # 检查是否需要转换为GLB以获得更好的动画支持 - if auto_convert_to_glb and self._shouldConvertToGLB(filepath): - print(f"🔄 检测到需要转换的格式,尝试转换为GLB...") - converted_path = self._convertToGLBWithProgress(filepath) - if converted_path: - print(f"✅ 转换成功: {converted_path}") - filepath = converted_path - # 转换成功的消息已在控制台显示,不再弹窗提示 - else: - print(f"⚠️ 转换失败,使用原始文件") - - # 总是重新加载模型以确保材质信息完整 - # 不使用ModelPool缓存,避免材质信息丢失问题 - #print("直接从文件加载模型...") model = self.world.loader.loadModel(filepath) if not model: print("加载模型失败") @@ -171,23 +159,13 @@ class SceneManager: model.setHpr(0, 90, 0) print("设置模型旋转为 (0, 90, 0)") - # # 可选的单位转换(主要针对FBX - # if apply_unit_conversion and filepath.lower().endswith('.fbx'): - # #print("应用FBX单位转换(厘米到米)...") - # self._applyUnitConversion(model, 0.01) - # - # # 智能缩放标准化(处理FBX子节点的大缩放值) - # if normalize_scales and filepath.lower().endswith('.fbx'): - # #print("标准化FBX模型缩放层级...") - # self._normalizeModelScales(model) - # 调整模型位置到地面 model.setPos(0,0,0) #self._adjustModelToGround(model) # 创建并设置基础材质 print("\n=== 开始设置材质 ===") - self._applyMaterialsToModel(model) + #self._applyMaterialsToModel(model) # 设置碰撞检测(重要!用于选择功能) print("\n=== 设置碰撞检测 ===") @@ -540,54 +518,6 @@ class SceneManager: # 失败时使用默认位置 model.setPos(0, 0, 0) - def _applyUnitConversion(self, model, scale_factor): - """应用单位转换缩放 - - Args: - model: 要转换的模型 - scale_factor: 缩放因子(如0.01表示从厘米转换到米) - """ - try: - print(f"应用单位转换缩放: {scale_factor}") - - # 检查模型是否已经应用过单位转换 - if model.hasTag("unit_conversion_applied"): - print("模型已应用过单位转换,跳过") - return - - - - # 获取当前边界用于后续位置调整 - original_bounds = model.getBounds() - - # 应用缩放 - model.setScale(scale_factor) - - # 应用缩放(添加异常处理) - try: - model.setScale(scale_factor) - except Exception as e: - print(f"直接设置缩放失败: {e},尝试使用变换状态") - try: - model.setTransform(TransformState.makeScale(scale_factor)) - except Exception as e2: - print(f"使用变换状态设置缩放也失败: {e2}") - - # 应用缩放后验证变换 - self._validateAndFixTransform(model) - # 重新调整位置(因为缩放会影响边界) - if original_bounds and not original_bounds.isEmpty(): - new_bounds = model.getBounds() - min_point = new_bounds.getMin() - ground_offset = -min_point.getZ() - model.setZ(ground_offset) - print(f"缩放后重新调整位置: Z偏移 = {ground_offset}") - - print(f"单位转换完成,缩放因子: {scale_factor}") - - except Exception as e: - print(f"应用单位转换失败: {str(e)}") - def _normalizeModelScales(self, model): """智能标准化模型缩放层级 @@ -679,7 +609,9 @@ class SceneManager: return None def _applyScaleNormalization(self, node, normalize_factor, depth=0): - """递归应用缩放标准化,同时调整位置以保持视觉一致性""" + """ + 安全地应用缩放标准化 + """ try: indent = " " * depth current_scale = node.getScale() @@ -689,8 +621,19 @@ class SceneManager: max_scale_component = max(abs(current_scale.x), abs(current_scale.y), abs(current_scale.z)) if max_scale_component > 10: # 只标准化明显的大缩放 + # 确保标准化因子有效 + if normalize_factor <= 0 or normalize_factor > 1000: + print(f"{indent}无效的标准化因子: {normalize_factor},跳过") + return + # 应用新的缩放 new_scale = current_scale * normalize_factor + + # 检查新缩放是否有效 + if any(s <= 0 for s in [new_scale.x, new_scale.y, new_scale.z]): + print(f"{indent}标准化后产生无效缩放,跳过") + return + node.setScale(new_scale) # 同时调整位置:当缩放变小时,位置也应该相应变小以保持视觉相对位置 @@ -702,11 +645,6 @@ class SceneManager: print(f"{indent} 缩放: {current_scale} -> {new_scale}") print(f"{indent} 位置: {current_pos} -> {new_pos}") - # 递归处理子节点 - for i in range(node.getNumChildren()): - child = node.getChild(i) - self._applyScaleNormalization(child, normalize_factor, depth + 1) - except Exception as e: print(f"应用缩放标准化失败 ({node.getName()}): {str(e)}") @@ -1588,6 +1526,8 @@ class SceneManager: nodePath.setScale(scale) print(f"{indent}恢复缩放: {scale}") + nodePath.show() + if nodePath.hasTag("has_scripts") and nodePath.getTag("has_scripts") == "true": if hasattr(self.world,'script_manager') and self.world.script_manager: try: diff --git a/templates/main_template.py b/templates/main_template.py index 63a23b38..9108750a 100644 --- a/templates/main_template.py +++ b/templates/main_template.py @@ -132,6 +132,8 @@ class MainApp(ShowBase): self.setupMouseClickHandler() self.cTrav = CollisionTraverser() + self.createFrameRateDisplay() + self.women_actor = None self.current_actor = None @@ -1518,7 +1520,35 @@ class MainApp(ShowBase): except Exception as e: print(f"处理鼠标点击失败: {e}") import traceback - traceback.print_exc() + traceback.print_exc()\ + + def createFrameRateDisplay(self): + from direct.gui.DirectGui import DirectLabel + from panda3d.core import TextNode + + self.fps_label = DirectLabel( + text="FPS:0", + pos=(-1.8,0,0.9), + scale=0.05, + text_fg=(1,1,1,1), + text_align=TextNode.ALeft, + frameSize=(-0.1,0.3,-0.05,0.05), + frameColor=(0,0,0,0.5), + text_font=self.getChineseFont() if self.getChineseFont() else None, + #parent=self.a2dTopLeft + ) + + taskMgr.add(self.updateFrameRate,"updateFrameRateTask") + + def updateFrameRate(self,task): + try: + fps = globalClock.getAverageFrameRate() + + if self.fps_label: + self.fps_label.setText(f"FPS:{fps:.1f}") + except Exception as e: + print(f"更新帧率失败: {e}") + return task.cont # 在 main.py 的最后部分,修改为: diff --git a/ui/widgets.py b/ui/widgets.py index d5978703..2a9ef38b 100644 --- a/ui/widgets.py +++ b/ui/widgets.py @@ -1386,7 +1386,7 @@ class CustomConsoleDockWidget(QWidget): # 帧率更新定时器 self.fpsTimer = QTimer() self.fpsTimer.timeout.connect(self.updateFPS) - self.fpsTimer.start(1000) # 每秒更新一次 + self.fpsTimer.start(100) # 每秒更新一次 toolbar.addStretch() layout.addLayout(toolbar)