This commit is contained in:
Rowland 2026-03-26 14:04:33 +08:00
parent bab5a9bb27
commit 26faa11ae0
35 changed files with 197 additions and 5625 deletions

View File

@ -1,4 +1,4 @@
{
"python-envs.pythonProjects": [],
"kiroAgent.configureMCP": "Disabled"
"kiroAgent.configureMCP": "Disabled",
"python-envs.defaultEnvManager": "ms-python.python:pyenv"
}

218
AGENTS.md
View File

@ -1,218 +0,0 @@
# EG 项目概览与开发指南
## 项目简介
EG 是一个基于 Panda3D 引擎开发的 3D 编辑器和游戏引擎集成了高级渲染管线、VR 支持、物理模拟、脚本系统等功能。该项目主要用于创建 3D 场景、游戏开发和交互式应用程序。
## 核心技术栈
- **渲染引擎**: Panda3D 1.10.15 + RenderPipeline (延迟渲染、PBR材质)
- **GUI框架**: imgui_bundle + p3dimgui (用于编辑器界面)
- **VR支持**: OpenVR 2.2.0
- **脚本系统**: Python 3.11
- **其他依赖**: openvr, numpy, aiohttp, pyassimp, pillow 等
## 项目架构
### 核心模块
- **main.py** - 应用程序入口点,初始化所有系统
- **Start_Run.py** - 启动脚本,设置环境变量和路径
- **core/** - 核心功能模块
- `world.py` - 世界管理器,处理场景渲染和更新
- `selection.py` - 选择系统,处理对象选择
- `event_handler.py` - 事件处理系统
- `tool_manager.py` - 工具管理器
- `script_system.py` - 脚本系统
- `patrol_system.py` - 巡逻系统
- `Command_System.py` - 命令系统
- `terrain_manager.py` - 地形管理
- `vr_manager.py` - VR 功能管理
- `collision_manager.py` - 碰撞检测管理
- `resource_manager.py` - 资源管理
- `assembly_interaction.py` - 装配交互
- `maintenance_gui.py` - 维护界面
- `render_pipeline_utils.py` - 渲染管线工具
- `InfoPanelManager.py` - 信息面板管理
- `CustomMouseController.py` - 自定义鼠标控制器
### GUI 系统
- **gui/gui_manager.py** - GUI管理器处理2D/3D界面元素
- **ui/icon_manager.py** - 图标管理器
### 场景管理
- **scene/scene_manager.py** - 场景管理器,处理模型导入、场景树构建
- **scene/util.py** - 场景工具函数
### 项目管理
- **project/project_manager.py** - 项目管理器,处理项目创建、保存、加载
### 脚本系统
- **scripts/** - 包含各种预定义脚本
- `MoverScript.py` - 移动脚本
- `RotatorScript.py` - 旋转脚本
- `ScalerScript.py` - 缩放脚本
- `ColorChangerScript.py` - 颜色变化脚本
- `FollowerScript.py` - 跟随脚本
- `BouncerScript.py` - 弹跳脚本
- `ComboAnimatorScript.py` - 组合动画脚本
### VR 系统
- **vr_actions/** - VR动作配置
- `actions.json` - VR动作定义
- `bindings_*.json` - 不同VR设备的绑定配置
### 资源管理
- **Resources/** - 资源目录
- `models/` - 3D模型
- `textures/` - 纹理贴图
- `materials/` - 材质文件
- `animations/` - 动画文件
- `icons/` - 图标资源
### 渲染管线
- **RenderPipelineFile/** - 高级渲染管线
- `rpcore/` - 渲染管线核心
- `rpplugins/` - 渲染插件
- `effects/` - 后处理效果
- `config/` - 渲染配置
## 启动和运行
### 环境要求
- Python 3.11
- Panda3D 1.10.15
- OpenVR 2.2.0 (VR功能)
### 运行方式
1. **直接运行主程序**:
```bash
python Start_Run.py
```
2. **带项目路径运行**:
```bash
python Start_Run.py /path/to/project
```
3. **从main.py运行**:
```bash
python main.py
```
### 配置文件
- **config/vr_settings.json** - VR渲染配置
- **imgui.ini** - ImGui界面配置
- **.gitignore** - Git忽略文件配置
## 开发约定
### 代码风格
- 使用UTF-8编码
- 遵循PEP 8代码规范
- 类名使用驼峰命名法
- 函数和变量使用下划线命名法
- 文件头部包含模块说明和导入信息
### 脚本开发
- 所有用户脚本应继承 `ScriptBase`
- 脚本文件放在 `scripts/` 目录下
- 使用 `ScriptManager` 管理脚本生命周期
- 脚本API通过 `world` 对象提供
### 插件开发
- 插件系统支持动态加载
- 插件配置文件使用JSON格式
- 插件应实现标准接口
### VR开发
- VR动作配置在 `vr_actions/actions.json` 中定义
- 支持多种VR设备Vive、Oculus、Index
- VR渲染配置在 `config/vr_settings.json`
## 构建和部署
### 依赖安装
```bash
pip install -r requirements/requirements.txt
```
### 测试
项目包含多个测试脚本:
- `test_quick_script.py` - 快速测试脚本
- `TestMover.py` - 移动测试
- `TestRotator.py` - 旋转测试
- `TestScaler.py` - 缩放测试
### 项目文件
- 项目配置使用JSON格式
- 项目文件包含场景、资源、脚本等信息
- 使用 `ProjectManager` 管理项目生命周期
## 常见问题
### VR相关问题
1. 确保VR设备已正确连接
2. 检查OpenVR运行时是否安装
3. 验证VR动作配置是否正确
### 渲染问题
1. 检查显卡驱动是否最新
2. 确认RenderPipeline配置正确
3. 验证材质和纹理路径
### 性能优化
1. 使用合适的LOD设置
2. 优化场景复杂度
3. 调整渲染质量设置
## 扩展开发
### 添加新脚本
1. 在 `scripts/` 目录创建新脚本文件
2. 继承 `ScriptBase`
3. 实现必要的方法
4. 通过 `ScriptManager` 注册脚本
### 添加新工具
1. 在 `core/tool_manager.py` 中注册新工具
2. 实现工具逻辑
3. 添加GUI界面元素
### 添加新渲染效果
1. 在 `RenderPipelineFile/rpplugins/` 目录创建插件
2. 实现渲染逻辑
3. 添加配置选项
## 联系和支持
- 项目Git仓库: http://10.0.0.99:4000/Rowland/EG.git
- 当前分支: imgui
- 最新提交: 移除qt依赖 (33e62bd1e4c2c8d3aac15e045b419edb8992d7ff)
---
*该文档由iFlow CLI自动生成最后更新时间: 2026年3月17日*

BIN
Assets/Models/jyc.glb Normal file

Binary file not shown.

View File

@ -0,0 +1,8 @@
{
"guid": "b547e6c1347a4ec0bb913a9cb1a7ec6b",
"asset_type": "model",
"source_hash": "46fc525a88f6d16eed0bac714a14692b68818d36a00065690b5877d878788948",
"importer": "model_importer",
"import_settings": {},
"dependency_guids": []
}

View File

@ -1,62 +0,0 @@
# EG 本地项目模块分析Qt 清理后)
更新时间2026-02-28
分析范围:`d:\IMGUI\EG` 本地文件,不包含任何远程仓库信息。
## 1. 当前结论
- 主编辑器路径已切换为 ImGui 方案Qt/PySide 在项目代码主路径中已清理完成。
- 本地扫描结果(排除 `RenderPipelineFile` 第三方目录):
- `PyQt/PySide/Qt` 关键引用:`0`
- Python 脚本数:`385`
- 当前主要技术债已从“Qt 运行时依赖”转为“旧接口命名和耦合残留”。
- 已完成基础稳定性优化:入口保护、兼容字段初始化、脚本恢复拼写错误修复。
- 已完成第一轮接口收敛:`event_handler/selection/runtime_actions/InfoPanelManager` 已改为 helper 访问模式。
## 2. 本轮 Qt 清理落点
已完成清理的关键文件:
- [core/InfoPanelManager.py](d:/IMGUI/EG/core/InfoPanelManager.py)
- [core/selection.py](d:/IMGUI/EG/core/selection.py)
- [scene/scene_manager_convert_tiles_mixin.py](d:/IMGUI/EG/scene/scene_manager_convert_tiles_mixin.py)
- [ui/widgets.py](d:/IMGUI/EG/ui/widgets.py)
- [ui/icon_manager.py](d:/IMGUI/EG/ui/icon_manager.py)
- [core/world.py](d:/IMGUI/EG/core/world.py)
- [core/vr_manager.py](d:/IMGUI/EG/core/vr_manager.py)
- [core/vr/testing/test_mode.py](d:/IMGUI/EG/core/vr/testing/test_mode.py)
- [scene/tree_roles.py](d:/IMGUI/EG/scene/tree_roles.py)
- [TransformGizmo/move_gizmo.py](d:/IMGUI/EG/TransformGizmo/move_gizmo.py)
- [TransformGizmo/rotate_gizmo.py](d:/IMGUI/EG/TransformGizmo/rotate_gizmo.py)
- [TransformGizmo/scale_gizmo.py](d:/IMGUI/EG/TransformGizmo/scale_gizmo.py)
- [requirements/requirements.txt](d:/IMGUI/EG/requirements/requirements.txt)
- [requirements/clean-requirements.txt](d:/IMGUI/EG/requirements/clean-requirements.txt)
- [requirements/environment.yml](d:/IMGUI/EG/requirements/environment.yml)
- [requirements/DEPLOYMENT_README.md](d:/IMGUI/EG/requirements/DEPLOYMENT_README.md)
## 3. 仍需优化的结构点(非 Qt 依赖问题)
- 旧接口命名残留仍较多:`interface_manager/treeWidget/gui_manager` 引用约 `92` 处。
- `main.py` 仍是脚本式入口(模块导入副作用风险仍在)。
- 场景树与 GUI 元素生命周期在多个模块分散维护,后续建议继续做单一上下文收敛。
## 4. 风险分级Qt 清理后)
- 高风险:
- 多处旧接口命名仍在调用链中,后续重构若不做适配层可能引发行为回归。
- 中风险:
- 场景保存/加载与树状态同步仍跨模块分散,维护成本高。
- 低风险:
- 文档和模板文件中仍有历史术语,易造成新成员理解偏差。
## 5. 后续建议顺序
1. 建立统一 EditorContext收敛 `interface_manager/gui_manager` 访问入口。
2. 将场景树操作抽象为 ImGui 专用接口,逐步移除 `treeWidget` 语义。
3. 统一入口规范(`if __name__ == "__main__":`)并补最小回归脚本。
## 6. 全量目录与脚本清单
为后续优化分析,完整目录树与全部脚本行数/用途清单见:
- [PROJECT_FULL_CATALOG.md](d:/IMGUI/EG/PROJECT_FULL_CATALOG.md)

File diff suppressed because it is too large Load Diff

View File

@ -1,352 +0,0 @@
# PROJECT_MODULE_INDEX
> 本文档只基于本地工作区扫描生成,不依赖 Git 远程信息。
- 生成时间: 2026-02-28 16:41:46
- 目标: 为后续优化/重构提供 功能模块 -> 文件 的快速定位索引
## 0. 关联分析文档
- 优化分析(本轮): `PROJECT_OPTIMIZATION_ANALYSIS.md`
- Qt 清理迁移清单: `QT_TO_IMGUI_MIGRATION_CHECKLIST.md`
- 历史模块分析: `IMGUI_MODULE_ANALYSIS.md`
## 1. 一级目录规模Python
| 目录 | 文件数 | 代码行数 |
|---|---:|---:|
| core | 40 | 19892 |
| gui | 1 | 7 |
| project | 2 | 759 |
| root | 2 | 812 |
| scene | 10 | 3792 |
| scripts | 18 | 1378 |
| ssbo_component | 3 | 1907 |
| templates | 1 | 1282 |
| tools | 1 | 73 |
| TransformGizmo | 5 | 3705 |
| ui | 63 | 17403 |
## 2. 功能模块 -> 关键文件映射
### 应用启动与组装
- 模块职责: 程序入口、系统初始化、模块装配
- 对应文件:
- Start_Run.py
- main.py
- templates/main_template.py
### 世界与运行时核心
- 模块职责: 世界生命周期、输入事件、选择、命令、资源、碰撞、地形等
- 对应文件:
- core/world.py
- core/event_handler.py
- core/selection.py
- core/Command_System.py
- core/tool_manager.py
- core/script_system.py
- core/resource_manager.py
- core/collision_manager.py
- core/terrain_manager.py
- core/model_drag_drop.py
- core/InfoPanelManager.py
- core/CustomMouseController.py
- core/imgui_style_manager.py
- core/imgui_webview.py
- core/selection_outline.py
- core/render_pipeline_utils.py
### VR 系统
- 模块职责: VR 管理、渲染阶段、交互、追踪、性能、可视化、配置
- 对应文件:
- core/vr_manager.py
- core/vr/config/vr_config.py
- core/vr/config/joystick_config.py
- core/vr/config/shadow_stage.py
- core/vr/rendering/stages.py
- core/vr/interaction/actions.py
- core/vr/interaction/grab.py
- core/vr/interaction/joystick.py
- core/vr/interaction/teleport.py
- core/vr/tracking/controllers.py
- core/vr/visualization/controllers.py
- core/vr/visualization/effects.py
- core/vr/performance/monitoring.py
- core/vr/performance/optimization.py
- core/vr/testing/test_mode.py
### 场景管理
- 模块职责: 模型导入、灯光管理、序列化、IO、切片转换
- 对应文件:
- scene/scene_manager.py
- scene/scene_manager_impl.py
- scene/scene_manager_model_mixin.py
- scene/scene_manager_light_mixin.py
- scene/scene_manager_serialization_mixin.py
- scene/scene_manager_io_mixin.py
- scene/scene_manager_convert_tiles_mixin.py
- scene/util.py
- scene/tree_roles.py
### 项目管理
- 模块职责: 项目创建、打开、保存、资源清单
- 对应文件:
- project/project_manager.py
### 编辑器面板 (ImGui)
- 模块职责: 顶部/左侧/中心/右侧属性面板、弹窗、运行时动作、创建对象、动画工具
- 对应文件:
- ui/panels/editor_panels.py
- ui/panels/editor_panels_top.py
- ui/panels/editor_panels_left.py
- ui/panels/editor_panels_center.py
- ui/panels/editor_panels_right.py
- ui/panels/editor_panels_right_transform.py
- ui/panels/editor_panels_right_material.py
- ui/panels/editor_panels_right_collision.py
- ui/panels/panel_delegates.py
- ui/panels/property_helpers.py
- ui/panels/runtime_actions.py
- ui/panels/create_actions.py
- ui/panels/app_actions.py
- ui/panels/dialog_panels.py
- ui/panels/script_panels.py
- ui/panels/interaction_panels.py
- ui/panels/object_factory.py
- ui/panels/animation_tools.py
### LUI 编辑器与组件
- 模块职责: LUI 管理、属性编辑、交互编辑、组件定义
- 对应文件:
- ui/lui_manager.py
- ui/lui_function.py
- ui/LUI/lui_manager_editor.py
- ui/LUI/lui_manager_interaction.py
- ui/LUI/lui_function_properties.py
- ui/LUI/lui_function_components.py
- ui/LUI/lui_shared.py
- ui/widgets.py
- ui/icon_manager.py
### LUI 内建控件与皮肤
- 模块职责: LUI 基础控件实现与皮肤资源脚本
- 对应文件:
- ui/Builtin/Elements.py
- ui/Builtin/LUI*.py
- ui/Builtin/RectTransform.py
- ui/Skins/Metro/LUIMetroSkin.py
- ui/Skins/Metro/copy_frames.py
### Transform Gizmo
- 模块职责: 移动/旋转/缩放 gizmo 与事件
- 对应文件:
- TransformGizmo/transform_gizmo.py
- TransformGizmo/move_gizmo.py
- TransformGizmo/rotate_gizmo.py
- TransformGizmo/scale_gizmo.py
- TransformGizmo/events.py
### SSBO 选取与编辑
- 模块职责: 基于 SSBO 的对象选取与编辑器
- 对应文件:
- ssbo_component/ssbo_editor.py
- ssbo_component/ssbo_controller.py
- ssbo_component/demo_component.py
### 脚本样例与测试
- 模块职责: 内置脚本、旋转/移动/缩放测试脚本
- 对应文件:
- scripts/*.py
### 开发工具
- 模块职责: 辅助分析脚本
- 对应文件:
- tools/open_source_rate.py
## 3. 配置与运行关键文件
- config/vr_settings.json
- core/vr/config/vr_settings.json
- vr_actions/actions.json
- vr_actions/bindings_index.json
- vr_actions/bindings_oculus.json
- vr_actions/bindings_vive.json
- imgui.ini
- requirements/requirements.txt
- requirements/clean-requirements.txt
- requirements/conda-requirements.txt
- requirements/environment.yml
## 4. 第三方/外部子树说明
- RenderPipelineFile/: 第三方渲染管线源码与工具Python 文件 246 个,约 31629 行。
- 优化建议: 优先在本项目业务目录改动core/, ui/, scene/, project/, ssbo_component/, TransformGizmo/),避免直接修改第三方目录。
## 5. 完整 Python 文件索引(含行数)
| 文件 | 行数 |
|---|---:|
| core/__init__.py | 23 |
| core/collision_manager.py | 1050 |
| core/Command_System.py | 576 |
| core/CustomMouseController.py | 185 |
| core/event_handler.py | 576 |
| core/imgui_style_manager.py | 428 |
| core/imgui_webview.py | 189 |
| core/InfoPanelManager.py | 1460 |
| core/model_drag_drop.py | 485 |
| core/render_pipeline_utils.py | 17 |
| core/resource_manager.py | 471 |
| core/script_system.py | 817 |
| core/selection.py | 2461 |
| core/selection_outline.py | 263 |
| core/terrain_manager.py | 571 |
| core/tool_manager.py | 133 |
| core/vr/__init__.py | 31 |
| core/vr/config/__init__.py | 8 |
| core/vr/config/joystick_config.py | 226 |
| core/vr/config/shadow_stage.py | 145 |
| core/vr/config/vr_config.py | 216 |
| core/vr/interaction/__init__.py | 9 |
| core/vr/interaction/actions.py | 484 |
| core/vr/interaction/grab.py | 329 |
| core/vr/interaction/joystick.py | 561 |
| core/vr/interaction/teleport.py | 331 |
| core/vr/performance/__init__.py | 9 |
| core/vr/performance/monitoring.py | 972 |
| core/vr/performance/optimization.py | 287 |
| core/vr/rendering/__init__.py | 10 |
| core/vr/rendering/stages.py | 712 |
| core/vr/testing/__init__.py | 6 |
| core/vr/testing/test_mode.py | 609 |
| core/vr/tracking/__init__.py | 9 |
| core/vr/tracking/controllers.py | 391 |
| core/vr/visualization/__init__.py | 8 |
| core/vr/visualization/controllers.py | 631 |
| core/vr/visualization/effects.py | 174 |
| core/vr_manager.py | 2970 |
| core/world.py | 1059 |
| gui/__init__.py | 7 |
| main.py | 770 |
| project/__init__.py | 10 |
| project/project_manager.py | 749 |
| scene/__init__.py | 10 |
| scene/scene_manager.py | 17 |
| scene/scene_manager_convert_tiles_mixin.py | 473 |
| scene/scene_manager_impl.py | 15 |
| scene/scene_manager_io_mixin.py | 962 |
| scene/scene_manager_light_mixin.py | 403 |
| scene/scene_manager_model_mixin.py | 919 |
| scene/scene_manager_serialization_mixin.py | 749 |
| scene/tree_roles.py | 3 |
| scene/util.py | 241 |
| scripts/a.py | 25 |
| scripts/BouncerScript.py | 99 |
| scripts/ColorChangerScript.py | 157 |
| scripts/ComboAnimatorScript.py | 36 |
| scripts/example_script.py | 44 |
| scripts/FollowerScript.py | 66 |
| scripts/MoverScript.py | 88 |
| scripts/R_P.py | 110 |
| scripts/R_R.py | 110 |
| scripts/Rotate_H_Script.py | 181 |
| scripts/Rotate_P_Script.py | 181 |
| scripts/Rotate_R_Script.py | 44 |
| scripts/RotatorScript.py | 36 |
| scripts/ScalerScript.py | 88 |
| scripts/test_quick_script.py | 25 |
| scripts/TestMover.py | 38 |
| scripts/TestRotator.py | 25 |
| scripts/TestScaler.py | 25 |
| ssbo_component/demo_component.py | 70 |
| ssbo_component/ssbo_controller.py | 1055 |
| ssbo_component/ssbo_editor.py | 782 |
| Start_Run.py | 42 |
| templates/main_template.py | 1282 |
| tools/open_source_rate.py | 73 |
| TransformGizmo/events.py | 14 |
| TransformGizmo/move_gizmo.py | 950 |
| TransformGizmo/rotate_gizmo.py | 1412 |
| TransformGizmo/scale_gizmo.py | 860 |
| TransformGizmo/transform_gizmo.py | 469 |
| ui/Builtin/__init__.py | 0 |
| ui/Builtin/Elements.py | 242 |
| ui/Builtin/LUIBlockText.py | 74 |
| ui/Builtin/LUIButton.py | 169 |
| ui/Builtin/LUICanvas.py | 97 |
| ui/Builtin/LUICheckbox.py | 65 |
| ui/Builtin/LUIFormattedLabel.py | 38 |
| ui/Builtin/LUIFrame.py | 54 |
| ui/Builtin/LUIHorizontalLayout.py | 13 |
| ui/Builtin/LUIInitialState.py | 32 |
| ui/Builtin/LUIInputField.py | 180 |
| ui/Builtin/LUIInputHandler.py | 5 |
| ui/Builtin/LUILabel.py | 63 |
| ui/Builtin/LUILayouts.py | 80 |
| ui/Builtin/LUIObject.py | 12 |
| ui/Builtin/LUIProgressbar.py | 54 |
| ui/Builtin/LUIRadiobox.py | 73 |
| ui/Builtin/LUIRadioboxGroup.py | 31 |
| ui/Builtin/LUIRegion.py | 5 |
| ui/Builtin/LUIRoot.py | 5 |
| ui/Builtin/LUIScrollableRegion.py | 119 |
| ui/Builtin/LUISelectbox.py | 158 |
| ui/Builtin/LUISkin.py | 34 |
| ui/Builtin/LUISlider.py | 185 |
| ui/Builtin/LUISprite.py | 12 |
| ui/Builtin/LUISpriteButton.py | 23 |
| ui/Builtin/LUITabbedFrame.py | 77 |
| ui/Builtin/LUIVerticalLayout.py | 13 |
| ui/Builtin/RectTransform.py | 73 |
| ui/icon_manager.py | 123 |
| ui/LUI/__init__.py | 1 |
| ui/LUI/lui_function_components.py | 1149 |
| ui/LUI/lui_function_properties.py | 1601 |
| ui/LUI/lui_manager_editor.py | 1593 |
| ui/LUI/lui_manager_interaction.py | 1351 |
| ui/LUI/lui_shared.py | 62 |
| ui/lui_function.py | 31 |
| ui/lui_manager.py | 199 |
| ui/panels/__init__.py | 0 |
| ui/panels/animation_tools.py | 1390 |
| ui/panels/app_actions.py | 1097 |
| ui/panels/create_actions.py | 147 |
| ui/panels/dialog_panels.py | 992 |
| ui/panels/editor_panels.py | 20 |
| ui/panels/editor_panels_center.py | 187 |
| ui/panels/editor_panels_left.py | 543 |
| ui/panels/editor_panels_right.py | 742 |
| ui/panels/editor_panels_right_collision.py | 210 |
| ui/panels/editor_panels_right_material.py | 222 |
| ui/panels/editor_panels_right_transform.py | 89 |
| ui/panels/editor_panels_top.py | 319 |
| ui/panels/interaction_panels.py | 128 |
| ui/panels/object_factory.py | 390 |
| ui/panels/panel_delegates.py | 512 |
| ui/panels/property_helpers.py | 1583 |
| ui/panels/runtime_actions.py | 356 |
| ui/panels/script_panels.py | 287 |
| ui/Skins/__init__.py | 0 |
| ui/Skins/Default/__init__.py | 0 |
| ui/Skins/Metro/__init__.py | 0 |
| ui/Skins/Metro/copy_frames.py | 31 |
| ui/Skins/Metro/LUIMetroSkin.py | 24 |
| ui/widgets.py | 38 |
## 6. 使用方式(优化流程建议)
1. 先在 功能模块 -> 关键文件映射 中定位模块。
2. 再到 完整 Python 文件索引 按文件名快速跳转。
3. 优先处理高行数核心文件(如 core/selection.py, core/vr_manager.py, ui/panels/property_helpers.py

View File

@ -1,270 +0,0 @@
# PROJECT_OPTIMIZATION_ANALYSIS
> 基于本地代码静态扫描生成(不含远程仓库信息,不含 `RenderPipelineFile/` 第三方目录)。
- 分析时间: 2026-02-28
- 扫描范围: `main.py`, `Start_Run.py`, `core/`, `scene/`, `project/`, `ui/`, `ssbo_component/`, `TransformGizmo/`, `scripts/`, `tools/`, `templates/`
## 0. 执行进展(非 VR
- 已完成 `EditorContext` 适配层:`core/editor_context.py`
- 已接入文件:
- `core/event_handler.py`
- `core/selection.py`
- `core/InfoPanelManager.py`
- `ui/panels/runtime_actions.py`
- `core/terrain_manager.py`
- `scene/scene_manager_convert_tiles_mixin.py`
- `scene/scene_manager_serialization_mixin.py`
- `scene/scene_manager_model_mixin.py`
- 效果(本地静态检索):
- 直接访问 `world.interface_manager`: `0`
- 直接访问 `interface_manager.treeWidget`: `0`
- `app.gui_manager` 仅剩注释引用(`ui/panels/editor_panels_left.py`
- Task B 第一轮已落地(`scene_manager_io_mixin.loadScene`
- 已抽出流程 helper
- `_preflight_load_scene`
- `_cleanup_after_failed_load`
- `_clear_current_scene_for_load`
- `_load_scene_root_from_file`
- `_bootstrap_scene_tree_for_loaded_root`
- `_load_scene_gui_metadata`
- `_retry_load_scene`
- `loadScene` 行数:`556 -> 366`
- 已清理重复异常分支:`_rebuildParentChildRelationships` 内重复 `except` 已移除
## 1. 总体画像
- Python 文件: `146`
- 代码总行数: `58,371`
- `except Exception` / `except:` 总计: `950`
- 裸 `except:` 总计: `63`
- 旧上下文关键词引用总量:
- `interface_manager`: `35`
- `treeWidget`: `10`
- `gui_manager`: `77`
结论:
- Qt 依赖已清理后,当前主要技术债集中在三类:
- 过大函数(可维护性差)
- 异常处理过宽(问题可观测性差)
- 旧 GUI 上下文命名耦合(边界不清晰)
## 2. 热点文件(按规模/风险)
### 2.1 超大文件 Top
1. `core/vr_manager.py` (`3553` 行)
2. `core/selection.py` (`2942` 行)
3. `core/InfoPanelManager.py` (`1726` 行)
4. `ui/LUI/lui_manager_editor.py` (`1724` 行)
5. `ui/panels/property_helpers.py` (`1711` 行)
6. `ui/LUI/lui_function_properties.py` (`1707` 行)
7. `TransformGizmo/rotate_gizmo.py` (`1587` 行)
8. `ui/panels/animation_tools.py` (`1579` 行)
### 2.2 长函数 Top优先拆分
1. `ui/LUI/lui_function_properties.py::_draw_component_properties` (`1441` 行)
2. `scene/scene_manager_io_mixin.py::loadScene` (`556` 行)
3. `ui/panels/animation_tools.py::_getActor` (`510` 行)
4. `main.py::__init__` (`375` 行)
5. `ui/LUI/lui_manager_interaction.py::_update_drag` (`348` 行)
6. `ui/panels/editor_panels_left.py::_draw_resource_manager` (`310` 行)
7. `scene/scene_manager_io_mixin.py::processNode` (`281` 行)
8. `core/selection.py::updateGizmoDrag` (`278` 行)
### 2.3 异常处理密度高(可观测性风险)
1. `ui/panels/animation_tools.py` (`except* = 85`)
2. `core/vr_manager.py` (`71`)
3. `core/selection.py` (`57`)
4. `ui/panels/property_helpers.py` (`54`)
5. `ui/panels/app_actions.py` (`46`)
6. `scene/scene_manager_model_mixin.py` (`36`)
7. `scene/scene_manager_serialization_mixin.py` (`27`)
8. `scene/scene_manager_io_mixin.py` (`20`)
9. `project/project_manager.py` (`20`)
10. `ui/panels/runtime_actions.py` (`20`)
`except:` 集中区:
- `core/selection.py` (`7`)
- `scene/scene_manager_model_mixin.py` (`7`)
- `ui/panels/editor_panels_right_material.py` (`6`)
- `ui/panels/editor_panels_left.py` (`5`)
### 2.4 旧上下文耦合集中区历史基线Task A 前)
1. `ui/panels/runtime_actions.py` (`gui_manager=29`)
2. `core/event_handler.py` (`interface_manager=11`, `gui_manager=11`)
3. `ui/panels/editor_panels_right.py` (`gui_manager=18`)
4. `scene/scene_manager_serialization_mixin.py` (`interface_manager=6`, `treeWidget=2`, `gui_manager=5`)
5. `core/selection.py` (`interface_manager=4`, `treeWidget=1`)
6. `core/InfoPanelManager.py` (`interface_manager=4`, `treeWidget=1`)
7. `core/terrain_manager.py` (`interface_manager=3`, `treeWidget=2`)
## 3. 优化优先级(建议执行顺序)
## P0: 上下文收敛(先做)
目标: 统一 GUI/场景树访问边界,减少跨模块 `hasattr(..., 'interface_manager')``treeWidget` 语义残留。
建议动作:
1. 引入 `EditorContext`(或 `UIContext`)统一提供:
- `get_tree_adapter()`
- `get_gui_service()`
- `get_selection_service()`
2. 在以下文件先改为调用上下文接口:
- `core/event_handler.py`
- `core/selection.py`
- `core/InfoPanelManager.py`
- `core/terrain_manager.py`
- `scene/scene_manager_serialization_mixin.py`
- `scene/scene_manager_convert_tiles_mixin.py`
- `ui/panels/runtime_actions.py`
预期收益:
- 降低命名残留与多处 `hasattr` 防御代码。
- 后续模块拆分时边界更稳定。
## P1: 大函数拆分(第二阶段)
目标: 将核心长函数拆分成“流程编排 + 子步骤函数”,减少单函数认知负担。
建议拆分顺序:
1. `scene/scene_manager_io_mixin.py::loadScene`
2. `main.py::__init__`
3. `ui/panels/animation_tools.py::_getActor`
4. `core/selection.py::updateGizmoDrag`
5. `ui/LUI/lui_function_properties.py::_draw_component_properties`(可按“变换/布局/视觉/交互/脚本”分区)
预期收益:
- 回归问题定位更快。
- 面板和场景加载逻辑更易测试。
## P2: 异常处理治理(并行推进)
目标: 将“吞异常”改为“有边界的降级 + 可追踪日志”。
建议规则:
1. 禁止新增裸 `except:`
2. 高风险路径必须记录上下文:
- 节点名/资源路径/操作类型/当前工具状态
3. 对可恢复错误使用 `warning`,不可恢复错误返回显式失败值。
优先文件:
- `ui/panels/animation_tools.py`
- `core/vr_manager.py`
- `core/selection.py`
- `ui/panels/property_helpers.py`
- `scene/scene_manager_io_mixin.py`
## 4. 下一步可直接执行的任务包
### Task A推荐先做1-2 天)
- 建立 `core/editor_context.py`(或同级命名)
- 给 `event_handler/selection/InfoPanelManager/runtime_actions` 接入上下文
- 保持外部 API 不变,仅替换内部访问路径
### Task B1 天)
- 重构 `scene_manager_io_mixin.loadScene`
- `preflight`
- `clear_old_scene`
- `load_bam`
- `rebuild_scene_tree`
- `post_load_sync`
### Task C1 天)
- 统一异常日志工具(轻量封装)
- 首批替换 `animation_tools.py``property_helpers.py`
## 4.1 本轮深入分析(非 VRP1 准备)
### A) `scene/scene_manager_io_mixin.py::loadScene`(核心优先)
- 函数规模: `556`
- 近似圈复杂度: `114`
- 关键问题:
- 单函数同时承担 8 类职责(校验/清理/加载/树同步/节点递归处理/脚本恢复/材质恢复/重试)。
- 内嵌 `processNode` 递归函数长达 `281` 行,可测试性差。
- 调试输出密度高(`print` 与注释 `print` 很多),影响可读性和噪声控制。
- `scene/scene_manager_io_mixin.py:1026``scene/scene_manager_io_mixin.py:1032` 存在重复 `except Exception` 分支(可合并)。
- `scene/scene_manager_io_mixin.py:946` 的 GUI 重建入口目前仍注释,行为边界不清晰。
- 建议拆分(保持外部 API `loadScene` 不变):
- `_preflight_scene_file(filename) -> (ok, normalized_path, reason)`
- `_cleanup_before_load(tree_widget, retry_count)`
- `_load_bam_scene(filename) -> scene_or_none`
- `_bootstrap_tree_items(scene, tree_widget)`
- `_walk_loaded_scene(scene, tree_widget) -> loaded_nodes`
- `_restore_loaded_nodes_state(node_path, processed_lights, loaded_nodes)`(从 `processNode` 中抽)
- `_post_load_finalize(scene, loaded_nodes, filename)`
- `_retry_load_scene(filename, retry_count, error) -> bool`
- 验收标准:
- `loadScene` 主体压缩到 `120` 行以内,只保留流程编排。
- 节点恢复行为(位置/材质/脚本/可见性)与当前一致。
- 失败重试逻辑保持语义一致。
- 当前状态2026-02-28:
- 第一轮已完成(预检/清理/加载/树初始化/GUI元数据/重试)。
- 剩余主要体积来自内嵌 `processNode`,下一轮应继续提取为独立方法。
### B) `main.py::__init__`(第二优先)
- 函数规模: `375`
- 近似圈复杂度: `15`
- 问题性质:
- 复杂度不高,但“启动装配职责”过于集中,初始化顺序风险高。
- 建议拆分:
- `_init_legacy_compat_fields()`
- `_init_core_services_non_vr()`(不含 VR
- `_init_imgui_runtime()`
- `_init_panel_modules()`
- `_init_runtime_state_flags()`
- `_bind_input_shortcuts()`
- `_init_drag_drop_and_messages()`
- 约束:
- VR 初始化保持原样,不纳入本轮改造范围。
### C) `ui/panels/animation_tools.py::_getActor`(第三优先)
- 函数规模: `510`
- 近似圈复杂度: `143`
- 关键问题:
- 路径推断、缓存策略、Actor 构建、autoBind 回退、GLTF 特化混在一个函数中。
- 局部嵌套函数层级深,行为分支很难覆盖测试。
- 建议拆分:
- `_resolve_actor_owner_and_paths(origin_model)`
- `_load_actor_from_candidate_paths(owner_model, paths)`
- `_load_actor_via_memory_fallback(owner_model, origin_model)`
- `_load_actor_via_gltf_special(path)`
- `_validate_actor_playable(actor_or_proxy)`
- `_cache_actor(owner_model, actor)`
- 验收标准:
- `_getActor` 保留为流程入口,长度控制到 `150` 行以内。
- 保持当前“优先路径加载,失败回退内存/autoBind”的策略不变。
## 4.2 Task B 执行顺序建议(非 VR
1. 先拆 `loadScene`(收益最大,且与 VR 无关)。
2. 再整理 `main.__init__`(降低后续模块接入冲突)。
3. 最后处理 `_getActor`(风险最高,建议独立提交并做手工回归)。
## 5. 与现有文档关系
- 模块总索引: `PROJECT_MODULE_INDEX.md`
- Qt 迁移状态: `QT_TO_IMGUI_MIGRATION_CHECKLIST.md`
- 历史分析: `IMGUI_MODULE_ANALYSIS.md`
---
如果按此路线继续,建议下一轮直接从 **Task A** 开始,我可以先落地上下文适配层并改 4 个高耦合文件。

View File

@ -1,62 +0,0 @@
# Qt -> ImGui 迁移清单Qt 清理后状态)
更新时间2026-02-28
适用范围:`d:\IMGUI\EG` 本地工作区
## 目标
- 编辑器主路径稳定运行在 ImGui + Panda3D。
- 项目主代码不再依赖 PyQt/PySide 运行。
- 后续把历史命名与耦合(`interface_manager/treeWidget/gui_manager`)继续收敛。
## 当前状态总览
- `PyQt/PySide/Qt` 在项目代码主路径(排除 `RenderPipelineFile`)扫描结果:`0`。
- 依赖清单已从 Qt 运行依赖切到 ImGui 运行依赖。
- `ui/widgets.py` 已改为 legacy 占位模块,避免误用 Qt 路径。
## 已完成Done
- [x] `core/InfoPanelManager.py`:去除 Qt 直接导入。
- [x] `core/selection.py`:光标逻辑改为 Panda3D 路径。
- [x] `scene/scene_manager_convert_tiles_mixin.py`:去掉 `QProgressDialog`,改为非 Qt 进度反馈。
- [x] `ui/widgets.py`:替换为 ImGui 迁移提示占位模块。
- [x] `ui/icon_manager.py`:替换为无 Qt 的兼容层实现。
- [x] `core/world.py` / `core/vr_manager.py` / `core/vr/testing/test_mode.py`:去除 `qtWidget` 语义依赖。
- [x] `requirements/*`:清理 Qt/PySide 依赖项并同步部署文档。
- [x] `main.py`:补齐兼容字段初始化(`gui_elements/gui_manager/interface_manager/guiEditMode/currentGUITool`)。
- [x] `scene/scene_manager_io_mixin.py`:修复 `_find_scrip_in_directory` 拼写错误。
- [x] `main.py`:入口改为 `if __name__ == "__main__":`,消除导入即运行副作用。
- [x] `core/event_handler.py`:新增场景树访问 helper替换 `interface_manager.treeWidget` 直连。
- [x] `core/selection.py`:统一树控件清空逻辑(`_get_tree_widget/_clear_tree_selection`)。
- [x] `ui/panels/runtime_actions.py`:统一 `gui_manager` 访问与 `gui_elements` 追加入口。
- [x] `core/InfoPanelManager.py`:统一场景树控件访问 helper。
- [x] `ui/panels/editor_panels.py`:按布局拆分为 4 个 mixin`top/left/right/center`),保留门面类组合。
## 待完成Next
### N1 场景树接口收敛(高优先级)
- 目标:移除 `treeWidget` 语义,统一走 ImGui 场景树接口。
- 影响路径:`core/event_handler.py`、`scene/scene_manager_*_mixin.py`、`core/terrain_manager.py` 等。
### N2 GUI 管理上下文收敛(高优先级)
- 目标:将 `interface_manager/gui_manager/gui_elements` 统一到单一上下文对象。
- 影响路径:`project/project_manager.py`、`scene/scene_manager_io_mixin.py`、`ui/panels/*`。
### N3 入口规范化与回归脚本(中优先级)
- 目标:减少脚本式副作用,建立最小回归检查。
- 建议覆盖:启动、导入模型、选择变换、保存加载、脚本挂载。
## 验收标准
- 不安装 PyQt/PySide 时,`python Start_Run.py` 能进入编辑器主界面。
- 场景导入/保存/加载流程不触发 Qt 相关异常。
- 关键交互(选择、变换、场景树)行为与清理前一致。
## 关联文档
- 模块分析: [IMGUI_MODULE_ANALYSIS.md](d:/IMGUI/EG/IMGUI_MODULE_ANALYSIS.md)
- 全量目录与脚本清单: [PROJECT_FULL_CATALOG.md](d:/IMGUI/EG/PROJECT_FULL_CATALOG.md)

Binary file not shown.

View File

@ -1,102 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
跳跃脚本 - 让对象产生上下跳跃效果
"""
from core.script_system import ScriptBase
import math
class BouncerScript(ScriptBase):
"""跳跃脚本类"""
def __init__(self):
super().__init__()
# 跳跃参数
self.jump_height = 2.0 # 跳跃高度
self.jump_speed = 3.0 # 跳跃速度 (跳跃/秒)
self.bounce_type = "sine" # 跳跃类型: "sine", "abs_sine", "square"
# 内部变量
self.time_accumulator = 0.0 # 时间累积器
self.original_y = None # 原始Y位置
self.is_bouncing = True # 是否正在跳跃
self.bounce_direction = 1 # 跳跃方向
def start(self):
"""脚本开始时调用"""
self.log("跳跃脚本启动!")
self.log(f"跳跃参数: 高度={self.jump_height}, 速度={self.jump_speed}, 类型={self.bounce_type}")
# 记录原始Y位置
self.original_y = self.gameObject.getZ() # Z轴是高度
self.log(f"原始高度: {self.original_y}")
def update(self, dt):
"""每帧更新"""
if not self.is_bouncing:
return
# 累积时间
self.time_accumulator += dt * self.bounce_direction
# 根据类型计算跳跃高度
if self.bounce_type == "sine":
# 标准正弦波跳跃
height_offset = math.sin(self.time_accumulator * self.jump_speed * 2 * math.pi) * self.jump_height
elif self.bounce_type == "abs_sine":
# 绝对值正弦波(始终向上)
height_offset = abs(math.sin(self.time_accumulator * self.jump_speed * 2 * math.pi)) * self.jump_height
elif self.bounce_type == "square":
# 方波跳跃(突然跳起落下)
sine_val = math.sin(self.time_accumulator * self.jump_speed * 2 * math.pi)
height_offset = self.jump_height if sine_val > 0 else 0
else:
height_offset = 0
# 应用跳跃
current_pos = self.gameObject.getPos()
new_z = self.original_y + height_offset
self.gameObject.setPos(current_pos.getX(), current_pos.getY(), new_z)
def set_bounce_parameters(self, height=None, speed=None, bounce_type=None):
"""设置跳跃参数"""
if height is not None:
self.jump_height = height
if speed is not None:
self.jump_speed = speed
if bounce_type is not None and bounce_type in ["sine", "abs_sine", "square"]:
self.bounce_type = bounce_type
self.log(f"跳跃参数更新: 高度={self.jump_height}, 速度={self.jump_speed}, 类型={self.bounce_type}")
def toggle_bouncing(self):
"""切换跳跃状态"""
self.is_bouncing = not self.is_bouncing
status = "恢复" if self.is_bouncing else "暂停"
self.log(f"跳跃{status}")
def reverse_direction(self):
"""反转跳跃方向"""
self.bounce_direction *= -1
direction = "正向" if self.bounce_direction > 0 else "反向"
self.log(f"跳跃方向改为{direction}")
def reset_position(self):
"""重置到原始高度"""
if self.original_y is not None:
current_pos = self.gameObject.getPos()
self.gameObject.setPos(current_pos.getX(), current_pos.getY(), self.original_y)
self.time_accumulator = 0.0
self.log("位置已重置到原始高度")
def jump_once(self):
"""执行一次跳跃"""
self.time_accumulator = 0.0
self.log("执行单次跳跃")
def on_destroy(self):
"""脚本销毁时调用"""
self.log("跳跃脚本停止")

View File

@ -1,160 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
颜色变化脚本 - 让对象颜色产生循环变化
"""
from core.script_system import ScriptBase
from panda3d.core import Vec4
import math
class ColorChangerScript(ScriptBase):
"""颜色变化脚本类"""
def __init__(self):
super().__init__()
# 颜色参数
self.color_speed = 1.0 # 颜色变化速度 (周期/秒)
self.color_mode = "rainbow" # 颜色模式: "rainbow", "pulse", "fade", "strobe"
self.base_color = Vec4(1, 1, 1, 1) # 基础颜色
self.intensity = 1.0 # 颜色强度
# 内部变量
self.time_accumulator = 0.0 # 时间累积器
self.original_color = None # 原始颜色
self.is_changing = True # 是否正在变化
self.strobe_state = False # 闪烁状态
def start(self):
"""脚本开始时调用"""
self.log("颜色变化脚本启动!")
self.log(f"颜色参数: 速度={self.color_speed}, 模式={self.color_mode}, 强度={self.intensity}")
# 记录原始颜色
self.original_color = self.gameObject.getColor()
self.log(f"原始颜色: {self.original_color}")
def update(self, dt):
"""每帧更新"""
if not self.is_changing:
return
# 累积时间
self.time_accumulator += dt
# 根据模式计算新颜色
if self.color_mode == "rainbow":
new_color = self._calculate_rainbow_color()
elif self.color_mode == "pulse":
new_color = self._calculate_pulse_color()
elif self.color_mode == "fade":
new_color = self._calculate_fade_color()
elif self.color_mode == "strobe":
new_color = self._calculate_strobe_color()
else:
new_color = self.base_color
# 应用颜色
self.gameObject.setColor(new_color)
def _calculate_rainbow_color(self):
"""计算彩虹颜色"""
# 使用HSV到RGB的转换创建彩虹效果
hue = (self.time_accumulator * self.color_speed) % 1.0
# 简单的HSV到RGB转换
i = int(hue * 6.0)
f = (hue * 6.0) - i
p = 0.0
q = 1.0 - f
t = f
if i % 6 == 0:
r, g, b = 1.0, t, p
elif i % 6 == 1:
r, g, b = q, 1.0, p
elif i % 6 == 2:
r, g, b = p, 1.0, t
elif i % 6 == 3:
r, g, b = p, q, 1.0
elif i % 6 == 4:
r, g, b = t, p, 1.0
else:
r, g, b = 1.0, p, q
return Vec4(r * self.intensity, g * self.intensity, b * self.intensity, 1.0)
def _calculate_pulse_color(self):
"""计算脉冲颜色"""
pulse = (math.sin(self.time_accumulator * self.color_speed * 2 * math.pi) + 1.0) / 2.0
multiplier = pulse * self.intensity
return Vec4(
self.base_color.getX() * multiplier,
self.base_color.getY() * multiplier,
self.base_color.getZ() * multiplier,
self.base_color.getW()
)
def _calculate_fade_color(self):
"""计算淡入淡出颜色"""
fade = (math.sin(self.time_accumulator * self.color_speed * 2 * math.pi) + 1.0) / 2.0
alpha = fade * self.intensity
return Vec4(
self.base_color.getX(),
self.base_color.getY(),
self.base_color.getZ(),
alpha
)
def _calculate_strobe_color(self):
"""计算闪烁颜色"""
# 根据时间间隔切换状态
interval = 1.0 / (self.color_speed * 2) # 闪烁间隔
if int(self.time_accumulator / interval) % 2 == 0:
return Vec4(
self.base_color.getX() * self.intensity,
self.base_color.getY() * self.intensity,
self.base_color.getZ() * self.intensity,
self.base_color.getW()
)
else:
return Vec4(0.1, 0.1, 0.1, self.base_color.getW()) # 暗色状态
def set_color_parameters(self, speed=None, mode=None, base_color=None, intensity=None):
"""设置颜色参数"""
if speed is not None:
self.color_speed = speed
if mode is not None and mode in ["rainbow", "pulse", "fade", "strobe"]:
self.color_mode = mode
if base_color is not None:
self.base_color = base_color
if intensity is not None:
self.intensity = intensity
self.log(f"颜色参数更新: 速度={self.color_speed}, 模式={self.color_mode}, 强度={self.intensity}")
def toggle_color_change(self):
"""切换颜色变化状态"""
self.is_changing = not self.is_changing
status = "恢复" if self.is_changing else "暂停"
self.log(f"颜色变化{status}")
def reset_color(self):
"""重置到原始颜色"""
if self.original_color:
self.gameObject.setColor(self.original_color)
self.time_accumulator = 0.0
self.log("颜色已重置到原始值")
def set_solid_color(self, r=1.0, g=1.0, b=1.0, a=1.0):
"""设置固定颜色"""
color = Vec4(r, g, b, a)
self.gameObject.setColor(color)
self.base_color = color
self.log(f"设置固定颜色: {color}")
def on_destroy(self):
"""脚本销毁时调用"""
self.log("颜色变化脚本停止")

View File

@ -1,39 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
复合动画脚本 - 结合旋转和跳跃效果
"""
from core.script_system import ScriptBase
import math
class ComboAnimatorScript(ScriptBase):
def __init__(self):
super().__init__()
self.time = 0.0
self.original_pos = None
self.is_active = True
def start(self):
self.log("复合动画脚本启动!")
self.original_pos = self.gameObject.getPos()
def update(self, dt):
if not self.is_active:
return
self.time += dt
# 旋转效果
current_hpr = self.gameObject.getHpr()
new_h = current_hpr.getX() + 45.0 * dt
self.gameObject.setHpr(new_h, current_hpr.getY(), current_hpr.getZ())
# 跳跃效果
if self.original_pos:
bounce_offset = abs(math.sin(self.time * 3.0)) * 1.0
self.gameObject.setZ(self.original_pos.getZ() + bounce_offset)
def on_destroy(self):
self.log("复合动画脚本停止")

View File

@ -1,69 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
跟随脚本 - 让对象跟随指定的目标对象
"""
from core.script_system import ScriptBase
from panda3d.core import Vec3
class FollowerScript(ScriptBase):
"""跟随脚本类"""
def __init__(self):
super().__init__()
self.target = None # 跟随目标
self.follow_speed = 5.0 # 跟随速度
self.follow_distance = 2.0 # 跟随距离
self.is_following = True # 是否正在跟随
def start(self):
"""脚本开始时调用"""
self.log("跟随脚本启动!")
self.log(f"跟随参数: 速度={self.follow_speed}, 距离={self.follow_distance}")
def update(self, dt):
"""每帧更新"""
if not self.is_following or self.target is None:
return
target_pos = self.target.getPos()
current_pos = self.gameObject.getPos()
# 计算目标方向
direction = target_pos - current_pos
distance = direction.length()
# 如果距离大于跟随距离,则移动
if distance > self.follow_distance:
if distance > 0:
direction.normalize()
# 计算目标位置(保持跟随距离)
target_follow_pos = target_pos - direction * self.follow_distance
# 平滑移动到目标位置
move_direction = target_follow_pos - current_pos
move_distance = move_direction.length()
if move_distance > 0:
move_direction.normalize()
move_amount = min(self.follow_speed * dt, move_distance)
new_pos = current_pos + move_direction * move_amount
self.gameObject.setPos(new_pos)
# 朝向目标
self.gameObject.lookAt(target_pos)
def set_target(self, target):
"""设置跟随目标"""
self.target = target
if target:
self.log(f"设置跟随目标: {target.getName()}")
else:
self.log("清除跟随目标")
def on_destroy(self):
"""脚本销毁时调用"""
self.log("跟随脚本停止")

View File

@ -1,91 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
移动脚本 - 让对象在指定方向上来回移动
"""
from core.script_system import ScriptBase
import math
class MoverScript(ScriptBase):
"""移动脚本类"""
def __init__(self):
super().__init__()
# 移动参数
self.move_distance = 5.0 # 移动距离
self.move_speed = 2.0 # 移动速度 (单位/秒)
self.move_axis = "x" # 移动轴: "x", "y", "z"
# 内部变量
self.start_position = None # 起始位置
self.current_direction = 1 # 当前移动方向: 1或-1
self.current_distance = 0.0 # 当前移动距离
self.is_moving = True # 是否正在移动
def start(self):
"""脚本开始时调用"""
self.log("移动脚本启动!")
self.log(f"移动参数: 距离={self.move_distance}, 速度={self.move_speed}, 轴={self.move_axis}")
# 记录起始位置
self.start_position = self.gameObject.getPos()
self.log(f"起始位置: {self.start_position}")
def update(self, dt):
"""每帧更新"""
if not self.is_moving or self.start_position is None:
return
# 计算移动增量
move_delta = self.move_speed * dt * self.current_direction
self.current_distance += abs(move_delta)
# 检查是否需要改变方向
if self.current_distance >= self.move_distance:
self.current_direction *= -1
self.current_distance = 0.0
# 应用移动
current_pos = self.gameObject.getPos()
new_pos = [current_pos.getX(), current_pos.getY(), current_pos.getZ()]
if self.move_axis == "x":
new_pos[0] += move_delta
elif self.move_axis == "y":
new_pos[1] += move_delta
elif self.move_axis == "z":
new_pos[2] += move_delta
self.gameObject.setPos(new_pos[0], new_pos[1], new_pos[2])
def set_move_parameters(self, distance=None, speed=None, axis=None):
"""设置移动参数"""
if distance is not None:
self.move_distance = distance
if speed is not None:
self.move_speed = speed
if axis is not None and axis in ["x", "y", "z"]:
self.move_axis = axis
self.log(f"移动参数更新: 距离={self.move_distance}, 速度={self.move_speed}, 轴={self.move_axis}")
def toggle_movement(self):
"""切换移动状态"""
self.is_moving = not self.is_moving
status = "恢复" if self.is_moving else "暂停"
self.log(f"移动{status}")
def reset_position(self):
"""重置到起始位置"""
if self.start_position:
self.gameObject.setPos(self.start_position)
self.current_distance = 0.0
self.current_direction = 1
self.log("位置已重置到起始点")
def on_destroy(self):
"""脚本销毁时调用"""
self.log("移动脚本停止")

View File

@ -1,133 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 30.0 # 最大旋转角度(相对于初始角度)
self.direction = 1
self.current_offset = 0.0 # 当前相对于初始角度的偏移
self.initial_angle = None # 模型的初始角度
self.is_rotating = True # 是否正在旋转
def start(self):
"""脚本开始时调用"""
self.log("旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"最大旋转角度: ±{self.max_angle}")
# 记录模型的初始角度
if self.gameObject:
initial_hpr = self.gameObject.getHpr()
self.initial_angle = initial_hpr.getZ() # 记录Z轴的初始角度
self.log(f"模型初始角度: {self.initial_angle}")
self.log(f"旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
else:
self.log("⚠️ 无法获取游戏对象使用默认初始角度0")
self.initial_angle = 0.0
def update(self, dt):
"""每帧更新"""
if not self.is_rotating or self.initial_angle is None:
return
# 计算角度变化量
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_offset += delta_angle
# 如果超出角度范围,则反向并限制在边界
if self.current_offset > self.max_angle:
self.current_offset = self.max_angle
self.direction *= -1
elif self.current_offset < -self.max_angle:
self.current_offset = -self.max_angle
self.direction *= -1
# 计算最终角度(初始角度 + 偏移量)
final_angle = self.initial_angle + self.current_offset
# 设置新的旋转只改变Z轴保持其他不变
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), final_angle, current_hpr.getZ())
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("旋转脚本停止")
# ==================== 控制方法 ====================
def set_max_angle(self, new_max_angle):
"""
设置新的最大旋转角度
Args:
new_max_angle: 新的最大角度值
"""
self.max_angle = new_max_angle
self.log(f"最大旋转角度已设置为: ±{self.max_angle}")
if self.initial_angle is not None:
self.log(f"新的旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
def set_rotation_speed(self, new_speed):
"""
设置新的旋转速度
Args:
new_speed: 新的旋转速度/
"""
self.rotation_speed_y = new_speed
self.log(f"旋转速度已设置为: {self.rotation_speed_y}度/秒")
def pause_rotation(self):
"""暂停旋转"""
self.is_rotating = False
self.log("旋转已暂停")
def resume_rotation(self):
"""恢复旋转"""
self.is_rotating = True
self.log("旋转已恢复")
def reset_to_initial_angle(self):
"""重置到初始角度"""
if self.initial_angle is not None and self.gameObject:
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), self.initial_angle)
self.current_offset = 0.0
self.direction = 1
self.log(f"已重置到初始角度: {self.initial_angle}")
def get_current_info(self):
"""获取当前旋转信息"""
if self.gameObject and self.initial_angle is not None:
current_hpr = self.gameObject.getHpr()
current_angle = current_hpr.getZ()
self.log("=== 当前旋转信息 ===")
self.log(f"初始角度: {self.initial_angle}")
self.log(f"当前角度: {current_angle}")
self.log(f"偏移量: {self.current_offset}")
self.log(f"最大角度: ±{self.max_angle}")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"旋转方向: {'正向' if self.direction > 0 else '反向'}")
self.log(f"旋转状态: {'运行中' if self.is_rotating else '已暂停'}")
self.log("=== 信息结束 ===")
else:
self.log("无法获取旋转信息")

View File

@ -1,133 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 30.0 # 最大旋转角度(相对于初始角度)
self.direction = 1
self.current_offset = 0.0 # 当前相对于初始角度的偏移
self.initial_angle = None # 模型的初始角度
self.is_rotating = True # 是否正在旋转
def start(self):
"""脚本开始时调用"""
self.log("旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"最大旋转角度: ±{self.max_angle}")
# 记录模型的初始角度
if self.gameObject:
initial_hpr = self.gameObject.getHpr()
self.initial_angle = initial_hpr.getZ() # 记录Z轴的初始角度
self.log(f"模型初始角度: {self.initial_angle}")
self.log(f"旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
else:
self.log("⚠️ 无法获取游戏对象使用默认初始角度0")
self.initial_angle = 0.0
def update(self, dt):
"""每帧更新"""
if not self.is_rotating or self.initial_angle is None:
return
# 计算角度变化量
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_offset += delta_angle
# 如果超出角度范围,则反向并限制在边界
if self.current_offset > self.max_angle:
self.current_offset = self.max_angle
self.direction *= -1
elif self.current_offset < -self.max_angle:
self.current_offset = -self.max_angle
self.direction *= -1
# 计算最终角度(初始角度 + 偏移量)
final_angle = self.initial_angle + self.current_offset
# 设置新的旋转只改变Z轴保持其他不变
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), final_angle)
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("旋转脚本停止")
# ==================== 控制方法 ====================
def set_max_angle(self, new_max_angle):
"""
设置新的最大旋转角度
Args:
new_max_angle: 新的最大角度值
"""
self.max_angle = new_max_angle
self.log(f"最大旋转角度已设置为: ±{self.max_angle}")
if self.initial_angle is not None:
self.log(f"新的旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
def set_rotation_speed(self, new_speed):
"""
设置新的旋转速度
Args:
new_speed: 新的旋转速度/
"""
self.rotation_speed_y = new_speed
self.log(f"旋转速度已设置为: {self.rotation_speed_y}度/秒")
def pause_rotation(self):
"""暂停旋转"""
self.is_rotating = False
self.log("旋转已暂停")
def resume_rotation(self):
"""恢复旋转"""
self.is_rotating = True
self.log("旋转已恢复")
def reset_to_initial_angle(self):
"""重置到初始角度"""
if self.initial_angle is not None and self.gameObject:
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), self.initial_angle)
self.current_offset = 0.0
self.direction = 1
self.log(f"已重置到初始角度: {self.initial_angle}")
def get_current_info(self):
"""获取当前旋转信息"""
if self.gameObject and self.initial_angle is not None:
current_hpr = self.gameObject.getHpr()
current_angle = current_hpr.getZ()
self.log("=== 当前旋转信息 ===")
self.log(f"初始角度: {self.initial_angle}")
self.log(f"当前角度: {current_angle}")
self.log(f"偏移量: {self.current_offset}")
self.log(f"最大角度: ±{self.max_angle}")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"旋转方向: {'正向' if self.direction > 0 else '反向'}")
self.log(f"旋转状态: {'运行中' if self.is_rotating else '已暂停'}")
self.log("=== 信息结束 ===")
else:
self.log("无法获取旋转信息")

View File

@ -1,215 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 30.0 # 最大旋转角度(相对于初始角度)
self.direction = 1
self.current_offset = 0.0 # 当前相对于初始角度的偏移
self.initial_angle = None # 模型的初始角度
self.is_rotating = True # 是否正在旋转
# 机器人式停顿参数
self.pause_duration = 0.5 # 停顿时间(秒)
self.current_pause_time = 0.0 # 当前停顿计时
self.is_paused = False # 是否正在停顿
self.robot_mode = True # 是否启用机器人模式
def start(self):
"""脚本开始时调用"""
self.log("机器人旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"最大旋转角度: ±{self.max_angle}")
self.log(f"机器人模式: {'开启' if self.robot_mode else '关闭'}")
if self.robot_mode:
self.log(f"停顿时间: {self.pause_duration}")
# 记录模型的初始角度
if self.gameObject:
initial_hpr = self.gameObject.getHpr()
self.initial_angle = initial_hpr.getY() # 记录Y轴的初始角度Pitch
self.log(f"模型初始角度: {self.initial_angle}")
self.log(f"旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
else:
self.log("⚠️ 无法获取游戏对象使用默认初始角度0")
self.initial_angle = 0.0
def update(self, dt):
"""每帧更新 - 机器人式旋转"""
if not self.is_rotating or self.initial_angle is None:
return
# 如果正在停顿中
if self.is_paused:
self.current_pause_time += dt
if self.current_pause_time >= self.pause_duration:
# 停顿结束,继续旋转
self.is_paused = False
self.current_pause_time = 0.0
self.log(f"停顿结束,继续旋转,方向: {'正向' if self.direction > 0 else '反向'}")
return
# 计算角度变化量
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_offset += delta_angle
# 检查是否到达边界
reached_boundary = False
if self.current_offset > self.max_angle:
self.current_offset = self.max_angle
self.direction *= -1
reached_boundary = True
elif self.current_offset < -self.max_angle:
self.current_offset = -self.max_angle
self.direction *= -1
reached_boundary = True
# 如果到达边界且启用机器人模式,开始停顿
if reached_boundary and self.robot_mode:
self.is_paused = True
self.current_pause_time = 0.0
self.log(f"到达边界 ({self.current_offset}°),开始停顿 {self.pause_duration}")
# 计算最终角度(初始角度 + 偏移量)
final_angle = self.initial_angle + self.current_offset
# 设置新的旋转只改变Y轴保持其他不变
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(final_angle, current_hpr.getY(), current_hpr.getZ())
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("机器人旋转脚本停止")
# ==================== 机器人模式控制方法 ====================
def set_robot_mode(self, enabled=True):
"""
启用或禁用机器人模式
Args:
enabled: 是否启用机器人模式
"""
self.robot_mode = enabled
self.log(f"机器人模式: {'开启' if enabled else '关闭'}")
if not enabled:
self.is_paused = False # 如果禁用机器人模式,立即结束停顿
def set_pause_duration(self, duration):
"""
设置停顿时间
Args:
duration: 停顿时间
"""
self.pause_duration = max(0.1, duration) # 最小0.1秒
self.log(f"停顿时间已设置为: {self.pause_duration}")
def set_max_angle(self, new_max_angle):
"""
设置新的最大旋转角度
Args:
new_max_angle: 新的最大角度值
"""
self.max_angle = new_max_angle
self.log(f"最大旋转角度已设置为: ±{self.max_angle}")
if self.initial_angle is not None:
self.log(f"新的旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
def set_rotation_speed(self, new_speed):
"""
设置新的旋转速度
Args:
new_speed: 新的旋转速度/
"""
self.rotation_speed_y = new_speed
self.log(f"旋转速度已设置为: {self.rotation_speed_y}度/秒")
def pause_rotation(self):
"""暂停旋转"""
self.is_rotating = False
self.log("旋转已暂停")
def resume_rotation(self):
"""恢复旋转"""
self.is_rotating = True
self.is_paused = False # 同时结束停顿状态
self.log("旋转已恢复")
def reset_to_initial_angle(self):
"""重置到初始角度"""
if self.initial_angle is not None and self.gameObject:
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), self.initial_angle, current_hpr.getZ())
self.current_offset = 0.0
self.direction = 1
self.is_paused = False
self.current_pause_time = 0.0
self.log(f"已重置到初始角度: {self.initial_angle}")
def get_current_info(self):
"""获取当前旋转信息"""
if self.gameObject and self.initial_angle is not None:
current_hpr = self.gameObject.getHpr()
current_angle = current_hpr.getY()
self.log("=== 机器人旋转信息 ===")
self.log(f"初始角度: {self.initial_angle}")
self.log(f"当前角度: {current_angle}")
self.log(f"偏移量: {self.current_offset}")
self.log(f"最大角度: ±{self.max_angle}")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"旋转方向: {'正向' if self.direction > 0 else '反向'}")
self.log(f"旋转状态: {'运行中' if self.is_rotating else '已暂停'}")
self.log(f"机器人模式: {'开启' if self.robot_mode else '关闭'}")
if self.robot_mode:
self.log(f"停顿时间: {self.pause_duration}")
self.log(f"当前状态: {'停顿中' if self.is_paused else '旋转中'}")
if self.is_paused:
remaining_time = self.pause_duration - self.current_pause_time
self.log(f"剩余停顿时间: {remaining_time:.1f}")
self.log("=== 信息结束 ===")
else:
self.log("无法获取旋转信息")
# ==================== 预设配置方法 ====================
def set_slow_robot_mode(self):
"""预设:慢速机器人模式"""
self.set_rotation_speed(15.0)
self.set_pause_duration(1.0)
self.set_robot_mode(True)
self.log("已设置为慢速机器人模式")
def set_fast_robot_mode(self):
"""预设:快速机器人模式"""
self.set_rotation_speed(45.0)
self.set_pause_duration(0.3)
self.set_robot_mode(True)
self.log("已设置为快速机器人模式")
def set_smooth_mode(self):
"""预设:平滑模式(非机器人)"""
self.set_robot_mode(False)
self.set_rotation_speed(30.0)
self.log("已设置为平滑旋转模式")

View File

@ -1,215 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 25.0 # 最大旋转角度(相对于初始角度)
self.direction = 1
self.current_offset = 0.0 # 当前相对于初始角度的偏移
self.initial_angle = None # 模型的初始角度
self.is_rotating = True # 是否正在旋转
# 机器人式停顿参数
self.pause_duration = 0.5 # 停顿时间(秒)
self.current_pause_time = 0.0 # 当前停顿计时
self.is_paused = False # 是否正在停顿
self.robot_mode = True # 是否启用机器人模式
def start(self):
"""脚本开始时调用"""
self.log("机器人旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"最大旋转角度: ±{self.max_angle}")
self.log(f"机器人模式: {'开启' if self.robot_mode else '关闭'}")
if self.robot_mode:
self.log(f"停顿时间: {self.pause_duration}")
# 记录模型的初始角度
if self.gameObject:
initial_hpr = self.gameObject.getHpr()
self.initial_angle = initial_hpr.getY() # 记录Y轴的初始角度Pitch
self.log(f"模型初始角度: {self.initial_angle}")
self.log(f"旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
else:
self.log("⚠️ 无法获取游戏对象使用默认初始角度0")
self.initial_angle = 0.0
def update(self, dt):
"""每帧更新 - 机器人式旋转"""
if not self.is_rotating or self.initial_angle is None:
return
# 如果正在停顿中
if self.is_paused:
self.current_pause_time += dt
if self.current_pause_time >= self.pause_duration:
# 停顿结束,继续旋转
self.is_paused = False
self.current_pause_time = 0.0
self.log(f"停顿结束,继续旋转,方向: {'正向' if self.direction > 0 else '反向'}")
return
# 计算角度变化量
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_offset += delta_angle
# 检查是否到达边界
reached_boundary = False
if self.current_offset > self.max_angle:
self.current_offset = self.max_angle
self.direction *= -1
reached_boundary = True
elif self.current_offset < -self.max_angle:
self.current_offset = -self.max_angle
self.direction *= -1
reached_boundary = True
# 如果到达边界且启用机器人模式,开始停顿
if reached_boundary and self.robot_mode:
self.is_paused = True
self.current_pause_time = 0.0
self.log(f"到达边界 ({self.current_offset}°),开始停顿 {self.pause_duration}")
# 计算最终角度(初始角度 + 偏移量)
final_angle = self.initial_angle + self.current_offset
# 设置新的旋转只改变Y轴保持其他不变
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), final_angle, current_hpr.getZ())
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("机器人旋转脚本停止")
# ==================== 机器人模式控制方法 ====================
def set_robot_mode(self, enabled=True):
"""
启用或禁用机器人模式
Args:
enabled: 是否启用机器人模式
"""
self.robot_mode = enabled
self.log(f"机器人模式: {'开启' if enabled else '关闭'}")
if not enabled:
self.is_paused = False # 如果禁用机器人模式,立即结束停顿
def set_pause_duration(self, duration):
"""
设置停顿时间
Args:
duration: 停顿时间
"""
self.pause_duration = max(0.1, duration) # 最小0.1秒
self.log(f"停顿时间已设置为: {self.pause_duration}")
def set_max_angle(self, new_max_angle):
"""
设置新的最大旋转角度
Args:
new_max_angle: 新的最大角度值
"""
self.max_angle = new_max_angle
self.log(f"最大旋转角度已设置为: ±{self.max_angle}")
if self.initial_angle is not None:
self.log(f"新的旋转范围: {self.initial_angle - self.max_angle}° 到 {self.initial_angle + self.max_angle}°")
def set_rotation_speed(self, new_speed):
"""
设置新的旋转速度
Args:
new_speed: 新的旋转速度/
"""
self.rotation_speed_y = new_speed
self.log(f"旋转速度已设置为: {self.rotation_speed_y}度/秒")
def pause_rotation(self):
"""暂停旋转"""
self.is_rotating = False
self.log("旋转已暂停")
def resume_rotation(self):
"""恢复旋转"""
self.is_rotating = True
self.is_paused = False # 同时结束停顿状态
self.log("旋转已恢复")
def reset_to_initial_angle(self):
"""重置到初始角度"""
if self.initial_angle is not None and self.gameObject:
current_hpr = self.gameObject.getHpr()
self.gameObject.setHpr(current_hpr.getX(), self.initial_angle, current_hpr.getZ())
self.current_offset = 0.0
self.direction = 1
self.is_paused = False
self.current_pause_time = 0.0
self.log(f"已重置到初始角度: {self.initial_angle}")
def get_current_info(self):
"""获取当前旋转信息"""
if self.gameObject and self.initial_angle is not None:
current_hpr = self.gameObject.getHpr()
current_angle = current_hpr.getY()
self.log("=== 机器人旋转信息 ===")
self.log(f"初始角度: {self.initial_angle}")
self.log(f"当前角度: {current_angle}")
self.log(f"偏移量: {self.current_offset}")
self.log(f"最大角度: ±{self.max_angle}")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
self.log(f"旋转方向: {'正向' if self.direction > 0 else '反向'}")
self.log(f"旋转状态: {'运行中' if self.is_rotating else '已暂停'}")
self.log(f"机器人模式: {'开启' if self.robot_mode else '关闭'}")
if self.robot_mode:
self.log(f"停顿时间: {self.pause_duration}")
self.log(f"当前状态: {'停顿中' if self.is_paused else '旋转中'}")
if self.is_paused:
remaining_time = self.pause_duration - self.current_pause_time
self.log(f"剩余停顿时间: {remaining_time:.1f}")
self.log("=== 信息结束 ===")
else:
self.log("无法获取旋转信息")
# ==================== 预设配置方法 ====================
def set_slow_robot_mode(self):
"""预设:慢速机器人模式"""
self.set_rotation_speed(15.0)
self.set_pause_duration(1.0)
self.set_robot_mode(True)
self.log("已设置为慢速机器人模式")
def set_fast_robot_mode(self):
"""预设:快速机器人模式"""
self.set_rotation_speed(45.0)
self.set_pause_duration(0.3)
self.set_robot_mode(True)
self.log("已设置为快速机器人模式")
def set_smooth_mode(self):
"""预设:平滑模式(非机器人)"""
self.set_robot_mode(False)
self.set_rotation_speed(30.0)
self.log("已设置为平滑旋转模式")

View File

@ -1,56 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.max_angle = 15.0
self.direction = 1
self.current_angle = 0.0
self.is_rotating = True # 是否正在旋转
def start(self):
"""脚本开始时调用"""
self.log("旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
def update(self, dt):
"""每帧更新"""
delta_angle = self.rotation_speed_y * dt * self.direction
self.current_angle += delta_angle
# 如果超出角度范围,则反向
if self.current_angle > self.max_angle:
self.current_angle = self.max_angle
self.direction *= -1
elif self.current_angle < -self.max_angle:
self.current_angle = -self.max_angle
self.direction *= -1
# 设置新的旋转只改变Z轴保持其他不变
base_hpr = self.gameObject.getHpr()
new_r = self.current_angle
self.gameObject.setHpr(base_hpr.getX(), base_hpr.getY(), new_r)
# if not self.is_rotating:
# return
#
# # 获取当前旋转并应用增量
# current_hpr = self.gameObject.getHpr()
# new_r = current_hpr.getZ() + self.rotation_speed_y * dt
# self.gameObject.setHpr(current_hpr.getX(), current_hpr.getY(), new_r)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("旋转脚本停止")

View File

@ -1,39 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
旋转脚本 - 让对象持续旋转
"""
from core.script_system import ScriptBase
class RotatorScript(ScriptBase):
"""旋转脚本类"""
def __init__(self):
super().__init__()
self.rotation_speed_y = 30.0 # Y轴旋转速度 (度/秒)
self.is_rotating = True # 是否正在旋转
def start(self):
"""脚本开始时调用"""
self.log("旋转脚本启动!")
self.log(f"旋转速度: {self.rotation_speed_y}度/秒")
def update(self, dt):
# 检查 gameObject 是否存在且不为空
if not self.gameObject or self.gameObject.isEmpty():
print("RotatorScript: gameObject is empty or None, skipping update")
return
"""每帧更新"""
if not self.is_rotating:
return
# 获取当前旋转并应用增量
current_hpr = self.gameObject.getHpr()
new_h = current_hpr.getX() + self.rotation_speed_y * dt
self.gameObject.setHpr(new_h, current_hpr.getY(), current_hpr.getZ())
def on_destroy(self):
"""脚本销毁时调用"""
self.log("旋转脚本停止")

View File

@ -1,91 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
缩放脚本 - 让对象产生呼吸般的缩放效果
"""
from core.script_system import ScriptBase
import math
class ScalerScript(ScriptBase):
"""缩放脚本类"""
def __init__(self):
super().__init__()
# 缩放参数
self.base_scale = 1.0 # 基础缩放
self.scale_amplitude = 0.3 # 缩放幅度
self.scale_speed = 2.0 # 缩放速度 (周期/秒)
self.uniform_scale = True # 是否统一缩放(所有轴)
# 内部变量
self.time_accumulator = 0.0 # 时间累积器
self.original_scale = None # 原始缩放
self.is_scaling = True # 是否正在缩放
def start(self):
"""脚本开始时调用"""
self.log("缩放脚本启动!")
self.log(f"缩放参数: 基础={self.base_scale}, 幅度={self.scale_amplitude}, 速度={self.scale_speed}")
# 记录原始缩放
self.original_scale = self.gameObject.getScale()
self.log(f"原始缩放: {self.original_scale}")
def update(self, dt):
"""每帧更新"""
if not self.is_scaling:
return
# 累积时间
self.time_accumulator += dt
# 计算正弦波缩放值
sine_value = math.sin(self.time_accumulator * self.scale_speed * 2 * math.pi)
scale_factor = self.base_scale + (self.scale_amplitude * sine_value)
# 应用缩放
if self.uniform_scale:
# 统一缩放
self.gameObject.setScale(scale_factor)
else:
# 非统一缩放仅Z轴
current_scale = self.gameObject.getScale()
self.gameObject.setScale(current_scale.getX(), current_scale.getY(), scale_factor)
def set_scale_parameters(self, base=None, amplitude=None, speed=None, uniform=None):
"""设置缩放参数"""
if base is not None:
self.base_scale = base
if amplitude is not None:
self.scale_amplitude = amplitude
if speed is not None:
self.scale_speed = speed
if uniform is not None:
self.uniform_scale = uniform
self.log(f"缩放参数更新: 基础={self.base_scale}, 幅度={self.scale_amplitude}, 速度={self.scale_speed}")
def toggle_scaling(self):
"""切换缩放状态"""
self.is_scaling = not self.is_scaling
status = "恢复" if self.is_scaling else "暂停"
self.log(f"缩放{status}")
def reset_scale(self):
"""重置到原始缩放"""
if self.original_scale:
self.gameObject.setScale(self.original_scale)
self.time_accumulator = 0.0
self.log("缩放已重置到原始值")
def pulse_once(self):
"""执行一次脉冲缩放"""
self.time_accumulator = 0.0
self.log("执行脉冲缩放")
def on_destroy(self):
"""脚本销毁时调用"""
self.log("缩放脚本停止")

View File

@ -1,41 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
TestMover - 移动脚本
"""
from core.script_system import ScriptBase
class Testmover(ScriptBase):
"""移动脚本类"""
def __init__(self):
super().__init__()
self.speed = 5.0 # 移动速度
self.direction = [1, 0, 0] # 移动方向
def start(self):
"""脚本开始时调用"""
self.log("移动脚本开始运行!")
def update(self, dt):
"""每帧更新"""
if self.transform:
# 计算移动偏移
offset_x = self.direction[0] * self.speed * dt
offset_y = self.direction[1] * self.speed * dt
offset_z = self.direction[2] * self.speed * dt
# 更新位置
current_pos = self.transform.getPos()
new_pos = (
current_pos.x + offset_x,
current_pos.y + offset_y,
current_pos.z + offset_z
)
self.transform.setPos(*new_pos)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("移动脚本被销毁")

View File

@ -1,28 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
TestRotator - 自定义脚本
"""
from core.script_system import ScriptBase
class Testrotator(ScriptBase):
"""自定义脚本类"""
def __init__(self):
super().__init__()
# 在这里初始化您的变量
def start(self):
"""脚本开始时调用"""
self.log("脚本开始运行!")
def update(self, dt):
"""每帧更新"""
# 在这里编写更新逻辑
pass
def on_destroy(self):
"""脚本销毁时调用"""
self.log("脚本被销毁")

View File

@ -1,28 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
TestScaler - 自定义脚本
"""
from core.script_system import ScriptBase
class Testscaler(ScriptBase):
"""自定义脚本类"""
def __init__(self):
super().__init__()
# 在这里初始化您的变量
def start(self):
"""脚本开始时调用"""
self.log("脚本开始运行!")
def update(self, dt):
"""每帧更新"""
# 在这里编写更新逻辑
pass
def on_destroy(self):
"""脚本销毁时调用"""
self.log("脚本被销毁")

View File

@ -1,28 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
a - 自定义脚本
"""
from core.script_system import ScriptBase
class A(ScriptBase):
"""自定义脚本类"""
def __init__(self):
super().__init__()
# 在这里初始化您的变量
def start(self):
"""脚本开始时调用"""
self.log("脚本开始运行!")
def update(self, dt):
"""每帧更新"""
# 在这里编写更新逻辑
pass
def on_destroy(self):
"""脚本销毁时调用"""
self.log("脚本被销毁")

View File

@ -1,47 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
示例脚本 - 演示如何编写脚本
"""
from core.script_system import ScriptBase
class ExampleScript(ScriptBase):
"""示例脚本类"""
def __init__(self):
super().__init__()
self.counter = 0
self.rotation_speed = 30.0 # 度/秒
def start(self):
"""脚本开始时调用"""
self.log("示例脚本开始运行!")
self.log(f"挂载到对象: {self.gameObject.getName()}")
def update(self, dt):
"""每帧更新"""
self.counter += 1
# 每60帧输出一次信息
if self.counter % 60 == 0:
self.log(f"运行了 {self.counter}")
# 让对象旋转
if self.transform:
current_h = self.transform.getH()
new_h = current_h + self.rotation_speed * dt
self.transform.setH(new_h)
def on_destroy(self):
"""脚本销毁时调用"""
self.log("示例脚本被销毁")
def on_enable(self):
"""脚本启用时调用"""
self.log("示例脚本被启用")
def on_disable(self):
"""脚本禁用时调用"""
self.log("示例脚本被禁用")

View File

@ -1,28 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
test_quick_script - 自定义脚本
"""
from core.script_system import ScriptBase
class TestQuickScript(ScriptBase):
"""自定义脚本类"""
def __init__(self):
super().__init__()
# 在这里初始化您的变量
def start(self):
"""脚本开始时调用"""
self.log("脚本开始运行!")
def update(self, dt):
"""每帧更新"""
# 在这里编写更新逻辑
pass
def on_destroy(self):
"""脚本销毁时调用"""
self.log("脚本被销毁")

View File

@ -1,534 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""EG packaged project runtime template."""
from __future__ import annotations
import importlib
import json
import os
import sys
import traceback
from direct.actor.Actor import Actor
from direct.showbase.ShowBase import ShowBase
from panda3d.core import (
CardMaker,
Filename,
MovieTexture,
Point3,
TextNode,
Texture,
TransparencyAttrib,
Vec3,
load_prc_file_data,
)
PROJECT_NAME = "111"
def _bootstrap_paths():
if getattr(sys, "frozen", False):
project_root = os.path.dirname(sys.executable)
else:
project_root = os.path.dirname(os.path.abspath(__file__))
os.chdir(project_root)
search_paths = [
project_root,
os.path.join(project_root, "third_party"),
os.path.join(project_root, "RenderPipelineFile"),
]
for path in search_paths:
if os.path.isdir(path) and path not in sys.path:
sys.path.insert(0, path)
return project_root
PROJECT_ROOT = _bootstrap_paths()
class MainApp(ShowBase):
def __init__(self):
self.project_path = PROJECT_ROOT
self.gui_elements = []
self.chinese_font = None
self.script_manager = None
self.render_pipeline = None
load_prc_file_data(
"",
f"""
win-size 1380 750
window-title {PROJECT_NAME}
sync-video false
show-frame-rate-meter false
support-threads false
""",
)
from rpcore import RenderPipeline
self.render_pipeline = RenderPipeline()
self.render_pipeline.pre_showbase_init()
ShowBase.__init__(self)
self.render_pipeline.create(self)
self.render_pipeline._showbase.camera = self.render_pipeline._showbase.cam
self.disableMouse()
self._load_font()
self._init_script_manager()
self.load_full_scene()
self.load_gui_from_json()
def _init_script_manager(self):
script_module = importlib.import_module("core.script_system")
self.script_manager = script_module.ScriptManager(self)
self.script_manager.hot_reload_enabled = False
scripts_dir = self.get_resource_path("scripts")
if hasattr(self.script_manager, "set_scripts_directory"):
self.script_manager.set_scripts_directory(
scripts_dir,
create=False,
reload_scripts=False,
)
self.script_manager.start_system()
self.script_manager.set_hot_reload_enabled(False)
def _load_font(self):
font_candidates = [
"C:/Windows/Fonts/msyh.ttc",
"C:/Windows/Fonts/simhei.ttf",
]
for font_path in font_candidates:
if os.path.exists(font_path):
try:
self.chinese_font = self.loader.loadFont(font_path)
if self.chinese_font:
print(f"✓ 中文字体加载成功: {font_path}")
return
except Exception:
continue
print("⚠ 未找到可用中文字体,继续使用默认字体")
def get_chinese_font(self):
return self.chinese_font
def get_resource_path(self, relative_path):
return os.path.normpath(os.path.join(PROJECT_ROOT, relative_path))
def _resolve_media_path(self, relative_path):
if not relative_path:
return ""
if os.path.isabs(relative_path):
return relative_path
return self.get_resource_path(relative_path.replace("/", os.sep))
def load_full_scene(self):
scene_file = self.get_resource_path("scene.bam")
if not os.path.exists(scene_file):
print(f"⚠ 未找到场景文件: {scene_file}")
return
scene = self.loader.loadModel(Filename.fromOsSpecific(scene_file))
if not scene:
print("⚠ 场景文件加载失败")
return
scene.reparentTo(self.render)
self.render_pipeline.prepare_scene(scene)
self.process_scene_elements(scene)
print("✓ 场景加载完成")
def process_scene_elements(self, root_node):
processed_lights = set()
def walk(node_path):
self._apply_user_visibility(node_path)
if node_path.hasTag("scripts_info"):
try:
scripts_info = json.loads(node_path.getTag("scripts_info"))
self.process_scripts(node_path, scripts_info)
except Exception as e:
print(f"处理节点脚本失败 {node_path.getName()}: {e}")
if node_path.hasTag("light_type") and node_path not in processed_lights:
if node_path.hasTag("is_auxiliary_light") and node_path.getTag("is_auxiliary_light").lower() == "true":
return
light_type = node_path.getTag("light_type")
if light_type == "spot_light":
self._recreate_spot_light(node_path)
elif light_type == "point_light":
self._recreate_point_light(node_path)
processed_lights.add(node_path)
for child in node_path.getChildren():
walk(child)
walk(root_node)
def _apply_user_visibility(self, node_path):
if not node_path.hasTag("user_visible"):
return
user_visible = node_path.getTag("user_visible").lower() == "true"
node_path.setPythonTag("user_visible", user_visible)
if user_visible:
node_path.show()
else:
node_path.hide()
def _recreate_spot_light(self, light_node):
try:
from rpcore import SpotLight
light = SpotLight()
light.direction = Vec3(0, 0, -1)
light.fov = float(light_node.getTag("light_fov")) if light_node.hasTag("light_fov") else 70.0
light.energy = float(light_node.getTag("light_energy")) if light_node.hasTag("light_energy") else 5000.0
light.radius = float(light_node.getTag("light_radius")) if light_node.hasTag("light_radius") else 1000.0
light.casts_shadows = True
light.shadow_map_resolution = 256
light.setPos(light_node.getPos())
self.render_pipeline.add_light(light)
except Exception as e:
print(f"创建聚光灯失败 {light_node.getName()}: {e}")
def _recreate_point_light(self, light_node):
try:
from rpcore import PointLight
light = PointLight()
light.energy = float(light_node.getTag("light_energy")) if light_node.hasTag("light_energy") else 5000.0
light.radius = float(light_node.getTag("light_radius")) if light_node.hasTag("light_radius") else 1000.0
light.inner_radius = 0.4
light.casts_shadows = True
light.shadow_map_resolution = 256
light.setPos(light_node.getPos())
self.render_pipeline.add_light(light)
except Exception as e:
print(f"创建点光源失败 {light_node.getName()}: {e}")
def process_scripts(self, node_path, script_info_list):
if not self.script_manager:
return
for script_info in script_info_list or []:
script_name = str(script_info.get("name", "") or "").strip()
if not script_name:
continue
try:
if script_name not in self.script_manager.loader.script_classes:
script_path = ""
if hasattr(self.script_manager, "resolve_script_path"):
script_path = self.script_manager.resolve_script_path(script_info)
if script_path:
self.script_manager.load_script_from_file(script_path)
script_component = self.script_manager.add_script_to_object(node_path, script_name)
if script_component:
print(f"✓ 脚本 {script_name} 已挂载到 {node_path.getName()}")
else:
print(f"⚠ 脚本 {script_name} 挂载失败")
except Exception as e:
print(f"挂载脚本失败 {script_name}: {e}")
def load_gui_from_json(self):
gui_json_path = self.get_resource_path(os.path.join("gui", "gui_elements.json"))
if not os.path.exists(gui_json_path):
return
with open(gui_json_path, "r", encoding="utf-8") as f:
content = f.read().strip()
if not content:
return
gui_data = json.loads(content)
self.create_gui_elements(gui_data)
def create_gui_elements(self, element_data):
processed_names = set()
element_original_data = {}
for index, gui_info in enumerate(element_data or []):
name = gui_info.get("name", f"gui_element_{index}")
element_original_data[name] = {
"scale": gui_info.get("scale", [1, 1, 1]),
"position": gui_info.get("position", [0, 0, 0]),
"parent_name": gui_info.get("parent_name"),
}
for index, gui_info in enumerate(element_data or []):
try:
gui_type = gui_info.get("type", "unknown")
name = gui_info.get("name", f"gui_element_{index}")
if name in processed_names:
continue
processed_names.add(name)
position = list(gui_info.get("position", [0, 0, 0]))
scale = list(gui_info.get("scale", [1, 1, 1]))
parent_name = gui_info.get("parent_name")
if parent_name and parent_name in element_original_data:
parent_scale = element_original_data[parent_name]["scale"]
for i in range(min(len(position), len(parent_scale))):
position[i] *= parent_scale[i]
for i in range(min(len(scale), len(parent_scale))):
scale[i] *= parent_scale[i]
text = gui_info.get("text", "")
image_path = self._resolve_media_path(gui_info.get("image_path", ""))
video_path = self._resolve_media_path(gui_info.get("video_path", ""))
new_element = None
if gui_type == "3d_text":
new_element = self.create_gui_3d_text(tuple(position), text, scale[0] if scale else 1.0)
elif gui_type == "3d_image":
new_element = self.create_gui_3d_image(tuple(position), image_path, scale)
elif gui_type == "button":
new_element = self.create_gui_button(tuple(position), text, scale[0] if scale else 1.0)
elif gui_type == "label":
new_element = self.create_gui_label(tuple(position), text, scale[0] if scale else 1.0)
elif gui_type == "entry":
new_element = self.create_gui_entry(tuple(position), text, scale[0] if scale else 1.0)
elif gui_type == "2d_image":
new_element = self.create_gui_2d_image(tuple(position), image_path, scale)
elif gui_type == "video_screen":
new_element = self.create_gui_video_screen(tuple(position), scale, video_path)
elif gui_type == "2d_video_screen":
new_element = self.create_gui_2d_video_screen(tuple(position), scale, video_path)
if not new_element:
continue
self._apply_gui_metadata(new_element, gui_info, gui_type, text, image_path, video_path)
self.gui_elements.append(new_element)
if gui_info.get("scripts"):
self.process_scripts(new_element, gui_info["scripts"])
except Exception as e:
print(f"重建 GUI 元素失败: {e}")
traceback.print_exc()
def _apply_gui_metadata(self, node, gui_info, gui_type, text, image_path, video_path):
try:
name = gui_info.get("name")
if name and hasattr(node, "setName"):
node.setName(name)
except Exception:
pass
if hasattr(node, "setTag"):
node.setTag("gui_type", gui_type)
node.setTag("is_gui_element", "true")
if text:
node.setTag("gui_text", str(text))
if image_path:
node.setTag("gui_image_path", str(image_path))
if video_path:
node.setTag("video_path", str(video_path))
for key, value in (gui_info.get("tags") or {}).items():
try:
node.setTag(str(key), str(value))
except Exception:
continue
user_visible = bool(gui_info.get("user_visible", True))
if hasattr(node, "setPythonTag"):
node.setPythonTag("user_visible", user_visible)
if hasattr(node, "show") and hasattr(node, "hide"):
if user_visible:
node.show()
else:
node.hide()
def create_gui_button(self, pos=(0, 0, 0), text="按钮", size=0.1):
from direct.gui.DirectGui import DirectButton
return DirectButton(
text=text,
pos=(pos[0], pos[1], pos[2]),
scale=size,
frameColor=(0.2, 0.6, 0.8, 1),
text_font=self.get_chinese_font() or None,
rolloverSound=None,
clickSound=None,
parent=self.aspect2d,
command=None,
)
def create_gui_label(self, pos=(0, 0, 0), text="标签", size=0.08):
from direct.gui.DirectGui import DirectLabel
return DirectLabel(
text=text,
pos=(pos[0], pos[1], pos[2]),
scale=size,
frameColor=(0, 0, 0, 0),
text_fg=(1, 1, 1, 1),
text_font=self.get_chinese_font() or None,
text_align=TextNode.ACenter,
text_mayChange=True,
parent=self.aspect2d,
)
def create_gui_entry(self, pos=(0, 0, 0), placeholder="输入文本...", size=0.08):
from direct.gui.DirectGui import DirectEntry
return DirectEntry(
text="",
pos=(pos[0], pos[1], pos[2]),
scale=size,
command=self.on_gui_entry_submit,
initialText=placeholder,
numLines=1,
width=12,
focus=0,
frameColor=(0, 0, 0, 0),
text_fg=(1, 1, 1, 1),
text_font=self.get_chinese_font() or None,
text_align=TextNode.ACenter,
text_mayChange=True,
parent=self.aspect2d,
rolloverSound=None,
clickSound=None,
suppressKeys=True,
suppressMouse=True,
)
def on_gui_entry_submit(self, text, *_args):
print(f"GUI 输入框提交: {text}")
def create_gui_2d_image(self, pos=(0, 0, 0), image_path="", size=(1, 1, 1)):
if isinstance(size, (list, tuple)) and len(size) >= 3:
width_scale = float(size[0]) * 0.2
height_scale = float(size[2]) * 0.2
else:
scalar = float(size) if isinstance(size, (int, float)) else 0.2
width_scale = scalar * 0.1
height_scale = width_scale
cm = CardMaker("gui_2d_image")
cm.setFrame(-width_scale, width_scale, -height_scale, height_scale)
image_node = self.aspect2d.attachNewNode(cm.generate())
image_node.setPos(pos)
image_node.setBin("fixed", 0)
image_node.setDepthWrite(False)
image_node.setDepthTest(False)
image_node.setTransparency(TransparencyAttrib.MAlpha)
if image_path:
texture = self.loader.loadTexture(image_path)
if texture:
image_node.setTexture(texture, 1)
return image_node
def create_gui_3d_text(self, pos=(0, 0, 0), text="3D文本", size=0.5):
text_node = TextNode("gui_3d_text")
text_node.setText(text)
text_node.setAlign(TextNode.ACenter)
if self.get_chinese_font():
text_node.setFont(self.get_chinese_font())
text_np = self.render.attachNewNode(text_node)
text_np.setPos(Vec3(pos[0], pos[1], pos[2]))
text_np.setScale(size)
text_np.setBin("fixed", 40)
text_np.setDepthWrite(False)
return text_np
def create_gui_3d_image(self, pos=(0, 0, 0), image_path="", size=(1, 1, 1)):
if isinstance(size, (list, tuple)) and len(size) >= 3:
width_scale = float(size[0])
height_scale = float(size[2])
else:
width_scale = float(size) if isinstance(size, (int, float)) else 1.0
height_scale = width_scale
cm = CardMaker("gui_3d_image")
cm.setFrame(-width_scale, width_scale, -height_scale, height_scale)
image_node = self.render.attachNewNode(cm.generate())
image_node.setPos(pos)
image_node.setTransparency(TransparencyAttrib.MAlpha)
if image_path:
texture = self.loader.loadTexture(image_path)
if texture:
image_node.setTexture(texture, 1)
return image_node
def _load_movie_texture(self, name, video_path):
if not video_path:
return None
movie_texture = MovieTexture(name)
if not movie_texture.read(Filename.fromOsSpecific(video_path)):
print(f"⚠ 无法加载视频: {video_path}")
return None
movie_texture.play()
return movie_texture
def create_gui_video_screen(self, pos=(0, 0, 0), size=(1, 1, 1), video_path=""):
if isinstance(size, (list, tuple)) and len(size) >= 3:
width_scale = float(size[0])
height_scale = float(size[2])
else:
width_scale = float(size) if isinstance(size, (int, float)) else 1.0
height_scale = width_scale
cm = CardMaker("gui_video_screen")
cm.setFrame(-width_scale, width_scale, -height_scale, height_scale)
video_node = self.render.attachNewNode(cm.generate())
video_node.setPos(pos)
video_node.setTransparency(TransparencyAttrib.MAlpha)
movie_texture = self._load_movie_texture("gui_video_texture_3d", video_path)
if movie_texture:
video_node.setTexture(movie_texture, 1)
return video_node
def create_gui_2d_video_screen(self, pos=(0, 0, 0), size=(1, 1, 1), video_path=""):
if isinstance(size, (list, tuple)) and len(size) >= 3:
width_scale = float(size[0]) * 0.2
height_scale = float(size[2]) * 0.2
else:
scalar = float(size) if isinstance(size, (int, float)) else 0.2
width_scale = scalar * 0.1
height_scale = width_scale
cm = CardMaker("gui_2d_video_screen")
cm.setFrame(-width_scale, width_scale, -height_scale, height_scale)
video_node = self.aspect2d.attachNewNode(cm.generate())
video_node.setPos(pos)
video_node.setTransparency(TransparencyAttrib.MAlpha)
video_node.setDepthWrite(False)
video_node.setDepthTest(False)
movie_texture = self._load_movie_texture("gui_video_texture_2d", video_path)
if movie_texture:
video_node.setTexture(movie_texture, 1)
return video_node
def play_model_animation(self):
actors = self.render.findAllMatches("**/+ActorNode")
for actor_np in actors:
actor_node = actor_np.node()
if isinstance(actor_node, Actor):
anim_names = actor_node.getAnimNames()
if anim_names:
actor_node.loop(anim_names[0])
if __name__ == "__main__":
try:
app = MainApp()
app.run()
except Exception as e:
print(f"应用程序启动失败: {e}")
traceback.print_exc()

View File

@ -36,14 +36,14 @@ Collapsed=0
DockId=0x00000007,0
[Window][属性面板]
Pos=1502,20
Pos=1504,20
Size=346,996
Collapsed=0
DockId=0x00000002,0
[Window][控制台]
Pos=341,617
Size=1159,399
Size=1161,399
Collapsed=0
DockId=0x00000006,1
@ -59,7 +59,7 @@ Collapsed=0
[Window][WindowOverViewport_11111111]
Pos=0,20
Size=1848,996
Size=1850,996
Collapsed=0
[Window][测试窗口1]
@ -83,12 +83,12 @@ Size=400,300
Collapsed=0
[Window][选择路径]
Pos=624,258
Pos=625,258
Size=600,500
Collapsed=0
[Window][打开项目]
Pos=674,308
Pos=675,308
Size=500,400
Collapsed=0
@ -99,7 +99,7 @@ Collapsed=0
[Window][资源管理器]
Pos=341,617
Size=1159,399
Size=1161,399
Collapsed=0
DockId=0x00000006,0
@ -203,7 +203,6 @@ Collapsed=0
Pos=1438,20
Size=610,748
Collapsed=0
DockId=0x00000005,2
[Window][项目另存为]
Pos=794,432
@ -226,7 +225,7 @@ Size=460,260
Collapsed=0
[Docking][Data]
DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=1848,996 Split=X
DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=1850,996 Split=X
DockNode ID=0x00000001 Parent=0x08BD597D SizeRef=2212,1012 Split=X
DockNode ID=0x00000007 Parent=0x00000001 SizeRef=339,1084 Selected=0xE0015051
DockNode ID=0x00000008 Parent=0x00000001 SizeRef=1871,1084 Split=Y

180
packaging/eg_editor.spec Normal file
View File

@ -0,0 +1,180 @@
# -*- mode: python ; coding: utf-8 -*-
import importlib.machinery
import importlib.util
import sys
from pathlib import Path
from PyInstaller.utils.hooks import collect_dynamic_libs, collect_submodules
def _has_module(module_name):
try:
return importlib.util.find_spec(module_name) is not None
except Exception:
return False
def _safe_collect_submodules(package_name):
try:
return collect_submodules(package_name)
except Exception:
return []
def _safe_collect_dynamic_libs(package_name):
try:
return collect_dynamic_libs(package_name)
except Exception:
return []
def _append_data_dir(datas, project_root, relative_path, bundle_target=None):
source = project_root / relative_path
if source.exists():
datas.append((str(source), bundle_target or relative_path.replace("\\", "/")))
def _append_package_data_dir(datas, package_name, relative_path, bundle_target):
spec = importlib.util.find_spec(package_name)
if not spec or not spec.origin:
return
package_root = Path(spec.origin).resolve().parent
source = package_root / relative_path
if source.exists():
datas.append((str(source), bundle_target))
SPEC_DIR = Path(SPEC).resolve().parent
PROJECT_ROOT = SPEC_DIR.parent
THIRD_PARTY_DIR = PROJECT_ROOT / "third_party"
RENDER_PIPELINE_DIR = PROJECT_ROOT / "RenderPipelineFile"
ENTRY_SCRIPT = PROJECT_ROOT / "Start_Run.py"
for candidate in (PROJECT_ROOT, THIRD_PARTY_DIR, RENDER_PIPELINE_DIR):
candidate_str = str(candidate)
if candidate.exists() and candidate_str not in sys.path:
sys.path.insert(0, candidate_str)
pathex = [
str(PROJECT_ROOT),
str(THIRD_PARTY_DIR),
str(RENDER_PIPELINE_DIR),
]
datas = []
for relative_path, bundle_target in (
("Resources", "Resources"),
("RenderPipelineFile", "RenderPipelineFile"),
("config", "config"),
("vr_actions", "vr_actions"),
("icons", "icons"),
("scripts", "scripts"),
("templates", "templates"),
("ui/Skins", "ui/Skins"),
("ssbo_component/shaders", "ssbo_component/shaders"),
("ssbo_component/effects", "ssbo_component/effects"),
):
_append_data_dir(datas, PROJECT_ROOT, relative_path, bundle_target)
imgui_ini = PROJECT_ROOT / "imgui.ini"
if imgui_ini.exists():
datas.append((str(imgui_ini), "."))
demo_dir = PROJECT_ROOT / "demo"
if demo_dir.exists():
datas.append((str(demo_dir), "demo"))
_append_package_data_dir(datas, "panda3d", "etc", "panda3d/etc")
binaries = []
for extension_suffix in importlib.machinery.EXTENSION_SUFFIXES:
local_lui_binary = PROJECT_ROOT / "ui" / f"lui{extension_suffix}"
if local_lui_binary.exists():
binaries.append((str(local_lui_binary), "ui"))
break
for package_name in ("panda3d", "imgui_bundle"):
binaries += _safe_collect_dynamic_libs(package_name)
if _has_module("openvr"):
binaries += _safe_collect_dynamic_libs("openvr")
hiddenimports = sorted(
set(
[
"pyassimp",
"p3dimgui",
"p3dimgui.backend",
"p3dimgui.shaders",
"rpcore",
"rpplugins",
"rplibs",
"RenderPipelineFile.rpcore",
"RenderPipelineFile.rpplugins",
"RenderPipelineFile.rplibs",
]
+ _safe_collect_submodules("core")
+ _safe_collect_submodules("scene")
+ _safe_collect_submodules("project")
+ _safe_collect_submodules("ui")
+ _safe_collect_submodules("ssbo_component")
+ _safe_collect_submodules("TransformGizmo")
+ _safe_collect_submodules("p3dimgui")
+ _safe_collect_submodules("rpcore")
+ _safe_collect_submodules("rpplugins")
+ _safe_collect_submodules("rplibs")
+ _safe_collect_submodules("RenderPipelineFile.rpcore")
+ _safe_collect_submodules("RenderPipelineFile.rpplugins")
+ _safe_collect_submodules("RenderPipelineFile.rplibs")
+ (["openvr"] if _has_module("openvr") else [])
+ (["pyassimp"] if _has_module("pyassimp") else [])
)
)
excludes = [
"PyQt5",
"PyQt6",
"PySide2",
"PySide6",
"tkinter",
]
a = Analysis(
[str(ENTRY_SCRIPT)],
pathex=pathex,
binaries=binaries,
datas=datas,
hiddenimports=hiddenimports,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=excludes,
win_no_prefer_redirects=False,
win_private_assemblies=False,
noarchive=False,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name="EGEditor",
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=False,
console=False,
)
coll = COLLECT(
exe,
a.binaries,
a.datas,
strip=False,
upx=False,
upx_exclude=[],
name="EGEditor",
)

View File

@ -1,90 +0,0 @@
# 项目部署指南
## 🚀 在新电脑上运行此项目
### 方法1使用Conda推荐
```bash
# 1. 安装Miniconda
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
bash Miniconda3-latest-Linux-x86_64.sh
# 2. 克隆项目
git clone <your-repo-url>
cd eg
# 3. 创建conda环境
conda env create -f environment.yml
# 4. 激活环境
conda activate eg-project
# 5. 运行项目
python Start_Run.py
```
### 方法2使用virtualenv + pip
```bash
# 1. 克隆项目
git clone <your-repo-url>
cd eg
# 2. 创建虚拟环境
python -m venv venv
source venv/bin/activate # Linux/Mac
# 或 venv\Scripts\activate # Windows
# 3. 安装依赖
pip install -r requirements/requirements.txt
# 4. 运行项目
python Start_Run.py
```
## 📁 需要复制的文件
**必须复制:**
- `main.py` - 主程序
- `environment.yml` - Conda环境配置
- `requirements.txt` - 核心依赖
**不要复制:**
- `.conda/` - 虚拟环境文件夹
- `__pycache__/` - Python缓存
- `conda-requirements.txt` - 仅用于最小 Conda 环境说明
## 🔧 Cursor IDE 设置
1. **打开项目**在Cursor中打开项目文件夹
2. **选择解释器**
- `Ctrl+Shift+P` → "Python: Select Interpreter"
- 选择虚拟环境中的Python路径
3. **验证环境**检查状态栏显示正确的Python版本
## 📦 项目依赖说明
- **Panda3D**: 3D图形引擎
- **imgui-bundle / p3dimgui**: ImGui 编辑器界面与 Panda3D 桥接
- **Pillow**: 图像处理与网页截图纹理转换
- **numpy**: VR / SSBO 数值处理
- **aiohttp**: 异步资源与场景 IO
- **openvr**: VR 支持
- **pyassimp**: 模型转换辅助
## 🌍 跨平台注意事项
- Linux/Mac: 使用 `source venv/bin/activate`
- Windows: 使用 `venv\Scripts\activate`
- 某些依赖可能需要系统级安装如OpenGL库
## 🐛 常见问题
**Q: 无法安装Panda3D**
A: 确保系统有OpenGL支持或使用conda安装
**Q: ImGui界面显示异常**
A: 检查显卡驱动/OpenGL支持并确认已安装 `imgui-bundle`
**Q: 导入错误?**
A: 检查 Python 版本,当前项目统一使用 Python 3.11。

View File

@ -1,3 +0,0 @@
# Minimal conda spec for the current project environment.
python=3.11
pip

View File

@ -1,15 +0,0 @@
name: eg-project
channels:
- conda-forge
- defaults
dependencies:
- python=3.11
- pip
- pip:
- Panda3D==1.10.15
- imgui-bundle
- Pillow>=9.0.1
- numpy>=1.24
- aiohttp>=3.9
- openvr==2.2.0
- pyassimp>=5.2.5