diff --git a/IMGUI_MODULE_ANALYSIS.md b/IMGUI_MODULE_ANALYSIS.md new file mode 100644 index 00000000..ad6dae7a --- /dev/null +++ b/IMGUI_MODULE_ANALYSIS.md @@ -0,0 +1,62 @@ +# 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) diff --git a/PROJECT_FULL_CATALOG.md b/PROJECT_FULL_CATALOG.md new file mode 100644 index 00000000..af962b5b --- /dev/null +++ b/PROJECT_FULL_CATALOG.md @@ -0,0 +1,2465 @@ +# 项目完整目录与脚本清单(本地自动生成) + +更新时间:2026-02-28 18:20:00(同步 Qt 清理提交) + +## 概览 + +- 根目录: `D:\IMGUI\EG` +- 目录总数(不含 __pycache__): 328 +- 文件总数(不含 __pycache__): 1534 +- Python 脚本总数: 385 + +## 完整目录树(tree /F /A) + +```text +Folder PATH listing for volume 应用 +Volume serial number is 5AA8-27D2 +D:. +| .gitignore +| AGENTS.md +| imgui.ini +| IMGUI_MODULE_ANALYSIS.md +| main.py +| QT_TO_IMGUI_MIGRATION_CHECKLIST.md +| Start_Run.py +| ++---.idea +| EG.iml +| misc.xml +| ++---.vscode +| settings.json +| ++---config +| vr_settings.json +| ++---core +| | collision_manager.py +| | Command_System.py +| | CustomMouseController.py +| | event_handler.py +| | imgui_style_manager.py +| | imgui_webview.py +| | InfoPanelManager.py +| | model_drag_drop.py +| | render_pipeline_utils.py +| | resource_manager.py +| | RotationHandleFull.fbx +| | RotationHandleQuarter.fbx +| | script_system.py +| | selection.py +| | selection_outline.py +| | terrain_manager.py +| | tool_manager.py +| | TranslateArrowHandle.fbx +| | UniformScaleHandle.fbx +| | vr_manager.py +| | world.py +| | world.py.backup +| | __init__.py +| | +| +---vr +| | | README.md +| | | __init__.py +| | | +| | +---config +| | | | joystick_config.py +| | | | shadow_stage.py +| | | | vr_config.py +| | | | vr_settings.json +| | | | __init__.py +| | | | +| | | \---__pycache__ +| | | vr_config.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---interaction +| | | | actions.py +| | | | grab.py +| | | | joystick.py +| | | | teleport.py +| | | | __init__.py +| | | | +| | | \---__pycache__ +| | | actions.cpython-310.pyc +| | | grab.cpython-310.pyc +| | | joystick.cpython-310.pyc +| | | teleport.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---performance +| | | | MIGRATION_REPORT.md +| | | | monitoring.py +| | | | optimization.py +| | | | __init__.py +| | | | +| | | \---__pycache__ +| | | monitoring.cpython-310.pyc +| | | optimization.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---rendering +| | | stages.py +| | | __init__.py +| | | +| | +---testing +| | | | test_mode.py +| | | | __init__.py +| | | | +| | | \---__pycache__ +| | | test_mode.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---tracking +| | | | controllers.py +| | | | __init__.py +| | | | +| | | \---__pycache__ +| | | controllers.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---visualization +| | | | controllers.py +| | | | effects.py +| | | | __init__.py +| | | | +| | | \---__pycache__ +| | | controllers.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | \---__pycache__ +| | __init__.cpython-310.pyc +| | +| \---__pycache__ +| collision_manager.cpython-310.pyc +| Command_System.cpython-310.pyc +| CustomMouseController.cpython-310.pyc +| event_handler.cpython-310.pyc +| imgui_style_manager.cpython-310.pyc +| InfoPanelManager.cpython-310.pyc +| model_drag_drop.cpython-310.pyc +| patrol_system.cpython-310.pyc +| render_pipeline_utils.cpython-310.pyc +| resource_manager.cpython-310.pyc +| script_system.cpython-310.pyc +| selection.cpython-310.pyc +| selection_outline.cpython-310.pyc +| terrain_manager.cpython-310.pyc +| tool_manager.cpython-310.pyc +| vr_manager.cpython-310.pyc +| world.cpython-310.pyc +| __init__.cpython-310.pyc +| ++---EG ++---gui +| | __init__.py +| | +| \---__pycache__ +| gui_manager.cpython-310.pyc +| __init__.cpython-310.pyc +| ++---icons +| | close_icon.png +| | delete_fail_icon.png +| | down_arrows.png +| | left_arrows.png +| | logo.png +| | minimize_icon.png +| | move_tool.png +| | property_select_image.png +| | right_arrows.png +| | rotate_tool.png +| | scale_tool.png +| | select_tool.png +| | solid_down_arrows.png +| | solid_right_arrows.png +| | success_icon.png +| | up_arrows.png +| | warning_icon.png +| | windowing_icon.png +| | +| \---file_types +| archive.png +| audio.png +| code.png +| config.png +| document.png +| file.png +| folder.png +| font.png +| image.png +| model.png +| python.png +| video.png +| ++---new +| project.json +| ++---project +| | project_manager.py +| | __init__.py +| | +| \---__pycache__ +| project_manager.cpython-310.pyc +| __init__.cpython-310.pyc +| ++---RenderPipelineFile +| | .flake8 +| | .gitignore +| | .pylintrc +| | .travis.yml +| | Default_NRM_2K.png +| | Default_Rough.png +| | LICENSE.txt +| | README.md +| | setup.py +| | setup_input.txt +| | start_daytime_editor.py +| | start_plugin_configurator.py +| | __init__.py +| | +| +---.idea +| | | .gitignore +| | | misc.xml +| | | modules.xml +| | | RenderPipeline-master.iml +| | | +| | \---inspectionProfiles +| | profiles_settings.xml +| | Project_Default.xml +| | +| +---config +| | daytime.yaml +| | debugging.yaml +| | Meta3d-config.prc +| | panda3d-config.prc +| | pipeline.yaml +| | plugins.yaml +| | stages.yaml +| | task-scheduler.yaml +| | +| +---core +| | RotationHandleQuarter.fbx +| | TranslateArrowHandle.fbx +| | UniformScaleHandle.fbx +| | +| +---data +| | | generate_txo_files.py +| | | install.flag +| | | +| | +---builtin_models +| | | \---skybox +| | | | skybox-2.jpg +| | | | skybox-blend.zip +| | | | skybox.bam +| | | | skybox.jpg +| | | | skybox.txo.pz +| | | | SOURCE.txt +| | | | +| | | +---skybox-blend +| | | | skybox.blend +| | | | +| | | \---sky_1 +| | | GreenExplosion.glb +| | | LightBlueSky.glb +| | | +| | +---default_cubemap +| | | | cubemap.txo.pz +| | | | filter.compute.glsl +| | | | filter.py +| | | | +| | | +---source +| | | | 0.png +| | | | 1.png +| | | | 2.png +| | | | 3.png +| | | | 4.png +| | | | 5.png +| | | | +| | | \---source_2 +| | | 0.jpg +| | | 1.jpg +| | | 2.jpg +| | | 3.jpg +| | | 4.jpg +| | | 5.jpg +| | | +| | +---empty_textures +| | | empty_basecolor.png +| | | empty_normal.png +| | | empty_roughness.png +| | | empty_specular.png +| | | README.md +| | | +| | +---environment_brdf +| | | | generate_reference.py +| | | | run_mitsuba.bat +| | | | +| | | +---res +| | | | environment.png +| | | | roughness.png +| | | | scene-coat.templ.xml +| | | | scene-metal.templ.xml +| | | | scene.templ.xml +| | | | scene.xml +| | | | +| | | +---slices +| | | | env_brdf_0.png +| | | | env_brdf_1.png +| | | | env_brdf_10.png +| | | | env_brdf_11.png +| | | | env_brdf_12.png +| | | | env_brdf_13.png +| | | | env_brdf_14.png +| | | | env_brdf_2.png +| | | | env_brdf_3.png +| | | | env_brdf_4.png +| | | | env_brdf_5.png +| | | | env_brdf_6.png +| | | | env_brdf_7.png +| | | | env_brdf_8.png +| | | | env_brdf_9.png +| | | | +| | | +---slices_coat +| | | | env_brdf.png +| | | | +| | | \---slices_metal +| | | env_brdf.png +| | | +| | +---film_grain +| | | generate.py +| | | grain.compute.glsl +| | | grain.txo.pz +| | | +| | +---font +| | | Roboto-Black.ttf +| | | Roboto-BlackItalic.ttf +| | | Roboto-Bold.ttf +| | | Roboto-BoldCondensed.ttf +| | | Roboto-BoldCondensedItalic.ttf +| | | Roboto-BoldItalic.ttf +| | | Roboto-Condensed.ttf +| | | Roboto-CondensedItalic.ttf +| | | Roboto-Italic.ttf +| | | roboto-LICENSE.txt +| | | Roboto-Light.ttf +| | | Roboto-LightItalic.ttf +| | | Roboto-Medium.ttf +| | | Roboto-MediumItalic.ttf +| | | Roboto-Regular.ttf +| | | Roboto-Thin.ttf +| | | Roboto-ThinItalic.ttf +| | | +| | +---gui +| | | black.jpg +| | | checkbox_checked.png +| | | checkbox_default.png +| | | close_window.png +| | | close_window_hover.png +| | | Default_Rough.png +| | | icon.ico +| | | icon.png +| | | icon_buffer_texture.png +| | | icon_pipe.png +| | | icon_texture.png +| | | icon_ubo.png +| | | keybindings.png +| | | loading_screen_bg.png +| | | loading_screen_bg.txo.pz +| | | pipeline_logo.png +| | | pipeline_logo_text.png +| | | python_warning.png +| | | radiobox_checked.png +| | | radiobox_default.png +| | | radiobox_disabled.png +| | | shader_reload_hint.png +| | | +| | +---ies_profiles +| | | area_light.ies +| | | bollard.ies +| | | comet.ies +| | | cylinder_narrow.ies +| | | cylinder_wide.ies +| | | defined.ies +| | | defined_diffuse.ies +| | | defined_diffuse_spot.ies +| | | defined_spot.ies +| | | display.ies +| | | jelly_fish.ies +| | | medium_scatter.ies +| | | overhead.ies +| | | parallel_beam.ies +| | | pear.ies +| | | PREVIEWS.jpg +| | | README.md +| | | scatter_light.ies +| | | soft_arrow.ies +| | | soft_display.ies +| | | star_focused.ies +| | | three_lobe_umbrella.ies +| | | three_lobe_vee.ies +| | | tight_focused.ies +| | | top_post.ies +| | | trapezoid.ies +| | | umbrella.ies +| | | vee.ies +| | | x_arrow.ies +| | | x_arrow_diffuse.ies +| | | x_arrow_soft.ies +| | | +| | +---Materials +| | | GroundMaterial.yaml +| | | +| | +---panda3d_patches +| | | prev-model-view-matrix-part2.diff +| | | prev-model-view-matrix.diff +| | | README.md +| | | +| | +---scripts +| | | example_script.py +| | | +| | \---setup +| | check_requirements.py +| | +| +---effects +| | basic_instancing.yaml +| | debug_metallic.yaml +| | debug_roughness.yaml +| | default.yaml +| | material_blend4.yaml +| | metallic_only.yaml +| | pbr_additive_metallic.yaml +| | pbr_direct_metallic.yaml +| | pbr_direct_roughness.yaml +| | pbr_extended.yaml +| | pbr_with_emission.yaml +| | pbr_with_metallic.yaml +| | pbr_with_normal.yaml +| | pbr_with_roughness.yaml +| | pbr_with_textures.yaml +| | roughness_only.yaml +| | simple_texture_test.yaml +| | simple_transparent.yaml +| | skybox.yaml +| | ssbo_instancing.yaml +| | terrain-effect.yaml +| | test_metallic_debug.yaml +| | test_roughness_debug.yaml +| | +| +---rp +| | +---core +| | | RotationHandleFull.fbx +| | | RotationHandleQuarter.fbx +| | | TranslateArrowHandle.fbx +| | | UniformScaleHandle.fbx +| | | +| | \---shader +| | \---core +| | RotationHandleFull.fbx +| | RotationHandleQuarter.fbx +| | TranslateArrowHandle.fbx +| | UniformScaleHandle.fbx +| | +| +---rpcore +| | | common_resources.py +| | | effect.py +| | | globals.py +| | | gpu_command_queue.py +| | | image.py +| | | light_manager.py +| | | loader.py +| | | mount_manager.py +| | | render_pipeline.py +| | | render_stage.py +| | | render_target.py +| | | rpobject.py +| | | stage_manager.py +| | | __init__.py +| | | +| | +---gui +| | | | buffer_viewer.py +| | | | checkbox.py +| | | | checkbox_collection.py +| | | | debugger.py +| | | | draggable_window.py +| | | | error_message_display.py +| | | | exposure_widget.py +| | | | fps_chart.py +| | | | labeled_checkbox.py +| | | | loading_screen.py +| | | | pipe_viewer.py +| | | | pixel_inspector.py +| | | | README.md +| | | | render_mode_selector.py +| | | | slider.py +| | | | sprite.py +| | | | text.py +| | | | texture_preview.py +| | | | text_node.py +| | | | __init__.py +| | | | +| | | \---__pycache__ +| | | buffer_viewer.cpython-310.pyc +| | | checkbox.cpython-310.pyc +| | | checkbox_collection.cpython-310.pyc +| | | debugger.cpython-310.pyc +| | | draggable_window.cpython-310.pyc +| | | error_message_display.cpython-310.pyc +| | | exposure_widget.cpython-310.pyc +| | | fps_chart.cpython-310.pyc +| | | labeled_checkbox.cpython-310.pyc +| | | loading_screen.cpython-310.pyc +| | | pipe_viewer.cpython-310.pyc +| | | pixel_inspector.cpython-310.pyc +| | | render_mode_selector.cpython-310.pyc +| | | slider.cpython-310.pyc +| | | sprite.cpython-310.pyc +| | | text.cpython-310.pyc +| | | texture_preview.cpython-310.pyc +| | | text_node.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---native +| | | | .gitignore +| | | | config.ini +| | | | update_module_builder.py +| | | | use_cxx.flag +| | | | __init__.py +| | | | +| | | +---source +| | | | config_rsnative.cpp +| | | | config_rsnative.h +| | | | config_rsnative.N +| | | | gpu_command.cpp +| | | | gpu_command.h +| | | | gpu_command.I +| | | | gpu_command_list.cpp +| | | | gpu_command_list.h +| | | | ies_dataset.cpp +| | | | ies_dataset.h +| | | | internal_light_manager.cpp +| | | | internal_light_manager.h +| | | | internal_light_manager.I +| | | | pointer_slot_storage.h +| | | | pssm_camera_rig.cpp +| | | | pssm_camera_rig.h +| | | | pssm_camera_rig.I +| | | | pssm_helper.cpp +| | | | pssm_helper.h +| | | | README.md +| | | | rp_light.cpp +| | | | rp_light.h +| | | | rp_light.I +| | | | rp_point_light.cpp +| | | | rp_point_light.h +| | | | rp_point_light.I +| | | | rp_spot_light.cpp +| | | | rp_spot_light.h +| | | | rp_spot_light.I +| | | | shadow_atlas.cpp +| | | | shadow_atlas.h +| | | | shadow_atlas.I +| | | | shadow_manager.cpp +| | | | shadow_manager.h +| | | | shadow_manager.I +| | | | shadow_source.cpp +| | | | shadow_source.h +| | | | shadow_source.I +| | | | tag_state_manager.cpp +| | | | tag_state_manager.h +| | | | tag_state_manager.I +| | | | +| | | \---__pycache__ +| | | __init__.cpython-310.pyc +| | | +| | +---pluginbase +| | | | base_plugin.py +| | | | day_manager.py +| | | | day_setting_types.py +| | | | manager.py +| | | | setting_types.py +| | | | __init__.py +| | | | +| | | \---__pycache__ +| | | base_plugin.cpython-310.pyc +| | | day_manager.cpython-310.pyc +| | | day_setting_types.cpython-310.pyc +| | | manager.cpython-310.pyc +| | | setting_types.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---pynative +| | | gpu_command.py +| | | gpu_command_list.py +| | | ies_dataset.py +| | | internal_light_manager.py +| | | pointer_slot_storage.py +| | | pssm_camera_rig.py +| | | README.md +| | | rp_light.py +| | | rp_point_light.py +| | | rp_spot_light.py +| | | shadow_atlas.py +| | | shadow_manager.py +| | | shadow_source.py +| | | tag_state_manager.py +| | | __init__.py +| | | +| | +---shader +| | | | ambient_stage.frag.glsl +| | | | apply_lights.frag.glsl +| | | | bilateral_blur.frag.glsl +| | | | bilateral_halfres_blur.frag.glsl +| | | | bilateral_upscale.frag.glsl +| | | | collect_used_cells.frag.glsl +| | | | combine_velocity.frag.glsl +| | | | cull_lights.frag.glsl +| | | | default_gui_shader.vert.glsl +| | | | default_post_process.vert.glsl +| | | | default_post_process_instanced.vert.glsl +| | | | downscale_depth.frag.glsl +| | | | final_present_stage.frag.glsl +| | | | final_stage.frag.glsl +| | | | flag_used_cells.frag.glsl +| | | | fps_chart.compute.glsl +| | | | fps_chart_update.compute.glsl +| | | | group_lights.frag.glsl +| | | | pixel_inspector.frag.glsl +| | | | process_command_queue.frag.glsl +| | | | render_pipeline_base.inc.glsl +| | | | tiled_culling.vert.glsl +| | | | upscale_stage.frag.glsl +| | | | view_frustum_cull.frag.glsl +| | | | visualize_exposure.compute.glsl +| | | | +| | | +---ibl +| | | | cubemap_diffuse.frag.glsl +| | | | cubemap_diffuse_filter.frag.glsl +| | | | cubemap_specular_filter.frag.glsl +| | | | cubemap_specular_filter_first.frag.glsl +| | | | cubemap_specular_prefilter.frag.glsl +| | | | cubemap_specular_prefilter_first.frag.glsl +| | | | +| | | +---includes +| | | | brdf.inc.glsl +| | | | color_spaces.inc.glsl +| | | | common_functions.inc.glsl +| | | | envprobes.inc.glsl +| | | | forward_shading.inc.glsl +| | | | gaussian_weights.inc.glsl +| | | | gbuffer.inc.glsl +| | | | halton_sequences.inc.glsl +| | | | ies_lighting.inc.glsl +| | | | importance_sampling.inc.glsl +| | | | lighting_pipeline.inc.glsl +| | | | lights.inc.glsl +| | | | light_classification.inc.glsl +| | | | light_culling.inc.glsl +| | | | light_data.inc.glsl +| | | | light_data.struct.glsl +| | | | material.inc.glsl +| | | | noise.inc.glsl +| | | | nonviewspace_shading_pipeline.inc.glsl +| | | | normal_mapping.inc.glsl +| | | | normal_packing.inc.glsl +| | | | poisson_disk.inc.glsl +| | | | sampling_sequences.inc.glsl +| | | | shadows.inc.glsl +| | | | skin_shading.inc.glsl +| | | | source_data.struct.glsl +| | | | temporal_resolve.inc.glsl +| | | | tonemapping.inc.glsl +| | | | transforms.inc.glsl +| | | | upsampling.inc.glsl +| | | | vertex_output.struct.glsl +| | | | +| | | \---templates +| | | envmap.frag.glsl +| | | forward.frag.glsl +| | | gbuffer.frag.glsl +| | | shadow.frag.glsl +| | | vertex.vert.glsl +| | | voxelize.frag.glsl +| | | +| | +---stages +| | | | ambient_stage.py +| | | | apply_lights_stage.py +| | | | collect_used_cells_stage.py +| | | | combine_velocity_stage.py +| | | | cull_lights_stage.py +| | | | downscale_z_stage.py +| | | | final_stage.py +| | | | flag_used_cells_stage.py +| | | | gbuffer_stage.py +| | | | shadow_stage.py +| | | | update_previous_pipes_stage.py +| | | | upscale_stage.py +| | | | __init__.py +| | | | +| | | \---__pycache__ +| | | ambient_stage.cpython-310.pyc +| | | apply_lights_stage.cpython-310.pyc +| | | collect_used_cells_stage.cpython-310.pyc +| | | combine_velocity_stage.cpython-310.pyc +| | | cull_lights_stage.cpython-310.pyc +| | | downscale_z_stage.cpython-310.pyc +| | | final_stage.cpython-310.pyc +| | | flag_used_cells_stage.cpython-310.pyc +| | | gbuffer_stage.cpython-310.pyc +| | | shadow_stage.cpython-310.pyc +| | | update_previous_pipes_stage.cpython-310.pyc +| | | upscale_stage.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---util +| | | | cubemap_filter.py +| | | | display_shader_builder.py +| | | | generic.py +| | | | ies_profile_loader.py +| | | | movement_controller.py +| | | | network_communication.py +| | | | post_process_region.py +| | | | shader_input_blocks.py +| | | | smooth_connected_curve.py +| | | | submodule_downloader.py +| | | | task_scheduler.py +| | | | __init__.py +| | | | +| | | \---__pycache__ +| | | cubemap_filter.cpython-310.pyc +| | | display_shader_builder.cpython-310.pyc +| | | generic.cpython-310.pyc +| | | ies_profile_loader.cpython-310.pyc +| | | network_communication.cpython-310.pyc +| | | post_process_region.cpython-310.pyc +| | | shader_input_blocks.cpython-310.pyc +| | | smooth_connected_curve.cpython-310.pyc +| | | task_scheduler.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | \---__pycache__ +| | common_resources.cpython-310.pyc +| | effect.cpython-310.pyc +| | globals.cpython-310.pyc +| | gpu_command_queue.cpython-310.pyc +| | image.cpython-310.pyc +| | light_manager.cpython-310.pyc +| | loader.cpython-310.pyc +| | mount_manager.cpython-310.pyc +| | render_pipeline.cpython-310.pyc +| | render_stage.cpython-310.pyc +| | render_target.cpython-310.pyc +| | rpobject.cpython-310.pyc +| | stage_manager.cpython-310.pyc +| | __init__.cpython-310.pyc +| | +| +---rplibs +| | | pyqt_imports.py +| | | six-source.txt +| | | six.py +| | | __init__.py +| | | +| | +---colorama +| | | | ansi.py +| | | | ansitowin32.py +| | | | initialise.py +| | | | LICENSE.txt +| | | | SOURCE.txt +| | | | win32.py +| | | | winterm.py +| | | | __init__.py +| | | | +| | | \---__pycache__ +| | | ansi.cpython-310.pyc +| | | ansitowin32.cpython-310.pyc +| | | initialise.cpython-310.pyc +| | | win32.cpython-310.pyc +| | | winterm.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---progressbar +| | | compat.py +| | | LICENSE.txt +| | | SOURCE.txt +| | | widgets.py +| | | __init__.py +| | | +| | +---yaml +| | | | LICENSE +| | | | SOURCE.txt +| | | | __init__.py +| | | | +| | | +---yaml_py2 +| | | | composer.py +| | | | constructor.py +| | | | cyaml.py +| | | | dumper.py +| | | | emitter.py +| | | | error.py +| | | | events.py +| | | | loader.py +| | | | nodes.py +| | | | parser.py +| | | | reader.py +| | | | representer.py +| | | | resolver.py +| | | | scanner.py +| | | | serializer.py +| | | | tokens.py +| | | | __init__.py +| | | | +| | | +---yaml_py3 +| | | | | composer.py +| | | | | constructor.py +| | | | | cyaml.py +| | | | | dumper.py +| | | | | emitter.py +| | | | | error.py +| | | | | events.py +| | | | | loader.py +| | | | | nodes.py +| | | | | parser.py +| | | | | reader.py +| | | | | representer.py +| | | | | resolver.py +| | | | | scanner.py +| | | | | serializer.py +| | | | | tokens.py +| | | | | __init__.py +| | | | | +| | | | \---__pycache__ +| | | | composer.cpython-310.pyc +| | | | constructor.cpython-310.pyc +| | | | cyaml.cpython-310.pyc +| | | | dumper.cpython-310.pyc +| | | | emitter.cpython-310.pyc +| | | | error.cpython-310.pyc +| | | | events.cpython-310.pyc +| | | | loader.cpython-310.pyc +| | | | nodes.cpython-310.pyc +| | | | parser.cpython-310.pyc +| | | | reader.cpython-310.pyc +| | | | representer.cpython-310.pyc +| | | | resolver.cpython-310.pyc +| | | | scanner.cpython-310.pyc +| | | | serializer.cpython-310.pyc +| | | | tokens.cpython-310.pyc +| | | | __init__.cpython-310.pyc +| | | | +| | | \---__pycache__ +| | | __init__.cpython-310.pyc +| | | +| | \---__pycache__ +| | six.cpython-310.pyc +| | __init__.cpython-310.pyc +| | +| +---rpplugins +| | | README.md +| | | __init__.py +| | | +| | +---ao +| | | | ao_stage.py +| | | | config.yaml +| | | | plugin.py +| | | | __init__.py +| | | | +| | | +---shader +| | | | alchemy.kernel.glsl +| | | | ao_sample.frag.glsl +| | | | hbao.kernel.glsl +| | | | resolve_ao.frag.glsl +| | | | small_scale_ao.frag.glsl +| | | | ssao.kernel.glsl +| | | | ssvo.kernel.glsl +| | | | ue4ao.kernel.glsl +| | | | +| | | \---__pycache__ +| | | ao_stage.cpython-310.pyc +| | | plugin.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---bloom +| | | | bloom_stage.py +| | | | config.yaml +| | | | plugin.py +| | | | __init__.py +| | | | +| | | +---resources +| | | | lens_dirt.ffxml +| | | | lens_dirt.png +| | | | lens_dirt.txo.pz +| | | | SOURCE.txt +| | | | +| | | +---shader +| | | | apply_bloom.frag.glsl +| | | | bloom_downsample.frag.glsl +| | | | bloom_upsample.frag.glsl +| | | | extract_bright_spots.frag.glsl +| | | | remove_fireflies.frag.glsl +| | | | +| | | \---__pycache__ +| | | bloom_stage.cpython-310.pyc +| | | plugin.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---clouds +| | | | apply_clouds_stage.py +| | | | cloud_voxel_stage.py +| | | | config.yaml +| | | | plugin.py +| | | | __init__.py +| | | | +| | | +---resources +| | | | generate_noise1.compute.glsl +| | | | generate_noise2.compute.glsl +| | | | noise.inc.glsl +| | | | noise1-data.txo.pz +| | | | noise2-data.txo.pz +| | | | precompute.py +| | | | weather_tex.png +| | | | +| | | +---shader +| | | | apply_clouds.frag.glsl +| | | | render_clouds.frag.glsl +| | | | +| | | \---__pycache__ +| | | apply_clouds_stage.cpython-310.pyc +| | | plugin.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---color_correction +| | | | auto_exposure_stage.py +| | | | color_correction_stage.py +| | | | config.yaml +| | | | manual_exposure_stage.py +| | | | plugin.py +| | | | sharpen_stage.py +| | | | tonemapping_stage.py +| | | | __init__.py +| | | | +| | | +---resources +| | | | | generate_default_lut.py +| | | | | generate_film_luts.py +| | | | | README.md +| | | | | +| | | | +---film_luts +| | | | | agfa_advantix_100.png +| | | | | agfa_advantix_200.png +| | | | | agfa_advantix_400.png +| | | | | agfa_agfachrome_ct_precisa_100.png +| | | | | agfa_agfachrome_ct_precisa_200.png +| | | | | agfa_agfachrome_rsx2_050.png +| | | | | agfa_agfachrome_rsx2_100.png +| | | | | agfa_agfachrome_rsx2_200.png +| | | | | agfa_agfacolor_futura_100.png +| | | | | agfa_agfacolor_futura_200.png +| | | | | agfa_agfacolor_futura_400.png +| | | | | agfa_agfacolor_futura_ii_100.png +| | | | | agfa_agfacolor_futura_ii_200.png +| | | | | agfa_agfacolor_futura_ii_400.png +| | | | | agfa_agfacolor_hdc_100_plus.png +| | | | | agfa_agfacolor_hdc_200_plus.png +| | | | | agfa_agfacolor_hdc_400_plus.png +| | | | | agfa_agfacolor_optima_ii_100.png +| | | | | agfa_agfacolor_optima_ii_200.png +| | | | | agfa_agfacolor_ultra_050.png +| | | | | agfa_agfacolor_vista_100.png +| | | | | agfa_agfacolor_vista_200.png +| | | | | agfa_agfacolor_vista_400.png +| | | | | agfa_agfacolor_vista_800.png +| | | | | default_lut.png +| | | | | fujifilm_f-125.png +| | | | | fujifilm_f-250.png +| | | | | fujifilm_f-400.png +| | | | | fujifilm_fci.png +| | | | | fujifilm_fp2900z.png +| | | | | kodak_dscs_3151.png +| | | | | kodak_dscs_3152.png +| | | | | kodak_dscs_3153.png +| | | | | kodak_dscs_3154.png +| | | | | kodak_dscs_3155.png +| | | | | kodak_dscs_3156.png +| | | | | kodak_ektachrome_100.png +| | | | | kodak_ektachrome_100_plus.png +| | | | | kodak_ektachrome_320t.png +| | | | | kodak_ektachrome_400x.png +| | | | | kodak_ektachrome_64.png +| | | | | kodak_ektachrome_64t.png +| | | | | kodak_ektachrome_e100s.png +| | | | | kodak_gold_100.png +| | | | | kodak_gold_200.png +| | | | | kodak_kodachrome_200.png +| | | | | kodak_kodachrome_25.png +| | | | | kodak_kodachrome_64.png +| | | | | kodak_max_zoom_800.png +| | | | | kodak_portra_100t.png +| | | | | kodak_portra_160nc.png +| | | | | kodak_portra_160vc.png +| | | | | kodak_portra_400nc.png +| | | | | kodak_portra_400vc.png +| | | | | kodak_portra_800.png +| | | | | saturation_lut.png +| | | | | +| | | | \---film_luts_raw +| | | | agfa_advantix_100.spi1d +| | | | agfa_advantix_200.spi1d +| | | | agfa_advantix_400.spi1d +| | | | agfa_agfachrome_ct_precisa_100.spi1d +| | | | agfa_agfachrome_ct_precisa_200.spi1d +| | | | agfa_agfachrome_rsx2_050.spi1d +| | | | agfa_agfachrome_rsx2_100.spi1d +| | | | agfa_agfachrome_rsx2_200.spi1d +| | | | agfa_agfacolor_futura_100.spi1d +| | | | agfa_agfacolor_futura_200.spi1d +| | | | agfa_agfacolor_futura_400.spi1d +| | | | agfa_agfacolor_futura_ii_100.spi1d +| | | | agfa_agfacolor_futura_ii_200.spi1d +| | | | agfa_agfacolor_futura_ii_400.spi1d +| | | | agfa_agfacolor_hdc_100_plus.spi1d +| | | | agfa_agfacolor_hdc_200_plus.spi1d +| | | | agfa_agfacolor_hdc_400_plus.spi1d +| | | | agfa_agfacolor_optima_ii_100.spi1d +| | | | agfa_agfacolor_optima_ii_200.spi1d +| | | | agfa_agfacolor_ultra_050.spi1d +| | | | agfa_agfacolor_vista_100.spi1d +| | | | agfa_agfacolor_vista_200.spi1d +| | | | agfa_agfacolor_vista_400.spi1d +| | | | agfa_agfacolor_vista_800.spi1d +| | | | canon_optura_981111.slrr.spi1d +| | | | canon_optura_981111.spi1d +| | | | canon_optura_981113.spi1d +| | | | canon_optura_981114.spi1d +| | | | eastman_double_x_neg_12min.spi1d +| | | | eastman_double_x_neg_4min.spi1d +| | | | eastman_double_x_neg_5min.spi1d +| | | | eastman_double_x_neg_6min.spi1d +| | | | fujifilm_f-125.spi1d +| | | | fujifilm_f-250.spi1d +| | | | fujifilm_f-400.spi1d +| | | | fujifilm_fci.spi1d +| | | | fujifilm_fp2900z.spi1d +| | | | kodak_dscs_3151.spi1d +| | | | kodak_dscs_3152.spi1d +| | | | kodak_dscs_3153.spi1d +| | | | kodak_dscs_3154.spi1d +| | | | kodak_dscs_3155.spi1d +| | | | kodak_dscs_3156.spi1d +| | | | kodak_ektachrome_100.spi1d +| | | | kodak_ektachrome_100_plus.spi1d +| | | | kodak_ektachrome_320t.spi1d +| | | | kodak_ektachrome_400x.spi1d +| | | | kodak_ektachrome_64.spi1d +| | | | kodak_ektachrome_64t.spi1d +| | | | kodak_ektachrome_e100s.spi1d +| | | | kodak_gold_100.spi1d +| | | | kodak_gold_200.spi1d +| | | | kodak_kaf-2001.spi1d +| | | | kodak_kaf-3000.spi1d +| | | | kodak_kai-0311.spi1d +| | | | kodak_kai-0372.spi1d +| | | | kodak_kai-1010.spi1d +| | | | kodak_kodachrome_200.spi1d +| | | | kodak_kodachrome_25.spi1d +| | | | kodak_kodachrome_64.spi1d +| | | | kodak_max_zoom_800.spi1d +| | | | kodak_portra_100t.spi1d +| | | | kodak_portra_160nc.spi1d +| | | | kodak_portra_160vc.spi1d +| | | | kodak_portra_400nc.spi1d +| | | | kodak_portra_400vc.spi1d +| | | | kodak_portra_800.spi1d +| | | | +| | | +---shader +| | | | analyze_brightness.frag.glsl +| | | | apply_exposure.frag.glsl +| | | | apply_tonemap.frag.glsl +| | | | chromatic_aberration.inc.glsl +| | | | downscale_luminance.frag.glsl +| | | | generate_luminance.frag.glsl +| | | | manual_exposure.frag.glsl +| | | | post_fx.frag.glsl +| | | | sharpen.frag.glsl +| | | | +| | | \---__pycache__ +| | | auto_exposure_stage.cpython-310.pyc +| | | color_correction_stage.cpython-310.pyc +| | | manual_exposure_stage.cpython-310.pyc +| | | plugin.cpython-310.pyc +| | | sharpen_stage.cpython-310.pyc +| | | tonemapping_stage.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---dof +| | | | config.yaml +| | | | dof_stage.py +| | | | plugin.py +| | | | __init__.py +| | | | +| | | +---shader +| | | | compute_dof.frag.glsl +| | | | dof.inc.glsl +| | | | dof_presort.frag.glsl +| | | | fetch_dof_minmax.frag.glsl +| | | | fetch_dof_minmax_horiz.frag.glsl +| | | | fetch_dof_tile_neighbors.frag.glsl +| | | | merge_dof.frag.glsl +| | | | prefilter_dof.frag.glsl +| | | | +| | | \---__pycache__ +| | | dof_stage.cpython-310.pyc +| | | plugin.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---env_probes +| | | | apply_envprobes_stage.py +| | | | config.yaml +| | | | cull_probes_stage.py +| | | | environment_capture_stage.py +| | | | environment_probe.py +| | | | plugin.py +| | | | probe_manager.py +| | | | __init__.py +| | | | +| | | +---shader +| | | | | apply_envprobes.frag.glsl +| | | | | cull_probes.frag.glsl +| | | | | filter_cubemap.frag.glsl +| | | | | filter_cubemap_diffuse.frag.glsl +| | | | | generate_mip_shaders.py +| | | | | store_cubemap.frag.glsl +| | | | | store_cubemap_diffuse.frag.glsl +| | | | | +| | | | \---mips +| | | | 0.autogen.glsl +| | | | 1.autogen.glsl +| | | | 10.autogen.glsl +| | | | 11.autogen.glsl +| | | | 2.autogen.glsl +| | | | 3.autogen.glsl +| | | | 4.autogen.glsl +| | | | 5.autogen.glsl +| | | | 6.autogen.glsl +| | | | 7.autogen.glsl +| | | | 8.autogen.glsl +| | | | 9.autogen.glsl +| | | | +| | | \---__pycache__ +| | | apply_envprobes_stage.cpython-310.pyc +| | | cull_probes_stage.cpython-310.pyc +| | | environment_capture_stage.cpython-310.pyc +| | | plugin.cpython-310.pyc +| | | probe_manager.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---forward_shading +| | | | config.yaml +| | | | forward_stage.py +| | | | plugin.py +| | | | __init__.py +| | | | +| | | +---shader +| | | | merge_with_deferred.frag.glsl +| | | | +| | | \---__pycache__ +| | | forward_stage.cpython-310.pyc +| | | plugin.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---fxaa +| | | | config.yaml +| | | | fxaa_stage.py +| | | | plugin.py +| | | | __init__.py +| | | | +| | | +---shader +| | | | FXAA.inc.glsl +| | | | fxaa_stage.frag.glsl +| | | | write_luma.frag.glsl +| | | | +| | | \---__pycache__ +| | | fxaa_stage.cpython-310.pyc +| | | plugin.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---motion_blur +| | | | config.yaml +| | | | motion_blur_stage.py +| | | | plugin.py +| | | | __init__.py +| | | | +| | | +---shader +| | | | apply_motion_blur.frag.glsl +| | | | camera_motion_blur.frag.glsl +| | | | fetch_dominant_velocity.frag.glsl +| | | | fetch_dominant_velocity_horiz.frag.glsl +| | | | motion_blur.inc.glsl +| | | | neighbor_minmax.frag.glsl +| | | | pack_blur_data.frag.glsl +| | | | +| | | \---__pycache__ +| | | motion_blur_stage.cpython-310.pyc +| | | plugin.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---plugin_prefab +| | | | config.yaml +| | | | demo_stage.py +| | | | plugin.py +| | | | __init__.py +| | | | +| | | +---resources +| | | | README.txt +| | | | +| | | \---shader +| | | README.txt +| | | +| | +---pssm +| | | | config.yaml +| | | | plugin.py +| | | | pssm_dist_shadow_stage.py +| | | | pssm_scene_shadow_stage.py +| | | | pssm_shadow_stage.py +| | | | pssm_stage.py +| | | | __init__.py +| | | | +| | | +---shader +| | | | apply_sun_shading.frag.glsl +| | | | blur_esm.frag.glsl +| | | | convert_to_esm.frag.glsl +| | | | filter_pssm.inc.glsl +| | | | filter_pssm_shadows.frag.glsl +| | | | +| | | \---__pycache__ +| | | plugin.cpython-310.pyc +| | | pssm_dist_shadow_stage.cpython-310.pyc +| | | pssm_scene_shadow_stage.cpython-310.pyc +| | | pssm_shadow_stage.cpython-310.pyc +| | | pssm_stage.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---scattering +| | | | config.yaml +| | | | godray_stage.py +| | | | plugin.py +| | | | scattering_envmap_stage.py +| | | | scattering_methods.py +| | | | scattering_stage.py +| | | | __init__.py +| | | | +| | | +---resources +| | | | \---hosek_wilkie_scattering +| | | | | .gitignore +| | | | | config.ini +| | | | | generate_table.py +| | | | | README.md +| | | | | update_module_builder.py +| | | | | +| | | | \---source +| | | | ArHosekSkyModel.cpp +| | | | ArHosekSkyModel.h +| | | | ArHosekSkyModelData_CIEXYZ.data +| | | | ArHosekSkyModelData_RGB.data +| | | | config_module.cpp +| | | | config_module.h +| | | | main.h +| | | | +| | | +---shader +| | | | | apply_scattering.frag.glsl +| | | | | compute_godrays.frag.glsl +| | | | | LICENSE.txt +| | | | | scattering_envmap.frag.glsl +| | | | | scattering_method.inc.glsl +| | | | | +| | | | +---eric_bruneton +| | | | | add_delta_e.compute.glsl +| | | | | add_delta_sr.compute.glsl +| | | | | compute_scattering.inc.glsl +| | | | | copy_inscatter.compute.glsl +| | | | | copy_irradiance.compute.glsl +| | | | | delta_e.compute.glsl +| | | | | delta_j.compute.glsl +| | | | | delta_sm_sr.compute.glsl +| | | | | delta_sr.compute.glsl +| | | | | irradiance_n.compute.glsl +| | | | | scattering_common.glsl +| | | | | transmittance.compute.glsl +| | | | | +| | | | \---hosek_wilkie +| | | | compute_scattering.inc.glsl +| | | | +| | | \---__pycache__ +| | | godray_stage.cpython-310.pyc +| | | plugin.cpython-310.pyc +| | | scattering_envmap_stage.cpython-310.pyc +| | | scattering_methods.cpython-310.pyc +| | | scattering_stage.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---selection_outline +| | | | config.yaml +| | | | plugin.py +| | | | selection_outline_stage.py +| | | | __init__.py +| | | | +| | | +---shader +| | | | selection_outline.frag.glsl +| | | | +| | | \---__pycache__ +| | | plugin.cpython-310.pyc +| | | selection_outline_stage.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---skin_shading +| | | | config.yaml +| | | | plugin.py +| | | | skin_shading_stage.py +| | | | __init__.py +| | | | +| | | +---shader +| | | | SeperableSSS.inc.glsl +| | | | sssss_blur.frag.glsl +| | | | +| | | \---__pycache__ +| | | plugin.cpython-310.pyc +| | | skin_shading_stage.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---sky_ao +| | | | ao_stage.py +| | | | capture_stage.py +| | | | config.yaml +| | | | plugin.py +| | | | __init__.py +| | | | +| | | +---shader +| | | | compute_sky_ao.frag.glsl +| | | | convert_depth.frag.glsl +| | | | sky_ao.inc.glsl +| | | | +| | | \---__pycache__ +| | | ao_stage.cpython-310.pyc +| | | capture_stage.cpython-310.pyc +| | | plugin.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---smaa +| | | | config.yaml +| | | | jitters.py +| | | | LICENSE.txt +| | | | plugin.py +| | | | smaa_stage.py +| | | | __init__.py +| | | | +| | | +---resources +| | | | area_tex.png +| | | | search_tex.png +| | | | +| | | +---shader +| | | | blending_weights.frag.glsl +| | | | edge_detection.frag.glsl +| | | | neighborhood_blending.frag.glsl +| | | | resolve_smaa.frag.glsl +| | | | SMAA.inc.glsl +| | | | smaa_wrapper.inc.glsl +| | | | +| | | \---__pycache__ +| | | jitters.cpython-310.pyc +| | | plugin.cpython-310.pyc +| | | smaa_stage.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---ssr +| | | | config.yaml +| | | | plugin.py +| | | | ssr_stage.py +| | | | __init__.py +| | | | +| | | +---shader +| | | | reflection_velocity.frag.glsl +| | | | reproject_lighting.frag.glsl +| | | | resolve_ssr.frag.glsl +| | | | ssr_trace.frag.glsl +| | | | upscale_bilateral_brdf.frag.glsl +| | | | +| | | \---__pycache__ +| | | plugin.cpython-310.pyc +| | | ssr_stage.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---volumetrics +| | | | config.yaml +| | | | plugin.py +| | | | volumetrics_stage.py +| | | | __init__.py +| | | | +| | | +---shader +| | | | apply_volumetrics.frag.glsl +| | | | compute_volumetric_shadows.frag.glsl +| | | | +| | | \---__pycache__ +| | | plugin.cpython-310.pyc +| | | volumetrics_stage.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | +---vxgi +| | | | config.yaml +| | | | plugin.py +| | | | voxelization_stage.py +| | | | vxgi_stage.py +| | | | __init__.py +| | | | +| | | +---shader +| | | | copy_voxels.frag.glsl +| | | | generate_mipmaps.frag.glsl +| | | | resolve_vxgi.frag.glsl +| | | | vxgi.inc.glsl +| | | | vxgi_diffuse.frag.glsl +| | | | vxgi_specular.frag.glsl +| | | | +| | | \---__pycache__ +| | | plugin.cpython-310.pyc +| | | voxelization_stage.cpython-310.pyc +| | | vxgi_stage.cpython-310.pyc +| | | __init__.cpython-310.pyc +| | | +| | \---__pycache__ +| | __init__.cpython-310.pyc +| | +| +---rptemp +| | \---core +| | RotationHandleFull.fbx +| | RotationHandleQuarter.fbx +| | TranslateArrowHandle.fbx +| | UniformScaleHandle.fbx +| | +| +---samples +| | .gitignore +| | download_samples.py +| | README.md +| | +| +---toolkit +| | +---bake_gi +| | | | bake.py +| | | | display.py +| | | | +| | | \---resources +| | | convolute.frag.glsl +| | | copy_cubemap.frag.glsl +| | | default.vert.glsl +| | | display.frag.glsl +| | | display.vert.glsl +| | | first-bounce.frag.glsl +| | | first-bounce.vert.glsl +| | | +| | +---day_time_editor +| | | | curve_widget.py +| | | | main.py +| | | | +| | | \---ui +| | | | compile_ui_qt4.bat +| | | | compile_ui_qt5.bat +| | | | main_window.ui +| | | | main_window_generated.py +| | | | point_insert.ui +| | | | point_insert_dialog_generated.py +| | | | resources.qrc +| | | | resources_rc.py +| | | | __init__.py +| | | | +| | | \---res +| | | 0_00.png +| | | 12_00.png +| | | 16_00.png +| | | 20_00.png +| | | 4_00.png +| | | 8_00.png +| | | daytime_editor_logo.png +| | | icon.png +| | | +| | +---import_sun_data +| | | import_data.py +| | | README.md +| | | +| | +---material_editor +| | | | main.py +| | | | +| | | \---ui +| | | checked.png +| | | compile_ui_qt4.bat +| | | compile_ui_qt5.bat +| | | down-arrow.png +| | | main_window.ui +| | | main_window_generated.py +| | | resources.qrc +| | | resources_rc.py +| | | +| | +---pathtracing_reference +| | | | batch_compare.py +| | | | generate_difference.py +| | | | get_brightness.py +| | | | README.md +| | | | run_mitsuba.bat +| | | | run_renderpipeline.py +| | | | +| | | +---config +| | | | daytime.yaml +| | | | debugging.yaml +| | | | panda3d-config.prc +| | | | pipeline.yaml +| | | | plugins.yaml +| | | | stages.yaml +| | | | task-scheduler.yaml +| | | | +| | | \---res +| | | | overlay.png +| | | | scene.templ.xml +| | | | scene.xml +| | | | sphere.bam +| | | | +| | | \---tex +| | | convert_cubemap.py +| | | empty_basecolor.png +| | | empty_normal.png +| | | empty_roughness.png +| | | empty_specular.png +| | | white.png +| | | +| | +---plugin_configurator +| | | | main.py +| | | | +| | | \---ui +| | | | compile_ui_qt4.bat +| | | | compile_ui_qt5.bat +| | | | main_window.ui +| | | | main_window_generated.py +| | | | resources.qrc +| | | | resources_rc.py +| | | | __init__.py +| | | | +| | | \---res +| | | icon.png +| | | pipeline_logo.png +| | | plugin_configurator_logo.png +| | | +| | +---poisson_disk_generator +| | | | .gitignore +| | | | config.ini +| | | | generate_poisson_disk.py +| | | | update_module_builder.py +| | | | +| | | \---source +| | | PoissonDiskGenerator.h +| | | +| | +---render_service +| | | | example_usage.py +| | | | README.md +| | | | service.py +| | | | +| | | +---config +| | | | daytime.yaml +| | | | debugging.yaml +| | | | panda3d-config.prc +| | | | pipeline.yaml +| | | | plugins.yaml +| | | | stages.yaml +| | | | task-scheduler.yaml +| | | | +| | | \---resources +| | | | preview.bam +| | | | sphere.bam +| | | | +| | | \---tex +| | | empty_basecolor.png +| | | empty_normal.png +| | | empty_roughness.png +| | | empty_specular.png +| | | +| | \---rp_distributor +| | launch.templ.bat +| | main.py +| | README.md +| | +| \---__pycache__ +| __init__.cpython-310.pyc +| ++---requirements +| clean-requirements.txt +| conda-requirements.txt +| DEPLOYMENT_README.md +| environment.yml +| requirements.txt +| ++---Resources +| | a +| | +| +---c +| | | a +| | | +| | \---b +| | a.txt +| | b +| | b.txt +| | +| +---icons +| | heightmap.png +| | test_metallic_stripes.png +| | test_roughness_checkerboard.png +| | test_roughness_circle.png +| | test_roughness_gradient.png +| | +| \---textures +| 布料 针织法线贴图.jpg +| 针织 编织 布料法线纹理贴图.jpg +| ++---scene +| | scene_manager.py +| | scene_manager_convert_tiles_mixin.py +| | scene_manager_impl.py +| | scene_manager_io_mixin.py +| | scene_manager_light_mixin.py +| | scene_manager_model_mixin.py +| | scene_manager_serialization_mixin.py +| | tree_roles.py +| | util.py +| | __init__.py +| | +| \---__pycache__ +| scene_manager.cpython-310.pyc +| scene_manager_convert_tiles_mixin.cpython-310.pyc +| scene_manager_impl.cpython-310.pyc +| scene_manager_io_mixin.cpython-310.pyc +| scene_manager_light_mixin.cpython-310.pyc +| scene_manager_model_mixin.cpython-310.pyc +| scene_manager_serialization_mixin.cpython-310.pyc +| tree_roles.cpython-310.pyc +| util.cpython-310.pyc +| __init__.cpython-310.pyc +| ++---scripts +| | a.py +| | BouncerScript.py +| | ColorChangerScript.py +| | ComboAnimatorScript.py +| | example_script.py +| | FollowerScript.py +| | MoverScript.py +| | Rotate_H_Script.py +| | Rotate_P_Script.py +| | Rotate_R_Script.py +| | RotatorScript.py +| | R_P.py +| | R_R.py +| | ScalerScript.py +| | TestMover.py +| | TestRotator.py +| | TestScaler.py +| | test_quick_script.py +| | +| \---__pycache__ +| a.cpython-310.pyc +| BouncerScript.cpython-310.pyc +| ColorChangerScript.cpython-310.pyc +| ComboAnimatorScript.cpython-310.pyc +| example_script.cpython-310.pyc +| FollowerScript.cpython-310.pyc +| MoverScript.cpython-310.pyc +| Rotate_H_Script.cpython-310.pyc +| Rotate_P_Script.cpython-310.pyc +| Rotate_R_Script.cpython-310.pyc +| RotatorScript.cpython-310.pyc +| R_P.cpython-310.pyc +| R_R.cpython-310.pyc +| ScalerScript.cpython-310.pyc +| TestMover.cpython-310.pyc +| TestRotator.cpython-310.pyc +| TestScaler.cpython-310.pyc +| test_quick_script.cpython-310.pyc +| ++---ssbo_component +| | demo_component.py +| | README.md +| | ssbo_controller.py +| | ssbo_editor.py +| | +| +---backup_merge_20260225_125202 +| | ssbo_controller.py.bak +| | ssbo_editor.py.bak +| | +| +---effects +| | ssbo_instancing.yaml +| | +| +---shaders +| | pick_id.frag +| | pick_id.vert +| | +| \---__pycache__ +| ssbo_controller.cpython-310.pyc +| ssbo_controller.cpython-312.pyc +| ssbo_editor.cpython-310.pyc +| ssbo_editor.cpython-312.pyc +| ++---templates +| main_template.py +| ++---test_project +| project.json +| ++---tex +| empty_basecolor.png +| empty_normal.png +| empty_roughness.png +| empty_specular.png +| ++---tools +| open_source_rate.py +| ++---TransformGizmo +| | events.py +| | move_gizmo.py +| | rotate_gizmo.py +| | scale_gizmo.py +| | transform_gizmo.py +| | +| +---docs +| | transform_gizmo_events.md +| | +| \---__pycache__ +| events.cpython-310.pyc +| move_gizmo.cpython-310.pyc +| rotate_gizmo.cpython-310.pyc +| scale_gizmo.cpython-310.pyc +| transform_gizmo.cpython-310.pyc +| ++---ui +| | icon_manager.py +| | lui.pyd +| | lui_function.py +| | lui_manager.py +| | widgets.py +| | +| +---Builtin +| | Elements.py +| | LUIBlockText.py +| | LUIButton.py +| | LUICanvas.py +| | LUICheckbox.py +| | LUIFormattedLabel.py +| | LUIFrame.py +| | LUIHorizontalLayout.py +| | LUIInitialState.py +| | LUIInputField.py +| | LUIInputHandler.py +| | LUILabel.py +| | LUILayouts.py +| | LUIObject.py +| | LUIProgressbar.py +| | LUIRadiobox.py +| | LUIRadioboxGroup.py +| | LUIRegion.py +| | LUIRoot.py +| | LUIScrollableRegion.py +| | LUISelectbox.py +| | LUISkin.py +| | LUISlider.py +| | LUISprite.py +| | LUISpriteButton.py +| | LUITabbedFrame.py +| | LUIVerticalLayout.py +| | RectTransform.py +| | __init__.py +| | +| +---LUI +| | | lui_function_components.py +| | | lui_function_properties.py +| | | lui_manager_editor.py +| | | lui_manager_interaction.py +| | | lui_shared.py +| | | __init__.py +| | | +| | \---__pycache__ +| | lui_function_components.cpython-310.pyc +| | lui_function_properties.cpython-310.pyc +| | lui_manager_editor.cpython-310.pyc +| | lui_manager_interaction.cpython-310.pyc +| | lui_shared.cpython-310.pyc +| | __init__.cpython-310.pyc +| | +| +---panels +| | | animation_tools.py +| | | app_actions.py +| | | create_actions.py +| | | dialog_panels.py +| | | editor_panels.py +| | | interaction_panels.py +| | | object_factory.py +| | | panel_delegates.py +| | | property_helpers.py +| | | runtime_actions.py +| | | script_panels.py +| | | __init__.py +| | | +| | \---__pycache__ +| | animation_tools.cpython-310.pyc +| | app_actions.cpython-310.pyc +| | create_actions.cpython-310.pyc +| | dialog_panels.cpython-310.pyc +| | editor_panels.cpython-310.pyc +| | interaction_panels.cpython-310.pyc +| | object_factory.cpython-310.pyc +| | panel_delegates.cpython-310.pyc +| | property_helpers.cpython-310.pyc +| | runtime_actions.cpython-310.pyc +| | script_panels.cpython-310.pyc +| | __init__.cpython-310.pyc +| | +| +---Skins +| | | __init__.py +| | | +| | +---Default +| | | | GenerateAtlas.bat +| | | | __init__.py +| | | | +| | | +---font +| | | | SourceSansPro-Black.ttf +| | | | SourceSansPro-BlackIt.ttf +| | | | SourceSansPro-Bold.ttf +| | | | SourceSansPro-BoldIt.ttf +| | | | SourceSansPro-ExtraLight.ttf +| | | | SourceSansPro-ExtraLightIt.ttf +| | | | SourceSansPro-It.ttf +| | | | SourceSansPro-Light.ttf +| | | | SourceSansPro-LightIt.ttf +| | | | SourceSansPro-Regular.ttf +| | | | SourceSansPro-Semibold.ttf +| | | | SourceSansPro-SemiboldIt.ttf +| | | | +| | | \---res +| | | atlas.png +| | | atlas.txt +| | | blank.png +| | | ButtonDefault.png +| | | ButtonDefaultFocus.png +| | | ButtonDefaultFocus_Left.png +| | | ButtonDefaultFocus_Right.png +| | | ButtonDefault_Left.png +| | | ButtonDefault_Right.png +| | | ButtonGreen.png +| | | ButtonGreenFocus.png +| | | ButtonGreenFocus_Left.png +| | | ButtonGreenFocus_Right.png +| | | ButtonGreen_Left.png +| | | ButtonGreen_Right.png +| | | Checkbox_Checked.png +| | | Checkbox_CheckedHover.png +| | | Checkbox_Default.png +| | | Checkbox_DefaultHover.png +| | | ColorpickerActiveColorOverlay.png +| | | ColorpickerFieldHandle.png +| | | ColorpickerFieldOverlay.png +| | | ColorpickerHueHandle.png +| | | ColorpickerHueSlider.png +| | | ColorpickerPreviewBg.png +| | | ColorpickerPreviewOverlay.png +| | | Frame_BL.png +| | | Frame_Bottom.png +| | | Frame_BR.png +| | | Frame_Left.png +| | | Frame_Mid.png +| | | Frame_Right.png +| | | Frame_TL.png +| | | Frame_Top.png +| | | Frame_TR.png +| | | HorizontalListDivider.png +| | | InputField.png +| | | InputField_Left.png +| | | InputField_Right.png +| | | Keymarker.png +| | | Keymarker_Left.png +| | | Keymarker_Right.png +| | | ListDivider.png +| | | Popup_BL.png +| | | Popup_Bottom.png +| | | Popup_BR.png +| | | Popup_Left.png +| | | Popup_Mid.png +| | | Popup_Right.png +| | | Popup_TL.png +| | | Popup_Top.png +| | | Popup_TR.png +| | | ProgressbarBg.png +| | | ProgressbarBg_Left.png +| | | ProgressbarBg_Right.png +| | | ProgressbarFg.png +| | | ProgressbarFg_Finish.png +| | | ProgressbarFg_Left.png +| | | ProgressbarFg_Right.png +| | | Radiobox_Active.png +| | | Radiobox_ActiveHover.png +| | | Radiobox_Default.png +| | | Radiobox_DefaultHover.png +| | | ScrollbarHandle.png +| | | ScrollbarHandle_Bottom.png +| | | ScrollbarHandle_Top.png +| | | ScrollShadowBottom.png +| | | ScrollShadowBottom_Left.png +| | | ScrollShadowBottom_Right.png +| | | ScrollShadowTop.png +| | | ScrollShadowTop_Left.png +| | | ScrollShadowTop_Right.png +| | | Selectbox.png +| | | SelectboxActive.png +| | | SelectboxActive_Left.png +| | | SelectboxOpen_Right.png +| | | Selectbox_Left.png +| | | Selectbox_Right.png +| | | SelectdropDivider.png +| | | Selectdrop_BL.png +| | | Selectdrop_Bottom.png +| | | Selectdrop_BR.png +| | | Selectdrop_Left.png +| | | Selectdrop_Mid.png +| | | Selectdrop_Right.png +| | | Selectdrop_TL.png +| | | Selectdrop_Top.png +| | | Selectdrop_TR.png +| | | SliderBg.png +| | | SliderBgFill.png +| | | SliderBgFill_Left.png +| | | SliderBg_Left.png +| | | SliderBg_Right.png +| | | SliderKnob.png +| | | SunkenFrame_BL.png +| | | SunkenFrame_Bottom.png +| | | SunkenFrame_BR.png +| | | SunkenFrame_Left.png +| | | SunkenFrame_Mid.png +| | | SunkenFrame_Right.png +| | | SunkenFrame_TL.png +| | | SunkenFrame_Top.png +| | | SunkenFrame_TR.png +| | | +| | \---Metro +| | | border.png +| | | copy_frames.py +| | | flat.png +| | | GenerateAtlas.bat +| | | LUIMetroSkin.py +| | | __init__.py +| | | +| | +---font +| | | Roboto-Bold.ttf +| | | Roboto-LICENSE.txt +| | | Roboto-Light.ttf +| | | Roboto-Medium.ttf +| | | Roboto-Thin.ttf +| | | +| | \---res +| | atlas.png +| | atlas.txt +| | blank.png +| | ButtonDefault.png +| | ButtonDefaultFocus.png +| | ButtonDefaultFocus_Left.png +| | ButtonDefaultFocus_Right.png +| | ButtonDefault_Left.png +| | ButtonDefault_Right.png +| | ButtonGreen.png +| | ButtonGreenFocus.png +| | ButtonGreenFocus_Left.png +| | ButtonGreenFocus_Right.png +| | ButtonGreen_Left.png +| | ButtonGreen_Right.png +| | Checkbox_Checked.png +| | Checkbox_CheckedHover.png +| | Checkbox_Default.png +| | Checkbox_DefaultHover.png +| | ColorpickerActiveColorOverlay.png +| | ColorpickerFieldHandle.png +| | ColorpickerFieldOverlay.png +| | ColorpickerHueHandle.png +| | ColorpickerHueSlider.png +| | ColorpickerPreviewBg.png +| | ColorpickerPreviewOverlay.png +| | Draft3.psd +| | Frame_BL.png +| | Frame_Bottom.png +| | Frame_BR.png +| | Frame_Left.png +| | Frame_Mid.png +| | Frame_Right.png +| | Frame_TL.png +| | Frame_Top.png +| | Frame_TR.png +| | HorizontalListDivider.png +| | InputField.png +| | InputField_Left.png +| | InputField_Right.png +| | Keymarker.png +| | Keymarker_Left.png +| | Keymarker_Right.png +| | ListDivider.png +| | Popup_BL.png +| | Popup_Bottom.png +| | Popup_BR.png +| | Popup_Left.png +| | Popup_Mid.png +| | Popup_Right.png +| | Popup_TL.png +| | Popup_Top.png +| | Popup_TR.png +| | ProgressbarBg.png +| | ProgressbarBg_Left.png +| | ProgressbarBg_Right.png +| | ProgressbarFg.png +| | ProgressbarFg_Finish.png +| | ProgressbarFg_Left.png +| | ProgressbarFg_Right.png +| | Radiobox_Active.png +| | Radiobox_ActiveHover.png +| | Radiobox_Default.png +| | Radiobox_DefaultHover.png +| | ScrollbarHandle.png +| | ScrollbarHandle_Bottom.png +| | ScrollbarHandle_Top.png +| | ScrollShadowBottom.png +| | ScrollShadowBottom_Left.png +| | ScrollShadowBottom_Right.png +| | ScrollShadowTop.png +| | ScrollShadowTop_Left.png +| | ScrollShadowTop_Right.png +| | Selectbox.png +| | SelectboxActive.png +| | SelectboxActive_Left.png +| | SelectboxOpen_Right.png +| | Selectbox_Left.png +| | Selectbox_Right.png +| | SelectdropDivider.png +| | Selectdrop_BL.png +| | Selectdrop_Bottom.png +| | Selectdrop_BR.png +| | Selectdrop_Left.png +| | Selectdrop_Mid.png +| | Selectdrop_Right.png +| | Selectdrop_TL.png +| | Selectdrop_Top.png +| | Selectdrop_TR.png +| | SliderBg.png +| | SliderBgFill.png +| | SliderBgFill_Left.png +| | SliderBg_Left.png +| | SliderBg_Right.png +| | SliderKnob.png +| | SunkenFrame_BL.png +| | SunkenFrame_Bottom.png +| | SunkenFrame_BR.png +| | SunkenFrame_Left.png +| | SunkenFrame_Mid.png +| | SunkenFrame_Right.png +| | SunkenFrame_TL.png +| | SunkenFrame_Top.png +| | SunkenFrame_TR.png +| | +| \---__pycache__ +| lui_function.cpython-310.pyc +| lui_manager.cpython-310.pyc +| ++---vr_actions +| actions.json +| bindings_index.json +| bindings_oculus.json +| bindings_vive.json +| +\---__pycache__ + main.cpython-310.pyc + +``` + +## Python 脚本用途与行数(全量) + +| 脚本路径 | 行数 | 用途说明 | +|---|---:|---| +| `.\core\__init__.py` | 23 | Core package - 核心功能模块 | +| `.\core\collision_manager.py` | 1050 | 定义核心类: CollisionManager | +| `.\core\Command_System.py` | 576 | 定义核心类: Command | +| `.\core\CustomMouseController.py` | 185 | 定义核心类: CustomMouseController | +| `.\core\event_handler.py` | 554 | 定义核心类: EventHandler | +| `.\core\imgui_style_manager.py` | 428 | ImGui样式管理器 | +| `.\core\imgui_webview.py` | 189 | imgui_webview.py | +| `.\core\InfoPanelManager.py` | 1455 | 定义核心类: InfoPanelManager | +| `.\core\model_drag_drop.py` | 485 | 模型拖拽服务 | +| `.\core\render_pipeline_utils.py` | 17 | 渲染管线工具模块 | +| `.\core\resource_manager.py` | 471 | 资源管理器模块 - ImGui版本 | +| `.\core\script_system.py` | 817 | 脚本系统模块 | +| `.\core\selection.py` | 2455 | 选择和变换系统模块 | +| `.\core\selection_outline.py` | 263 | 定义核心类: SelectionOutlineManager | +| `.\core\terrain_manager.py` | 571 | 定义核心类: TerrainManager | +| `.\core\tool_manager.py` | 133 | 定义核心类: ToolManager | +| `.\core\vr\__init__.py` | 31 | VR模块 - 虚拟现实功能 | +| `.\core\vr\config\__init__.py` | 8 | VR配置子系统 | +| `.\core\vr\config\joystick_config.py` | 226 | VR摇杆配置模块 | +| `.\core\vr\config\shadow_stage.py` | 145 | VR专用阴影Stage | +| `.\core\vr\config\vr_config.py` | 216 | VR配置管理器模块 | +| `.\core\vr\interaction\__init__.py` | 9 | VR交互子系统 | +| `.\core\vr\interaction\actions.py` | 484 | VR动作系统模块 | +| `.\core\vr\interaction\grab.py` | 329 | VR交互系统模块 | +| `.\core\vr\interaction\joystick.py` | 561 | VR摇杆交互系统模块 | +| `.\core\vr\interaction\teleport.py` | 331 | VR传送系统模块 | +| `.\core\vr\performance\__init__.py` | 9 | VR性能优化子系统 | +| `.\core\vr\performance\monitoring.py` | 972 | VR性能监控子系统 | +| `.\core\vr\performance\optimization.py` | 287 | VR对象池和优化系统 | +| `.\core\vr\rendering\__init__.py` | 10 | VR渲染子系统 | +| `.\core\vr\rendering\stages.py` | 712 | VR专用渲染Stages | +| `.\core\vr\testing\__init__.py` | 6 | VR测试调试子系统 | +| `.\core\vr\testing\test_mode.py` | 609 | VR测试调试子系统 | +| `.\core\vr\tracking\__init__.py` | 9 | VR跟踪子系统 | +| `.\core\vr\tracking\controllers.py` | 391 | VR手柄管理模块 | +| `.\core\vr\visualization\__init__.py` | 8 | VR可视化子系统 | +| `.\core\vr\visualization\controllers.py` | 631 | VR可视化模块 | +| `.\core\vr\visualization\effects.py` | 174 | VR Effects Manager - 为VR场景中的模型自动应用RenderPipeline Effects | +| `.\core\vr_manager.py` | 2970 | VR管理器模块 | +| `.\core\world.py` | 1059 | 定义核心类: CoreWorld | +| `.\gui\__init__.py` | 7 | GUI包 | +| `.\main.py` | 770 | 定义核心类: MyWorld | +| `.\project\__init__.py` | 10 | 项目管理包 - 提供项目生命周期管理功能 | +| `.\project\project_manager.py` | 749 | 定义核心类: ProjectManager | +| `.\RenderPipelineFile\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\data\default_cubemap\filter.py` | 80 | RenderPipeline | +| `.\RenderPipelineFile\data\environment_brdf\generate_reference.py` | 91 | Uses mitsuba to generate the environment brdf | +| `.\RenderPipelineFile\data\film_grain\generate.py` | 59 | RenderPipeline | +| `.\RenderPipelineFile\data\generate_txo_files.py` | 26 | Converts pipeline resources to TXO files to speed up loading. | +| `.\RenderPipelineFile\data\scripts\example_script.py` | 44 | 示例脚本 - 演示如何编写脚本 | +| `.\RenderPipelineFile\data\setup\check_requirements.py` | 44 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\__init__.py` | 35 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\common_resources.py` | 201 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\effect.py` | 245 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\globals.py` | 35 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\gpu_command_queue.py` | 94 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\gui\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpcore\gui\buffer_viewer.py` | 211 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\gui\checkbox.py` | 100 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\gui\checkbox_collection.py` | 52 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\gui\debugger.py` | 272 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\gui\draggable_window.py` | 139 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\gui\error_message_display.py` | 75 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\gui\exposure_widget.py` | 74 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\gui\fps_chart.py` | 103 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\gui\labeled_checkbox.py` | 65 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\gui\loading_screen.py` | 53 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\gui\pipe_viewer.py` | 182 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\gui\pixel_inspector.py` | 64 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\gui\render_mode_selector.py` | 102 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\gui\slider.py` | 52 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\gui\sprite.py` | 114 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\gui\text.py` | 62 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\gui\text_node.py` | 66 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\gui\texture_preview.py` | 142 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\image.py` | 128 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\light_manager.py` | 164 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\loader.py` | 113 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\mount_manager.py` | 258 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\native\__init__.py` | 76 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\native\update_module_builder.py` | 42 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\pluginbase\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpcore\pluginbase\base_plugin.py` | 78 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\pluginbase\day_manager.py` | 79 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\pluginbase\day_setting_types.py` | 122 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\pluginbase\manager.py` | 239 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\pluginbase\setting_types.py` | 186 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\pynative\__init__.py` | 30 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\pynative\gpu_command.py` | 64 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\pynative\gpu_command_list.py` | 35 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\pynative\ies_dataset.py` | 72 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\pynative\internal_light_manager.py` | 167 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\pynative\pointer_slot_storage.py` | 69 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\pynative\pssm_camera_rig.py` | 76 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\pynative\rp_light.py` | 149 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\pynative\rp_point_light.py` | 59 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\pynative\rp_spot_light.py` | 67 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\pynative\shadow_atlas.py` | 75 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\pynative\shadow_manager.py` | 91 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\pynative\shadow_source.py` | 79 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\pynative\tag_state_manager.py` | 79 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\render_pipeline.py` | 595 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\render_stage.py` | 147 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\render_target.py` | 324 | RenderTarget | +| `.\RenderPipelineFile\rpcore\rpobject.py` | 117 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\stage_manager.py` | 231 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\stages\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpcore\stages\ambient_stage.py` | 35 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\stages\apply_lights_stage.py` | 34 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\stages\collect_used_cells_stage.py` | 56 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\stages\combine_velocity_stage.py` | 33 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\stages\cull_lights_stage.py` | 108 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\stages\downscale_z_stage.py` | 33 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\stages\final_stage.py` | 46 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\stages\flag_used_cells_stage.py` | 43 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\stages\gbuffer_stage.py` | 50 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\stages\shadow_stage.py` | 58 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\stages\update_previous_pipes_stage.py` | 101 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\stages\upscale_stage.py` | 36 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\util\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpcore\util\cubemap_filter.py` | 158 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\util\display_shader_builder.py` | 130 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\util\generic.py` | 88 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\util\ies_profile_loader.py` | 207 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\util\movement_controller.py` | 233 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\util\network_communication.py` | 126 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\util\post_process_region.py` | 91 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\util\shader_input_blocks.py` | 151 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\util\smooth_connected_curve.py` | 110 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\util\submodule_downloader.py` | 117 | RenderPipeline | +| `.\RenderPipelineFile\rpcore\util\task_scheduler.py` | 60 | RenderPipeline | +| `.\RenderPipelineFile\rplibs\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rplibs\colorama\__init__.py` | 5 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rplibs\colorama\ansi.py` | 83 | 定义核心类: AnsiCodes | +| `.\RenderPipelineFile\rplibs\colorama\ansitowin32.py` | 197 | 定义核心类: StreamWrapper | +| `.\RenderPipelineFile\rplibs\colorama\initialise.py` | 58 | 函数模块(入口函数: reset_all) | +| `.\RenderPipelineFile\rplibs\colorama\win32.py` | 135 | 定义核心类: CONSOLE_SCREEN_BUFFER_INFO | +| `.\RenderPipelineFile\rplibs\colorama\winterm.py` | 144 | 定义核心类: WinColor | +| `.\RenderPipelineFile\rplibs\progressbar\__init__.py` | 238 | 定义核心类: UnknownLength | +| `.\RenderPipelineFile\rplibs\progressbar\compat.py` | 36 | 函数模块(入口函数: next) | +| `.\RenderPipelineFile\rplibs\progressbar\widgets.py` | 221 | 定义核心类: Widget | +| `.\RenderPipelineFile\rplibs\pyqt_imports.py` | 50 | Wrapper script to import all qt classes | +| `.\RenderPipelineFile\rplibs\six.py` | 808 | 定义核心类: X | +| `.\RenderPipelineFile\rplibs\yaml\__init__.py` | 48 | Main PyYAML importer script. Provides functions to load YAML files as dictionaries. | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py2\__init__.py` | 281 | 定义核心类: YAMLObjectMetaclass | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py2\composer.py` | 117 | 定义核心类: ComposerError | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py2\constructor.py` | 589 | 定义核心类: ConstructorError | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py2\cyaml.py` | 67 | 定义核心类: CBaseLoader | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py2\dumper.py` | 53 | 定义核心类: BaseDumper | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py2\emitter.py` | 1023 | 定义核心类: EmitterError | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py2\error.py` | 65 | 定义核心类: Mark | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py2\events.py` | 69 | 定义核心类: Event | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py2\loader.py` | 31 | 定义核心类: BaseLoader | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py2\nodes.py` | 43 | 定义核心类: Node | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py2\parser.py` | 532 | 定义核心类: ParserError | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py2\reader.py` | 170 | 定义核心类: ReaderError | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py2\representer.py` | 404 | 定义核心类: RepresenterError | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py2\resolver.py` | 200 | 定义核心类: ResolverError | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py2\scanner.py` | 1244 | 定义核心类: ScannerError | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py2\serializer.py` | 97 | 定义核心类: SerializerError | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py2\tokens.py` | 81 | 定义核心类: Token | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py3\__init__.py` | 278 | 定义核心类: YAMLObjectMetaclass | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py3\composer.py` | 117 | 定义核心类: ComposerError | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py3\constructor.py` | 600 | 定义核心类: ConstructorError | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py3\cyaml.py` | 67 | 定义核心类: CBaseLoader | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py3\dumper.py` | 53 | 定义核心类: BaseDumper | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py3\emitter.py` | 1020 | 定义核心类: EmitterError | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py3\error.py` | 65 | 定义核心类: Mark | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py3\events.py` | 69 | 定义核心类: Event | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py3\loader.py` | 31 | 定义核心类: BaseLoader | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py3\nodes.py` | 43 | 定义核心类: Node | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py3\parser.py` | 532 | 定义核心类: ParserError | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py3\reader.py` | 172 | 定义核心类: ReaderError | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py3\representer.py` | 311 | 定义核心类: RepresenterError | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py3\resolver.py` | 200 | 定义核心类: ResolverError | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py3\scanner.py` | 1235 | 定义核心类: ScannerError | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py3\serializer.py` | 97 | 定义核心类: SerializerError | +| `.\RenderPipelineFile\rplibs\yaml\yaml_py3\tokens.py` | 81 | 定义核心类: Token | +| `.\RenderPipelineFile\rpplugins\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpplugins\ao\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpplugins\ao\ao_stage.py` | 89 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\ao\plugin.py` | 33 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\bloom\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpplugins\bloom\bloom_stage.py` | 95 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\bloom\plugin.py` | 35 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\clouds\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpplugins\clouds\apply_clouds_stage.py` | 52 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\clouds\cloud_voxel_stage.py` | 67 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\clouds\plugin.py` | 56 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\clouds\resources\precompute.py` | 70 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\color_correction\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpplugins\color_correction\auto_exposure_stage.py` | 87 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\color_correction\color_correction_stage.py` | 32 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\color_correction\manual_exposure_stage.py` | 33 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\color_correction\plugin.py` | 72 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\color_correction\resources\generate_default_lut.py` | 37 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\color_correction\resources\generate_film_luts.py` | 56 | Script to convert spi1d files to luts | +| `.\RenderPipelineFile\rpplugins\color_correction\sharpen_stage.py` | 55 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\color_correction\tonemapping_stage.py` | 32 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\dof\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpplugins\dof\dof_stage.py` | 78 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\dof\plugin.py` | 34 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\env_probes\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpplugins\env_probes\apply_envprobes_stage.py` | 39 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\env_probes\cull_probes_stage.py` | 55 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\env_probes\environment_capture_stage.py` | 159 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\env_probes\environment_probe.py` | 107 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\env_probes\plugin.py` | 87 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\env_probes\probe_manager.py` | 82 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\env_probes\shader\generate_mip_shaders.py` | 14 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpplugins\forward_shading\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpplugins\forward_shading\forward_stage.py` | 54 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\forward_shading\plugin.py` | 39 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\fxaa\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpplugins\fxaa\fxaa_stage.py` | 38 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\fxaa\plugin.py` | 30 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\motion_blur\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpplugins\motion_blur\motion_blur_stage.py` | 74 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\motion_blur\plugin.py` | 31 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\plugin_prefab\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpplugins\plugin_prefab\demo_stage.py` | 33 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\plugin_prefab\plugin.py` | 41 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\pssm\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpplugins\pssm\plugin.py` | 137 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\pssm\pssm_dist_shadow_stage.py` | 108 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\pssm\pssm_scene_shadow_stage.py` | 93 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\pssm\pssm_shadow_stage.py` | 71 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\pssm\pssm_stage.py` | 48 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\scattering\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpplugins\scattering\godray_stage.py` | 34 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\scattering\plugin.py` | 68 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\scattering\resources\hosek_wilkie_scattering\generate_table.py` | 21 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\scattering\resources\hosek_wilkie_scattering\update_module_builder.py` | 42 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\scattering\scattering_envmap_stage.py` | 49 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\scattering\scattering_methods.py` | 202 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\scattering\scattering_stage.py` | 34 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\selection_outline\__init__.py` | 1 | Selection outline plugin package. | +| `.\RenderPipelineFile\rpplugins\selection_outline\plugin.py` | 9 | 定义核心类: Plugin | +| `.\RenderPipelineFile\rpplugins\selection_outline\selection_outline_stage.py` | 54 | 定义核心类: SelectionOutlineStage | +| `.\RenderPipelineFile\rpplugins\skin_shading\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpplugins\skin_shading\plugin.py` | 29 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\skin_shading\skin_shading_stage.py` | 51 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\sky_ao\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpplugins\sky_ao\ao_stage.py` | 45 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\sky_ao\capture_stage.py` | 71 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\sky_ao\plugin.py` | 43 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\smaa\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpplugins\smaa\jitters.py` | 115 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\smaa\plugin.py` | 82 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\smaa\smaa_stage.py` | 84 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\ssr\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpplugins\ssr\plugin.py` | 35 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\ssr\ssr_stage.py` | 69 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\volumetrics\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpplugins\volumetrics\plugin.py` | 34 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\volumetrics\volumetrics_stage.py` | 54 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\vxgi\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\rpplugins\vxgi\plugin.py` | 71 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\vxgi\voxelization_stage.py` | 163 | RenderPipeline | +| `.\RenderPipelineFile\rpplugins\vxgi\vxgi_stage.py` | 84 | RenderPipeline | +| `.\RenderPipelineFile\samples\download_samples.py` | 31 | RenderPipeline | +| `.\RenderPipelineFile\setup.py` | 248 | RenderPipeline | +| `.\RenderPipelineFile\start_daytime_editor.py` | 10 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\start_plugin_configurator.py` | 10 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\toolkit\bake_gi\bake.py` | 210 | Precomputed GI using spherical harmonics. | +| `.\RenderPipelineFile\toolkit\bake_gi\display.py` | 71 | Test-script to view the baked GI | +| `.\RenderPipelineFile\toolkit\day_time_editor\curve_widget.py` | 272 | RenderPipeline | +| `.\RenderPipelineFile\toolkit\day_time_editor\main.py` | 229 | RenderPipeline | +| `.\RenderPipelineFile\toolkit\day_time_editor\ui\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\toolkit\day_time_editor\ui\main_window_generated.py` | 462 | 定义核心类: Ui_MainWindow | +| `.\RenderPipelineFile\toolkit\day_time_editor\ui\point_insert_dialog_generated.py` | 49 | 定义核心类: Ui_Dialog | +| `.\RenderPipelineFile\toolkit\day_time_editor\ui\resources_rc.py` | 1384 | 函数模块(入口函数: qInitResources) | +| `.\RenderPipelineFile\toolkit\import_sun_data\import_data.py` | 78 | This script downloads real world sun position data and imports them into the | +| `.\RenderPipelineFile\toolkit\material_editor\main.py` | 273 | RenderPipeline | +| `.\RenderPipelineFile\toolkit\material_editor\ui\main_window_generated.py` | 354 | 定义核心类: Ui_MainWindow | +| `.\RenderPipelineFile\toolkit\material_editor\ui\resources_rc.py` | 80 | 函数模块(入口函数: qInitResources) | +| `.\RenderPipelineFile\toolkit\pathtracing_reference\batch_compare.py` | 164 | Compares various results | +| `.\RenderPipelineFile\toolkit\pathtracing_reference\generate_difference.py` | 26 | Compares both renders | +| `.\RenderPipelineFile\toolkit\pathtracing_reference\get_brightness.py` | 22 | 函数模块(入口函数: approx) | +| `.\RenderPipelineFile\toolkit\pathtracing_reference\res\tex\convert_cubemap.py` | 53 | Converts the cubemap to a spherical one | +| `.\RenderPipelineFile\toolkit\pathtracing_reference\run_renderpipeline.py` | 69 | Renders the sphere using the render pipeline | +| `.\RenderPipelineFile\toolkit\plugin_configurator\main.py` | 371 | RenderPipeline | +| `.\RenderPipelineFile\toolkit\plugin_configurator\ui\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\RenderPipelineFile\toolkit\plugin_configurator\ui\main_window_generated.py` | 784 | 定义核心类: Ui_MainWindow | +| `.\RenderPipelineFile\toolkit\plugin_configurator\ui\resources_rc.py` | 1053 | 函数模块(入口函数: qInitResources) | +| `.\RenderPipelineFile\toolkit\poisson_disk_generator\generate_poisson_disk.py` | 26 | RenderPipeline | +| `.\RenderPipelineFile\toolkit\poisson_disk_generator\update_module_builder.py` | 42 | RenderPipeline | +| `.\RenderPipelineFile\toolkit\render_service\example_usage.py` | 46 | Example of using the render service to render a simple sphere. | +| `.\RenderPipelineFile\toolkit\render_service\service.py` | 137 | Render service to render previews of materials | +| `.\RenderPipelineFile\toolkit\rp_distributor\main.py` | 121 | Tool to distribute the rp and the panda3d build. | +| `.\scene\__init__.py` | 10 | 场景管理包 - 提供场景和模型管理功能 | +| `.\scene\scene_manager.py` | 17 | Scene manager entry module. | +| `.\scene\scene_manager_convert_tiles_mixin.py` | 473 | Scene manager conversion and tileset operations. | +| `.\scene\scene_manager_impl.py` | 15 | Scene manager implementation composed from focused mixins. | +| `.\scene\scene_manager_io_mixin.py` | 962 | Scene manager load/save and GUI rebuild operations. | +| `.\scene\scene_manager_light_mixin.py` | 403 | Scene manager light operations. | +| `.\scene\scene_manager_model_mixin.py` | 919 | Scene manager model and geometry operations. | +| `.\scene\scene_manager_serialization_mixin.py` | 749 | Scene manager node serialization operations. | +| `.\scene\tree_roles.py` | 3 | Shared tree data-role constants. | +| `.\scene\util.py` | 241 | 跨平台路径处理工具 | +| `.\scripts\a.py` | 25 | a - 自定义脚本 | +| `.\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 | test_quick_script - 自定义脚本 | +| `.\scripts\TestMover.py` | 38 | TestMover - 移动脚本 | +| `.\scripts\TestRotator.py` | 25 | TestRotator - 自定义脚本 | +| `.\scripts\TestScaler.py` | 25 | TestScaler - 自定义脚本 | +| `.\ssbo_component\demo_component.py` | 70 | SSBO Editor Component Demo | +| `.\ssbo_component\ssbo_controller.py` | 1055 | 定义核心类: ObjectController | +| `.\ssbo_component\ssbo_editor.py` | 782 | 定义核心类: SSBOEditor | +| `.\Start_Run.py` | 42 | 通用脚本模块(无模块说明) | +| `.\templates\main_template.py` | 1282 | 打包 | +| `.\tools\open_source_rate.py` | 73 | 函数模块(入口函数: is_third_party) | +| `.\TransformGizmo\events.py` | 14 | 定义核心类: GizmoEvent | +| `.\TransformGizmo\move_gizmo.py` | 950 | 定义核心类: MoveGizmo | +| `.\TransformGizmo\rotate_gizmo.py` | 1412 | 定义核心类: RotateGizmo | +| `.\TransformGizmo\scale_gizmo.py` | 860 | 定义核心类: ScaleGizmo | +| `.\TransformGizmo\transform_gizmo.py` | 469 | 定义核心类: TransformGizmoMode | +| `.\ui\Builtin\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\ui\Builtin\Elements.py` | 242 | OUTDATED | +| `.\ui\Builtin\LUIBlockText.py` | 74 | 定义核心类: LUIBlockText | +| `.\ui\Builtin\LUIButton.py` | 169 | 定义核心类: LUIButton | +| `.\ui\Builtin\LUICanvas.py` | 97 | LUICanvas - 类似Unity的Canvas容器,带有可视化边框 | +| `.\ui\Builtin\LUICheckbox.py` | 65 | 定义核心类: LUICheckbox | +| `.\ui\Builtin\LUIFormattedLabel.py` | 38 | 定义核心类: LUIFormattedLabel | +| `.\ui\Builtin\LUIFrame.py` | 54 | 定义核心类: LUIFrame | +| `.\ui\Builtin\LUIHorizontalLayout.py` | 13 | This is a wrapper file. It contains no actual implementation | +| `.\ui\Builtin\LUIInitialState.py` | 32 | 定义核心类: LUIInitialState | +| `.\ui\Builtin\LUIInputField.py` | 180 | 定义核心类: LUIInputField | +| `.\ui\Builtin\LUIInputHandler.py` | 5 | This is a wrapper file. It contains no actual implementation | +| `.\ui\Builtin\LUILabel.py` | 63 | 定义核心类: LUILabel | +| `.\ui\Builtin\LUILayouts.py` | 80 | 定义核心类: LUICornerLayout | +| `.\ui\Builtin\LUIObject.py` | 12 | This is a wrapper file. It contains no actual implementation | +| `.\ui\Builtin\LUIProgressbar.py` | 54 | 定义核心类: LUIProgressbar | +| `.\ui\Builtin\LUIRadiobox.py` | 73 | 定义核心类: LUIRadiobox | +| `.\ui\Builtin\LUIRadioboxGroup.py` | 31 | 定义核心类: LUIRadioboxGroup | +| `.\ui\Builtin\LUIRegion.py` | 5 | This is a wrapper file. It contains no actual implementation | +| `.\ui\Builtin\LUIRoot.py` | 5 | This is a wrapper file. It contains no actual implementation | +| `.\ui\Builtin\LUIScrollableRegion.py` | 119 | 定义核心类: LUIScrollableRegion | +| `.\ui\Builtin\LUISelectbox.py` | 158 | 定义核心类: LUISelectbox | +| `.\ui\Builtin\LUISkin.py` | 34 | 定义核心类: LUISkin | +| `.\ui\Builtin\LUISlider.py` | 185 | 定义核心类: LUISlider | +| `.\ui\Builtin\LUISprite.py` | 12 | This is a wrapper file. It contains no actual implementation | +| `.\ui\Builtin\LUISpriteButton.py` | 23 | 定义核心类: LUISpriteButton | +| `.\ui\Builtin\LUITabbedFrame.py` | 77 | 定义核心类: LUITabbedFrame | +| `.\ui\Builtin\LUIVerticalLayout.py` | 13 | This is a wrapper file. It contains no actual implementation | +| `.\ui\Builtin\RectTransform.py` | 73 | RectTransform - 一个轻量级的 Unity-like RectTransform 实现(Python 层 MVP) | +| `.\ui\icon_manager.py` | 123 | Icon manager compatibility layer for the ImGui-only runtime. | +| `.\ui\LUI\__init__.py` | 1 | LUI split modules. | +| `.\ui\LUI\lui_function_components.py` | 1149 | luiFunction component and HTTP creation mixin. | +| `.\ui\LUI\lui_function_properties.py` | 1601 | luiFunction component property drawing mixin. | +| `.\ui\LUI\lui_manager_editor.py` | 1593 | LUIManager tree/editor/layout mixin. | +| `.\ui\LUI\lui_manager_interaction.py` | 1351 | LUIManager interaction and resize mixin. | +| `.\ui\LUI\lui_shared.py` | 62 | Shared LUI bootstrap and imports. | +| `.\ui\lui_function.py` | 31 | 定义核心类: luiFunction | +| `.\ui\lui_manager.py` | 199 | 定义核心类: LUIManager | +| `.\ui\panels\__init__.py` | 0 | 通用脚本模块(无模块说明) | +| `.\ui\panels\animation_tools.py` | 1390 | 定义核心类: _BoundAnimationProxy | +| `.\ui\panels\app_actions.py` | 1097 | 定义核心类: AppActions | +| `.\ui\panels\create_actions.py` | 147 | 定义核心类: CreateActions | +| `.\ui\panels\dialog_panels.py` | 992 | 定义核心类: DialogPanels | +| `.\ui\panels\editor_panels.py` | 2311 | 定义核心类: EditorPanels | +| `.\ui\panels\interaction_panels.py` | 128 | 定义核心类: InteractionPanels | +| `.\ui\panels\object_factory.py` | 390 | 定义核心类: ObjectFactory | +| `.\ui\panels\panel_delegates.py` | 512 | Delegated panel/runtime methods for MyWorld. | +| `.\ui\panels\property_helpers.py` | 1583 | 定义核心类: PropertyHelpers | +| `.\ui\panels\runtime_actions.py` | 336 | 定义核心类: RuntimeActions | +| `.\ui\panels\script_panels.py` | 287 | 定义核心类: ScriptPanels | +| `.\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 | 定义核心类: LUIMetroSkin | +| `.\ui\widgets.py` | 38 | Legacy widgets module (removed). | diff --git a/QT_TO_IMGUI_MIGRATION_CHECKLIST.md b/QT_TO_IMGUI_MIGRATION_CHECKLIST.md new file mode 100644 index 00000000..a364c9f5 --- /dev/null +++ b/QT_TO_IMGUI_MIGRATION_CHECKLIST.md @@ -0,0 +1,62 @@ +# 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 main.py` 能进入编辑器主界面。 +- 场景导入/保存/加载流程不触发 Qt 相关异常。 +- 关键交互(选择、变换、场景树)行为与清理前一致。 + +## 关联文档 + +- 模块分析: [IMGUI_MODULE_ANALYSIS.md](d:/IMGUI/EG/IMGUI_MODULE_ANALYSIS.md) +- 全量目录与脚本清单: [PROJECT_FULL_CATALOG.md](d:/IMGUI/EG/PROJECT_FULL_CATALOG.md) diff --git a/core/event_handler.py b/core/event_handler.py index a958daf3..8902c525 100644 --- a/core/event_handler.py +++ b/core/event_handler.py @@ -14,6 +14,43 @@ class EventHandler: self.showRay = False # 是否显示射线(默认关闭) self.rayNode = None # 当前显示的射线节点 self.rayLifetime = 2.0 # 射线显示时间(秒) + + def _get_interface_manager(self): + """获取兼容层中的场景树管理器(若存在)。""" + return getattr(self.world, "interface_manager", None) + + def _get_tree_widget(self): + """安全获取场景树控件。""" + interface_manager = self._get_interface_manager() + if not interface_manager: + return None + return getattr(interface_manager, "treeWidget", None) + + def _sync_tree_selection(self, selected_model): + """将当前选中模型同步到场景树。""" + interface_manager = self._get_interface_manager() + tree_widget = self._get_tree_widget() + if not interface_manager or not tree_widget: + return False + + root = tree_widget.invisibleRootItem() + found_item = None + + for i in range(root.childCount()): + scene_item = root.child(i) + if scene_item.text(0) != "场景": + continue + found_item = interface_manager.findTreeItem(selected_model, scene_item) + if found_item: + try: + tree_widget.itemClicked.disconnect() + except TypeError: + pass + tree_widget.setCurrentItem(found_item) + tree_widget.itemClicked.connect(interface_manager.onTreeItemClicked) + return True + break + return False def showClickRay(self, nearPoint, farPoint, hitPos=None): """显示鼠标点击的射线""" @@ -239,17 +276,22 @@ class EventHandler: # 处理GUI编辑模式 if self.world.guiEditMode: + gui_manager = getattr(self.world, "gui_manager", None) + if not gui_manager: + print("GUI编辑模式已开启,但 gui_manager 不可用") + pickerNP.removeNode() + return #print("处理GUI编辑模式点击") # 检查是否点击了GUI元素 - clickedGUI = self.world.gui_manager.findClickedGUI(hitNode) + clickedGUI = gui_manager.findClickedGUI(hitNode) if clickedGUI: # 选中GUI元素 self.world.selection.updateSelection(clickedGUI) - self.world.gui_manager.selectGUIInTree(clickedGUI) + gui_manager.selectGUIInTree(clickedGUI) print(f"选中GUI元素: {clickedGUI.getTag('gui_text')}") elif hasattr(self.world, 'currentGUITool') and self.world.currentGUITool: # 在点击位置创建新GUI元素 - self.world.gui_manager.createGUIAtPosition(hitPos, self.world.currentGUITool) + gui_manager.createGUIAtPosition(hitPos, self.world.currentGUITool) pickerNP.removeNode() return @@ -280,7 +322,9 @@ class EventHandler: default_height = 0.0 world_pos = Point3(mx * 10, 0, my * 10) # 简单的屏幕到世界坐标转换 world_pos.setZ(default_height) - self.world.gui_manager.createGUIAtPosition(world_pos, self.world.currentGUITool) + gui_manager = getattr(self.world, "gui_manager", None) + if gui_manager: + gui_manager.createGUIAtPosition(world_pos, self.world.currentGUITool) # 确保总是清理碰撞检测节点 try: @@ -488,35 +532,12 @@ class EventHandler: self.world.selection.updateSelection(selectedModel) # 在树形控件中查找并选中对应的项 - if hasattr(self.world, 'interface_manager') and self.world.interface_manager and hasattr(self.world.interface_manager, 'treeWidget') and self.world.interface_manager.treeWidget: - #print("查找树形控件中的对应项...") - root = self.world.interface_manager.treeWidget.invisibleRootItem() - foundItem = None - - for i in range(root.childCount()): - sceneItem = root.child(i) - if sceneItem.text(0) == "场景": - #print(f"在场景节点下查找...") - foundItem = self.world.interface_manager.findTreeItem(selectedModel, sceneItem) - if foundItem: - print(f"✓ 在树形控件中找到对应项: {foundItem.text(0)}") - try: - self.world.interface_manager.treeWidget.itemClicked.disconnect() - except TypeError: - pass - - self.world.interface_manager.treeWidget.setCurrentItem(foundItem) - - self.world.interface_manager.treeWidget.itemClicked.connect( - self.world.interface_manager.onTreeItemClicked) - else: - print("× 在树形控件中没有找到对应项") - break - - if not foundItem: - print("× 没有找到场景节点或对应的树形项") + if self._sync_tree_selection(selectedModel): + tree_widget = self._get_tree_widget() + if tree_widget and tree_widget.currentItem(): + print(f"✓ 在树形控件中找到对应项: {tree_widget.currentItem().text(0)}") else: - print("× 树形控件不存在") + print("× 场景树未同步(接口不可用或未找到对应项)") else: print("× 没有找到可选择的模型节点") self.world.selection.updateSelection(None) @@ -534,11 +555,12 @@ class EventHandler: super(type(self.world), self.world).wheelForward(data) # 更新属性面板 - if (self.world.interface_manager.treeWidget and - self.world.interface_manager.treeWidget.currentItem() and - self.world.interface_manager.treeWidget.currentItem().text(0) == "相机"): - self.world.property_panel.updatePropertyPanel( - self.world.interface_manager.treeWidget.currentItem()) + tree_widget = self._get_tree_widget() + current_item = tree_widget.currentItem() if tree_widget else None + if (current_item and + current_item.text(0) == "相机" and + hasattr(self.world, "property_panel")): + self.world.property_panel.updatePropertyPanel(current_item) def wheelBackward(self, data=None): """处理滚轮向后滚动(后退)""" @@ -546,11 +568,12 @@ class EventHandler: super(type(self.world), self.world).wheelBackward(data) # 更新属性面板 - if (self.world.interface_manager.treeWidget and - self.world.interface_manager.treeWidget.currentItem() and - self.world.interface_manager.treeWidget.currentItem().text(0) == "相机"): - self.world.property_panel.updatePropertyPanel( - self.world.interface_manager.treeWidget.currentItem()) + tree_widget = self._get_tree_widget() + current_item = tree_widget.currentItem() if tree_widget else None + if (current_item and + current_item.text(0) == "相机" and + hasattr(self.world, "property_panel")): + self.world.property_panel.updatePropertyPanel(current_item) def mousePressEventMiddle(self, evt): """处理鼠标中键按下事件""" diff --git a/core/selection.py b/core/selection.py index e56baba8..5ebe0e84 100644 --- a/core/selection.py +++ b/core/selection.py @@ -90,6 +90,22 @@ class SelectionSystem: self._double_click_task = None print("✓ 选择和变换系统初始化完成") + + def _get_tree_widget(self): + """统一获取场景树控件。""" + interface_manager = getattr(self.world, "interface_manager", None) + if not interface_manager: + return None + return getattr(interface_manager, "treeWidget", None) + + def _clear_tree_selection(self): + """清空场景树选中项。""" + tree_widget = self._get_tree_widget() + if not tree_widget: + return False + tree_widget.setCurrentItem(None) + return True + # ==================== 光标设置 ==================== def _setCursor(self,cursor_type): try: @@ -2103,10 +2119,7 @@ class SelectionSystem: print("✓ 取消选择") #当取消选择时,同步清空树形控件的选中状态 - if (hasattr(self.world,'interface_manager')and - self.world.interface_manager and - self.world.interface_manager.treeWidget): - self.world.interface_manager.treeWidget.setCurrentItem(None) + if self._clear_tree_selection(): print("✓ 树形控件选中状态已清空") #print("=== 选择状态更新完成 ===\n") @@ -2955,11 +2968,7 @@ class SelectionSystem: self.clearGizmo() # 清除树形控件中的选择 - if (hasattr(self.world, 'interface_manager') and - self.world.interface_manager and - hasattr(self.world.interface_manager, 'treeWidget') and - self.world.interface_manager.treeWidget): - self.world.interface_manager.treeWidget.setCurrentItem(None) + self._clear_tree_selection() print("已清除选择") except Exception as e: diff --git a/main.py b/main.py index d656757d..0686ac0a 100644 --- a/main.py +++ b/main.py @@ -68,6 +68,13 @@ class MyWorld(PanelDelegates, CoreWorld): #props.set_maximized(True) self.win.request_properties(props) print("✓ 窗口已设置为最大化模式") + + # Legacy compatibility fields used by scene/project modules. + self.gui_elements = [] + self.gui_manager = None + self.interface_manager = None + self.guiEditMode = False + self.currentGUITool = None # 初始化选择和变换系统 self.selection = SelectionSystem(self) @@ -817,5 +824,6 @@ class MyWorld(PanelDelegates, CoreWorld): except Exception as e: print(f"绑定 TransformGizmo 灯光同步事件失败: {e}") -demo = MyWorld() -demo.run() +if __name__ == "__main__": + demo = MyWorld() + demo.run() diff --git a/scene/scene_manager_io_mixin.py b/scene/scene_manager_io_mixin.py index f6ebb7b9..b6db76dd 100644 --- a/scene/scene_manager_io_mixin.py +++ b/scene/scene_manager_io_mixin.py @@ -811,7 +811,7 @@ class SceneManagerIOMixin: loaded_class = script_manager.load_script_from_file(script_file) if loaded_class is None: print(f"从文件加载脚本失败{script_file}") - script_path = self._find_scrip_in_directory(script_name) + script_path = self._find_script_in_directory(script_name) if script_path: print(f"从目录找到脚本并加载{script_path}") script_manager.load_script_from_file(script_path) diff --git a/ui/panels/editor_panels.py b/ui/panels/editor_panels.py index de23cdb6..fe64559d 100644 --- a/ui/panels/editor_panels.py +++ b/ui/panels/editor_panels.py @@ -1,23 +1,20 @@ -from imgui_bundle import imgui, imgui_ctx -import os -import io -import tempfile -import time -from pathlib import Path -from panda3d.core import Filename -try: - from PIL import Image -except Exception: # pragma: no cover - pillow may be missing in minimal env - Image = None +from ui.panels.editor_panels_center import EditorPanelsCenterMixin +from ui.panels.editor_panels_left import EditorPanelsLeftMixin +from ui.panels.editor_panels_right import EditorPanelsRightMixin +from ui.panels.editor_panels_top import EditorPanelsTopMixin -class EditorPanels: +class EditorPanels( + EditorPanelsTopMixin, + EditorPanelsLeftMixin, + EditorPanelsRightMixin, + EditorPanelsCenterMixin, +): """Centralized UI panel renderer for top-level editor panels.""" def __init__(self, app): self.app = app - def __getattr__(self, name): return getattr(self.app, name) @@ -26,2438 +23,3 @@ class EditorPanels: object.__setattr__(self, name, value) else: setattr(self.app, name, value) - - def _on_create_web_panel(self): - """创建或激活 ImGui Web 面板。""" - self._ensure_web_panel_state() - self.app.showWebPanel = True - - webview = getattr(self.app, "_imgui_webview", None) - if webview and getattr(webview, "_running", False): - return True - - return self._start_imgui_webview(self.app.web_panel_url_input) - - def _ensure_web_panel_state(self): - if not hasattr(self.app, "web_panel_url_input") or not self.app.web_panel_url_input: - self.app.web_panel_url_input = "https://www.baidu.com" - if not hasattr(self.app, "_imgui_webview"): - self.app._imgui_webview = None - if not hasattr(self.app, "_imgui_webview_tex_id"): - self.app._imgui_webview_tex_id = None - if not hasattr(self.app, "_imgui_webview_texture_path"): - self.app._imgui_webview_texture_path = None - - def _start_imgui_webview(self, url): - self._ensure_web_panel_state() - self._stop_imgui_webview() - try: - target_url = (url or "").strip() - if not target_url: - target_url = "https://www.example.com" - if not target_url.startswith(("http://", "https://", "file://")): - target_url = "https://" + target_url - - from core.imgui_webview import ImGuiWebView - webview = ImGuiWebView(width=1280, height=720) - webview.start(target_url) - self.app._imgui_webview = webview - return True - except Exception as e: - self.app.add_error_message(f"启动Web视图失败: {e}") - return False - - def _stop_imgui_webview(self): - webview = getattr(self.app, "_imgui_webview", None) - if webview: - try: - webview.stop() - except Exception: - pass - self.app._imgui_webview = None - - tex_id = getattr(self.app, "_imgui_webview_tex_id", None) - if tex_id: - try: - self.app.imgui.removeTexture(tex_id) - except Exception: - pass - self.app._imgui_webview_tex_id = None - texture_path = getattr(self.app, "_imgui_webview_texture_path", None) - if texture_path: - try: - Path(texture_path).unlink(missing_ok=True) - except Exception: - pass - self.app._imgui_webview_texture_path = None - - def _navigate_web_panel(self): - self._ensure_web_panel_state() - url = (self.app.web_panel_url_input or "").strip() - if not url: - return - - webview = getattr(self.app, "_imgui_webview", None) - if not webview or not getattr(webview, "_running", False): - self._start_imgui_webview(url) - return - - webview.navigate(url) - - def _update_web_panel_texture(self): - webview = getattr(self.app, "_imgui_webview", None) - if not webview: - return - if not webview.tex_dirty: - return - - jpeg_bytes = webview.get_screenshot_bytes() - if not jpeg_bytes: - return - - try: - if Image is None: - self.app.add_warning_message("缺少 Pillow,无法更新Web纹理") - return - img = Image.open(io.BytesIO(jpeg_bytes)).convert("RGBA") - # p3dimgui 纹理坐标系与网页截图存在Y轴方向差异,先在像素层修正 - img = img.transpose(Image.FLIP_TOP_BOTTOM) - - temp_dir = Path(tempfile.gettempdir()) / "eg_imgui_webpanel" - temp_dir.mkdir(parents=True, exist_ok=True) - temp_path = temp_dir / f"webview_{time.time_ns()}.png" - img.save(temp_path, format="PNG") - - panda_path = Filename.fromOsSpecific(str(temp_path)).getFullpath() - new_tex_id = self.app.imgui.loadTexture(panda_path) - old_tex_id = getattr(self.app, "_imgui_webview_tex_id", None) - if old_tex_id: - try: - self.app.imgui.removeTexture(old_tex_id) - except Exception: - pass - - old_texture_path = getattr(self.app, "_imgui_webview_texture_path", None) - if old_texture_path: - try: - Path(old_texture_path).unlink(missing_ok=True) - except Exception: - pass - - self.app._imgui_webview_tex_id = new_tex_id - self.app._imgui_webview_texture_path = str(temp_path) - except Exception as e: - self.app.add_warning_message(f"Web纹理更新失败: {e}") - finally: - webview.tex_dirty = False - - @staticmethod - def _clamp01(value): - return 0.0 if value < 0.0 else 1.0 if value > 1.0 else value - - def draw_menu_bar(self): - """绘制菜单栏""" - with imgui_ctx.begin_main_menu_bar() as main_menu: - if main_menu: - # 文件菜单 - with imgui_ctx.begin_menu("文件") as file_menu: - if file_menu: - if imgui.menu_item("新建项目", "Ctrl+N", False, True)[1]: - self.app._on_new_project() - if imgui.menu_item("打开项目", "Ctrl+O", False, True)[1]: - self.app._on_open_project() - imgui.separator() - if imgui.menu_item("保存", "Ctrl+S", False, True)[1]: - self.app._on_save_project() - if imgui.menu_item("另存为", "", False, True)[1]: - self.app._on_save_as_project() - imgui.separator() - if imgui.menu_item("退出", "Alt+F4", False, True)[1]: - self.app._on_exit() - - # 编辑菜单 - with imgui_ctx.begin_menu("编辑") as edit_menu: - if edit_menu: - if imgui.menu_item("撤销", "Ctrl+Z", False, True)[1]: - self.app._on_undo() - if imgui.menu_item("重做", "Ctrl+Y", False, True)[1]: - self.app._on_redo() - imgui.separator() - if imgui.menu_item("剪切", "Ctrl+X", False, True)[1]: - self.app._on_cut() - if imgui.menu_item("复制", "Ctrl+C", False, True)[1]: - self.app._on_copy() - if imgui.menu_item("粘贴", "Ctrl+V", False, True)[1]: - self.app._on_paste() - imgui.separator() - if imgui.menu_item("删除", "Del", False, True)[1]: - self.app._on_delete() - - # 创建菜单 - with imgui_ctx.begin_menu("创建") as create_menu: - if create_menu: - if imgui.menu_item("导入模型", "", False, True)[1]: - self.app._on_import_model() - - imgui.separator() - - if imgui.menu_item("空对象", "", False, True)[1]: - self.app._on_create_empty_object() - - # 3D对象子菜单 - with imgui_ctx.begin_menu("3D对象") as three_d_menu: - if three_d_menu: - if imgui.menu_item("立方体", "", False, True)[1]: - self.app._on_create_cube() - if imgui.menu_item("球体", "", False, True)[1]: - self.app._on_create_sphere() - if imgui.menu_item("圆柱体", "", False, True)[1]: - self.app._on_create_cylinder() - if imgui.menu_item("平面", "", False, True)[1]: - self.app._on_create_plane() - - # 3D GUI子菜单 - # with imgui_ctx.begin_menu("3D GUI") as three_d_gui_menu: - # if three_d_gui_menu: - # if imgui.menu_item("3D文本", "", False, True)[1]: - # self.app._on_create_3d_text() - # if imgui.menu_item("3D图片", "", False, True)[1]: - # self.app._on_create_3d_image() - - # # GUI子菜单 - # with imgui_ctx.begin_menu("GUI") as gui_menu: - # if gui_menu: - # if imgui.menu_item("创建按钮", "", False, True)[1]: - # self.app._on_create_gui_button() - # if imgui.menu_item("创建标签", "", False, True)[1]: - # self.app._on_create_gui_label() - # if imgui.menu_item("创建输入框", "", False, True)[1]: - # self.app._on_create_gui_entry() - # if imgui.menu_item("创建图片", "", False, True)[1]: - # self.app._on_create_gui_image() - # imgui.separator() - # if imgui.menu_item("创建视频屏幕", "", False, True)[1]: - # self.app._on_create_video_screen() - # if imgui.menu_item("创建2D视频屏幕", "", False, True)[1]: - # self.app._on_create_2d_video_screen() - # if imgui.menu_item("创建球形视频", "", False, True)[1]: - # self.app._on_create_spherical_video() - # if imgui.menu_item("创建虚拟屏幕", "", False, True)[1]: - # self.app._on_create_virtual_screen() - - # 光源子菜单 - with imgui_ctx.begin_menu("光源") as light_menu: - if light_menu: - if imgui.menu_item("聚光灯", "", False, True)[1]: - self.app._on_create_spot_light() - if imgui.menu_item("点光源", "", False, True)[1]: - self.app._on_create_point_light() - - # 地形子菜单 - with imgui_ctx.begin_menu("地形") as terrain_menu: - if terrain_menu: - if imgui.menu_item("创建平面地形", "", False, True)[1]: - self.app._on_create_flat_terrain() - if imgui.menu_item("从高度图创建地形", "", False, True)[1]: - self.app._on_create_heightmap_terrain() - - # 脚本子菜单 - with imgui_ctx.begin_menu("脚本") as script_menu: - if script_menu: - if imgui.menu_item("创建脚本...", "", False, True)[1]: - self.app._on_create_script() - if imgui.menu_item("加载脚本文件...", "", False, True)[1]: - self.app._on_load_script() - imgui.separator() - if imgui.menu_item("重载所有脚本", "", False, True)[1]: - self.app._on_reload_all_scripts() - _, self.app.hotReloadEnabled = imgui.menu_item("启用热重载", "", self.app.hotReloadEnabled, True) - if imgui.menu_item("脚本管理器", "", False, True)[1]: - self.app._on_open_scripts_manager() - - # 信息面板子菜单 - with imgui_ctx.begin_menu("信息面板") as info_panel_menu: - if info_panel_menu: - # if imgui.menu_item("创建2D示例面板", "", False, True)[1]: - # self.app._on_create_2d_sample_panel() - # if imgui.menu_item("创建3D实例面板", "", False, True)[1]: - # self.app._on_create_3d_sample_panel() - if imgui.menu_item("Web面板", "", False, True)[1]: - self.app._on_create_web_panel() - - # 视图菜单 - with imgui_ctx.begin_menu("视图") as view_menu: - if view_menu: - _, self.app.showToolbar = imgui.menu_item("工具栏", "", self.app.showToolbar, True) - _, self.app.showSceneTree = imgui.menu_item("场景树", "", self.app.showSceneTree, True) - _, self.app.showResourceManager = imgui.menu_item("资源管理器", "", self.app.showResourceManager, True) - _, self.app.showPropertyPanel = imgui.menu_item("属性面板", "", self.app.showPropertyPanel, True) - _, self.app.showConsole = imgui.menu_item("控制台", "", self.app.showConsole, True) - _, self.app.showScriptPanel = imgui.menu_item("脚本管理", "", self.app.showScriptPanel, True) - _, self.app.showLUIEditor = imgui.menu_item("LUI编辑器", "", self.app.showLUIEditor, True) - prev_show_web_panel = self.app.showWebPanel - _, self.app.showWebPanel = imgui.menu_item("Web面板", "", self.app.showWebPanel, True) - if prev_show_web_panel and not self.app.showWebPanel: - self._stop_imgui_webview() - elif (not prev_show_web_panel) and self.app.showWebPanel: - self._on_create_web_panel() - - # 工具菜单 - with imgui_ctx.begin_menu("工具") as tools_menu: - if tools_menu: - # 工具切换选项 - if imgui.menu_item("选择工具", "", False, True)[1]: - self.app.tool_manager.setCurrentTool("选择") - if imgui.menu_item("移动工具", "", False, True)[1]: - self.app.tool_manager.setCurrentTool("移动") - if imgui.menu_item("旋转工具", "", False, True)[1]: - self.app.tool_manager.setCurrentTool("旋转") - if imgui.menu_item("缩放工具", "", False, True)[1]: - self.app.tool_manager.setCurrentTool("缩放") - - imgui.separator() - - # 编辑工具 - # if imgui.menu_item("光照编辑", "", False, True)[1]: - # self.app.tool_manager.setCurrentTool("光照编辑") - # if imgui.menu_item("图形编辑", "", False, True)[1]: - # self.app.tool_manager.setCurrentTool("图形编辑") - - imgui.separator() - - # VR子菜单 - with imgui_ctx.begin_menu("VR") as vr_menu: - if vr_menu: - if imgui.menu_item("进入VR模式", "", False, True)[1]: - self.app._toggle_vr_mode() - if imgui.menu_item("退出VR模式", "", False, True)[1]: - self.app._exit_vr_mode() - - imgui.separator() - - if imgui.menu_item("VR状态", "", False, True)[1]: - self.app._show_vr_status() - if imgui.menu_item("VR设置", "", False, True)[1]: - self.app._show_vr_settings() - - imgui.separator() - - # VR调试子菜单 - with imgui_ctx.begin_menu("VR调试") as vr_debug_menu: - if vr_debug_menu: - _, self.app.vr_debug_enabled = imgui.menu_item("启用调试输出", "", self.app.vr_debug_enabled, True) - - if imgui.menu_item("立即显示性能报告", "", False, True)[1]: - self.app._show_vr_performance_report() - - imgui.separator() - - # 输出模式 - with imgui_ctx.begin_menu("输出模式") as output_menu: - if output_menu: - if imgui.menu_item("简短模式", "", not self.app.vr_detailed_mode, True)[1]: - self.app.vr_detailed_mode = False - if imgui.menu_item("详细模式", "", self.app.vr_detailed_mode, True)[1]: - self.app.vr_detailed_mode = True - - imgui.separator() - - _, self.app.vr_performance_monitor = imgui.menu_item("启用性能监控", "", self.app.vr_performance_monitor, True) - - # 窗口菜单 - 已隐藏 - # with imgui_ctx.begin_menu("窗口") as window_menu: - # if window_menu: - # _, self.app.showDemoWindow = imgui.menu_item("ImGui演示", "", self.app.showDemoWindow, True) - # if self.app.testTexture: - # imgui.menu_item("关闭纹理测试", "", False, True) - # else: - # imgui.menu_item("显示纹理测试", "", False, True) - - # 帮助菜单 - with imgui_ctx.begin_menu("帮助") as help_menu: - if help_menu: - imgui.menu_item("关于", "", False, True) - imgui.menu_item("文档", "", False, True) - - # 右侧显示FPS - imgui.set_cursor_pos_x(imgui.get_window_size().x - 140) - imgui.text("%.2f FPS (%.2f ms)" % (imgui.get_io().framerate, 1000.0 / imgui.get_io().framerate)) - - - def draw_toolbar(self): - """绘制工具栏""" - # 工具栏可以保持无标题栏,但允许移动和调整大小 - flags = self.app.style_manager.get_window_flags("toolbar") - - with self.app.style_manager.begin_styled_window("工具栏", self.app.showToolbar, flags): - self.app.showToolbar = True # 确保窗口保持打开 - - # 选择工具按钮 - select_active = self.app.tool_manager.isSelectionTool() - if self.app.icons.get('select'): - tint_col = (1.0, 1.0, 0.0, 1.0) if select_active else (1.0, 1.0, 1.0, 1.0) - if self.app.style_manager.image_button(self.app.icons['select'], (24, 24), tint_col=tint_col): - self.app.tool_manager.setCurrentTool("选择") - if imgui.is_item_hovered(): - imgui.set_tooltip("选择工具 (Q)") - imgui.same_line() - else: - if imgui.button("选择##select_tool"): - self.app.tool_manager.setCurrentTool("选择") - if select_active: - draw_list = imgui.get_window_draw_list() - button_min = imgui.get_item_rect_min() - button_max = imgui.get_item_rect_max() - draw_list.add_rect_filled(button_min, button_max, imgui.get_color_u32((0.3, 0.6, 1.0, 0.3))) - imgui.same_line() - - # 移动工具按钮 - move_active = self.app.tool_manager.isMoveTool() - if self.app.icons.get('move'): - # 使用不同颜色表示活动状态 - tint_col = (1.0, 1.0, 0.0, 1.0) if move_active else (1.0, 1.0, 1.0, 1.0) # 活动时显示黄色 - if self.app.style_manager.image_button(self.app.icons['move'], (24, 24), tint_col=tint_col): - self.app.tool_manager.setCurrentTool("移动") - if imgui.is_item_hovered(): - imgui.set_tooltip("移动工具 (W)") - imgui.same_line() - else: - if imgui.button("移动##move_tool"): - self.app.tool_manager.setCurrentTool("移动") - if move_active: - # 为活动按钮添加背景色 - draw_list = imgui.get_window_draw_list() - button_min = imgui.get_item_rect_min() - button_max = imgui.get_item_rect_max() - draw_list.add_rect_filled(button_min, button_max, imgui.get_color_u32((0.3, 0.6, 1.0, 0.3))) - imgui.same_line() - - # 旋转工具按钮 - rotate_active = self.app.tool_manager.isRotateTool() - if self.app.icons.get('rotate'): - tint_col = (1.0, 1.0, 0.0, 1.0) if rotate_active else (1.0, 1.0, 1.0, 1.0) - if self.app.style_manager.image_button(self.app.icons['rotate'], (24, 24), tint_col=tint_col): - self.app.tool_manager.setCurrentTool("旋转") - if imgui.is_item_hovered(): - imgui.set_tooltip("旋转工具 (E)") - imgui.same_line() - else: - if imgui.button("旋转##rotate_tool"): - self.app.tool_manager.setCurrentTool("旋转") - if rotate_active: - draw_list = imgui.get_window_draw_list() - button_min = imgui.get_item_rect_min() - button_max = imgui.get_item_rect_max() - draw_list.add_rect_filled(button_min, button_max, imgui.get_color_u32((0.3, 0.6, 1.0, 0.3))) - imgui.same_line() - - # 缩放工具按钮 - scale_active = self.app.tool_manager.isScaleTool() - if self.app.icons.get('scale'): - tint_col = (1.0, 1.0, 0.0, 1.0) if scale_active else (1.0, 1.0, 1.0, 1.0) - if self.app.style_manager.image_button(self.app.icons['scale'], (24, 24), tint_col=tint_col): - self.app.tool_manager.setCurrentTool("缩放") - if imgui.is_item_hovered(): - imgui.set_tooltip("缩放工具 (R)") - else: - if imgui.button("缩放##scale_tool"): - self.app.tool_manager.setCurrentTool("缩放") - if scale_active: - draw_list = imgui.get_window_draw_list() - button_min = imgui.get_item_rect_min() - button_max = imgui.get_item_rect_max() - draw_list.add_rect_filled(button_min, button_max, imgui.get_color_u32((0.3, 0.6, 1.0, 0.3))) - - imgui.same_line() - imgui.separator() - imgui.same_line() - - # 工具按钮已移除(导入、保存、播放) - - - def _draw_scene_tree(self): - """绘制场景树面板""" - # 使用更少的限制性标志,允许docking - flags = (imgui.WindowFlags_.no_collapse) - - with self.app.style_manager.begin_styled_window("场景树", self.app.showSceneTree, flags): - self.app.showSceneTree = True # 确保窗口保持打开 - window_pos = imgui.get_window_pos() - window_size = imgui.get_window_size() - self.app._scene_tree_window_rect = ( - float(window_pos.x), - float(window_pos.y), - float(window_size.x), - float(window_size.y), - ) - - imgui.text("场景层级") - imgui.separator() - - # 构建动态场景树 - self._build_scene_tree() - - - def _build_scene_tree(self): - """构建动态场景树""" - # 渲染节点 - if imgui.tree_node("渲染"): - # 环境光 - if hasattr(self.app, 'ambient_light') and self.app.ambient_light: - self._draw_scene_node(self.app.ambient_light, "环境光", "light") - - # 聚光灯 - if hasattr(self.app, 'scene_manager') and self.app.scene_manager: - if hasattr(self.app.scene_manager, 'Spotlight') and self.app.scene_manager.Spotlight: - for i, spotlight in enumerate(self.app.scene_manager.Spotlight): - self._draw_scene_node(spotlight, f"聚光灯_{i+1}", "light") - if hasattr(self.app.scene_manager, 'Pointlight') and self.app.scene_manager.Pointlight: - for i, pointlight in enumerate(self.app.scene_manager.Pointlight): - self._draw_scene_node(pointlight, f"点光源_{i+1}", "light") - - # 地板 - if hasattr(self.app, 'ground') and self.app.ground: - self._draw_scene_node(self.app.ground, "地板", "geometry") - - imgui.tree_pop() - - # 相机节点 - if imgui.tree_node("相机"): - if hasattr(self.app, 'camera') and self.app.camera: - self._draw_scene_node(self.app.camera, "主相机", "camera") - imgui.tree_pop() - - # 3D模型节点 - if imgui.tree_node("模型"): - models = [] - if hasattr(self.app, 'scene_manager') and self.app.scene_manager and hasattr(self.app.scene_manager, 'models'): - models.extend([m for m in self.app.scene_manager.models if m and not m.isEmpty()]) - - # SSBO模式下,模型可能不在 scene_manager.models 中,补充显示 ssbo_editor.model - ssbo_editor = getattr(self.app, "ssbo_editor", None) - ssbo_model = getattr(ssbo_editor, "model", None) if ssbo_editor else None - if ssbo_model and not ssbo_model.isEmpty() and ssbo_model.hasParent() and ssbo_model not in models: - models.append(ssbo_model) - - if models: - for i, model in enumerate(models): - self._draw_scene_node(model, model.getName() or f"模型_{i+1}", "model") - else: - imgui.text("(空)") - imgui.tree_pop() - - # if imgui.tree_node("GUI元素"): - # if hasattr(self,'gui_manager') and self.app.gui_manager and hasattr(self.app.gui_manager,'gui_elements'): - # if self.app.gui_manager.gui_elements: - # for gui_element in self.app.gui_manager.gui_elements: - # if gui_element and hasattr(gui_element,'node'): - # gui_type = getattr(gui_element,'gui_type','GUI_UNKNOWN') - # display_name = getattr(gui_element,'name',gui_type) - # self._draw_scene_node(gui_element.node,display_name,"gui",gui_type) - # else: - # imgui.text("(空)") - # else: - # imgui.text("(空)") - # imgui.tree_pop() - - # LUI元素节点 - if imgui.tree_node("GUI元素"): - if hasattr(self.app, 'lui_manager') and self.app.lui_manager.lui_enabled: - self.app.lui_manager.draw_component_tree() - imgui.tree_pop() - # if imgui.tree_node("LUI元素"): - # if hasattr(self.app, 'lui_manager') and self.app.lui_manager.lui_enabled: - # if self.app.lui_manager.components: - # for comp in self.app.lui_manager.components: - # if 'node' in comp: - # self._draw_scene_node(comp['node'], comp['name'], "ui") - - # if self.app.lui_manager.canvases: - # for canvas in self.app.lui_manager.canvases: - # if imgui.tree_node(f"Canvas: {canvas['name']}"): - # # 实际上组件已经在 node 下了,可以通过 _draw_scene_node 递归显示 - # # 但为了清晰,我们可以手动列出或者依赖递归 - # self._draw_scene_node(canvas['node'], canvas['name'], "geometry") - # imgui.tree_pop() - # else: - # imgui.text("(空)") - # else: - # imgui.text("(空)") - # imgui.tree_pop() - - - def _draw_scene_node(self, node, name, node_type, gui_subtype=None): - """绘制单个场景节点""" - if not node or node.isEmpty(): - return - - # 检查是否被选中 - is_selected = (hasattr(self.app, 'selection') and self.app.selection and - hasattr(self.app.selection, 'selectedNode') and - self.app.selection.selectedNode == node) - - # 节点可见性 - is_visible = node.is_hidden() == False - - # 设置选择颜色 - if is_selected: - imgui.push_style_color(imgui.Col_.text, (0.2, 0.6, 1.0, 1.0)) - - node_open = False - try: - # 显示节点 - node_open = imgui.tree_node(name) - - # 处理节点选择 - if imgui.is_item_clicked(): - # In SSBO mode, clicking model root should finally bind gizmo to - # SSBO group proxy (not legacy model root). So run legacy - # selection first, then force SSBO root selection at the end. - force_ssbo_root_key = None - ssbo_editor_ref = None - try: - ssbo_editor = getattr(self.app, "ssbo_editor", None) - controller = getattr(ssbo_editor, "controller", None) if ssbo_editor else None - ssbo_model = getattr(ssbo_editor, "model", None) if ssbo_editor else None - root_key = getattr(controller, "tree_root_key", None) if controller else None - if ( - ssbo_editor and controller and ssbo_model and node == ssbo_model - and root_key and root_key in controller.tree_nodes - ): - force_ssbo_root_key = root_key - ssbo_editor_ref = ssbo_editor - except Exception: - pass - - if hasattr(self.app, 'selection') and self.app.selection: - self.app.selection.updateSelection(node) - - if force_ssbo_root_key and ssbo_editor_ref: - try: - ssbo_editor_ref.select_node(force_ssbo_root_key) - except Exception: - pass - # Clear LUI selection when a scene node is selected - if hasattr(self.app, 'lui_manager'): - self.app.lui_manager.selected_index = -1 - - if self.app.is_dragging and imgui.is_item_hovered(): - self.app._drag_scene_tree_hover_node = node - - # 右键菜单 - if imgui.is_item_hovered() and imgui.is_mouse_clicked(1): - self._show_node_context_menu(node, name, node_type) - - # 显示节点属性 - imgui.same_line() - if is_visible: - imgui.text_colored((0.5, 1.0, 0.5, 1.0), "可见") - else: - imgui.text_colored((0.5, 0.5, 0.5, 1.0), "隐藏") - - if node_open: - # SSBO模型使用虚拟层级显示(避免 flatten 后真实子级丢失) - if self._draw_ssbo_virtual_children(node): - pass - elif node.getNumChildren() > 0: - for i in range(node.getNumChildren()): - child = node.getChild(i) - if not child or child.isEmpty(): - continue - child_name = child.getName() or f"child_{i+1}" - # 过滤碰撞辅助节点,避免污染场景树 - if child_name.startswith("modelCollision_"): - continue - self._draw_scene_node(child, child_name, node_type) - # tree_pop moved to finally - except Exception as e: - print(f"绘制场景节点时出错: {e}") - finally: - if node_open: - imgui.tree_pop() - # Ensure style stack is balanced. - if is_selected: - imgui.pop_style_color() - - def _draw_ssbo_virtual_children(self, node): - """Draw SSBO controller tree_nodes as virtual children for scene tree.""" - ssbo_editor = getattr(self.app, "ssbo_editor", None) - if not ssbo_editor: - return False - model = getattr(ssbo_editor, "model", None) - controller = getattr(ssbo_editor, "controller", None) - if not model or model != node or not controller: - return False - - root_key = getattr(controller, "tree_root_key", None) - if not root_key or root_key not in controller.tree_nodes: - imgui.text_disabled("(无可用子节点)") - return True - - root_node = controller.tree_nodes[root_key] - if not root_node["children"]: - imgui.text_disabled("(无可用子节点)") - return True - - for child_key in root_node["children"]: - self._draw_ssbo_virtual_tree_node(ssbo_editor, controller, child_key) - return True - - def _draw_ssbo_virtual_tree_node(self, ssbo_editor, controller, key): - """Recursively draw SSBO tree_nodes hierarchy in scene tree.""" - node_data = controller.tree_nodes.get(key) - if not node_data: - return - - # Skip redundant wrapper nodes (e.g. ROOT), show their children instead. - if controller.should_hide_tree_node(key): - for child_key in node_data["children"]: - self._draw_ssbo_virtual_tree_node(ssbo_editor, controller, child_key) - return - - display = controller.display_names.get(key, key) - obj_count = len(controller.name_to_ids.get(key, [])) - children = node_data["children"] - is_selected = (getattr(ssbo_editor, "selected_name", None) == key) - - if not children: - # Leaf node: selectable - label = f"{display} ({obj_count})##{key}" - if imgui.selectable(label, is_selected)[0]: - ssbo_editor.select_node(key) - if hasattr(self.app, "lui_manager"): - self.app.lui_manager.selected_index = -1 - else: - # Branch node: tree node - flags = imgui.TreeNodeFlags_.open_on_arrow - if is_selected: - flags |= imgui.TreeNodeFlags_.selected - label = f"{display} ({obj_count})##{key}" - opened = imgui.tree_node_ex(label, flags) - if imgui.is_item_clicked(0): - ssbo_editor.select_node(key) - if hasattr(self.app, "lui_manager"): - self.app.lui_manager.selected_index = -1 - if opened: - for child_key in children: - self._draw_ssbo_virtual_tree_node(ssbo_editor, controller, child_key) - imgui.tree_pop() - def _show_node_context_menu(self, node, name, node_type): - """显示节点右键菜单""" - self.app._context_menu_node = True - self.app._context_menu_target = node - - - def _draw_resource_manager(self): - """绘制资源管理器面板""" - flags = self.app.style_manager.get_window_flags("panel") - - with self.app.style_manager.begin_styled_window("资源管理器", self.app.showResourceManager, flags): - self.app.showResourceManager = True - rm = self.app.resource_manager - - window_pos = imgui.get_window_pos() - window_size = imgui.get_window_size() - self.app._resource_manager_window_rect = ( - float(window_pos.x), - float(window_pos.y), - float(window_size.x), - float(window_size.y), - ) - self.app._resource_drop_targets.append(( - float(window_pos.x), - float(window_pos.y), - float(window_size.x), - float(window_size.y), - str(rm.current_path), - )) - - imgui.text("文件浏览器") - imgui.separator() - - if imgui.button("◀"): - rm.navigate_back() - imgui.same_line() - if imgui.button("▶"): - rm.navigate_forward() - imgui.same_line() - if imgui.button("▲"): - rm.navigate_up() - imgui.same_line() - if imgui.button("主页"): - rm.navigate_to(rm.project_root / "Resources") - imgui.same_line() - if imgui.button("刷新"): - rm.force_refresh() - - # 自动刷新开关 - imgui.same_line() - changed, rm.auto_refresh_enabled = imgui.checkbox("自动刷新", rm.auto_refresh_enabled) - if changed: - rm.set_auto_refresh(rm.auto_refresh_enabled) - - imgui.same_line() - imgui.text(" ") - imgui.same_line() - - # 路径输入框 - changed, new_path = imgui.input_text("路径", str(rm.current_path), 256) - if changed: - try: - rm.navigate_to(Path(new_path)) - except: - pass - - # 搜索框 - changed, rm.search_filter = imgui.input_text("搜索", rm.search_filter, 256) - - imgui.separator() - - if rm.refresh_if_needed(): - pass - - dirs, files = rm.get_directory_contents(rm.current_path) - - for dir_path in dirs: - if not rm.should_show_file(dir_path): - continue - - icon_name = rm.get_file_icon(dir_path.name, is_folder=True) - is_selected = dir_path in rm.selected_files - - if is_selected: - imgui.push_style_color(imgui.Col_.header, (100/255, 150/255, 200/255, 1.0)) - - icon_texture = None - try: - icon_texture = self.app.style_manager.load_icon(f"file_types/{icon_name}") - except: - pass - - if icon_texture: - imgui.image(icon_texture, (16, 16)) - imgui.same_line() - node_open = imgui.tree_node(f"{dir_path.name}##dir_{dir_path}") - else: - node_open = imgui.tree_node(f"[{icon_name.upper()}]{dir_path.name}##dir_{dir_path}") - - if is_selected: - imgui.pop_style_color() - - self._append_drop_target_from_last_item(dir_path) - self._start_resource_drag_if_needed(rm, dir_path) - - if imgui.is_item_clicked(): - if imgui.get_io().key_ctrl: - if is_selected: - rm.selected_files.discard(dir_path) - else: - rm.selected_files.add(dir_path) - else: - rm.selected_files.clear() - rm.selected_files.add(dir_path) - rm.focused_file = dir_path - - if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0): - rm.navigate_to(dir_path) - - if imgui.is_item_hovered() and imgui.is_mouse_clicked(1): - rm.show_context_menu = True - rm.context_menu_file = dir_path - rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y) - imgui.open_popup("resource_context_menu") - - if node_open: - subdirs, subfiles = rm.get_directory_contents(dir_path) - - for subdir in subdirs: - if not rm.should_show_file(subdir): - continue - - subicon_name = rm.get_file_icon(subdir.name, is_folder=True) - sub_is_selected = subdir in rm.selected_files - - subicon_texture = None - try: - subicon_texture = self.app.style_manager.load_icon(f"file_types/{subicon_name}") - except: - pass - - if subicon_texture: - imgui.image(subicon_texture, (16, 16)) - imgui.same_line() - sub_node_open = imgui.tree_node(f" {subdir.name}##dir_{subdir}") - else: - sub_node_open = imgui.tree_node(f" [{subicon_name.upper()}]{subdir.name}##dir_{subdir}") - - self._append_drop_target_from_last_item(subdir) - self._start_resource_drag_if_needed(rm, subdir) - - if imgui.is_item_clicked(): - if imgui.get_io().key_ctrl: - if sub_is_selected: - rm.selected_files.discard(subdir) - else: - rm.selected_files.add(subdir) - else: - rm.selected_files.clear() - rm.selected_files.add(subdir) - rm.focused_file = subdir - - if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0): - rm.navigate_to(subdir) - - if imgui.is_item_hovered() and imgui.is_mouse_clicked(1): - rm.show_context_menu = True - rm.context_menu_file = subdir - rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y) - imgui.open_popup("resource_context_menu") - - if sub_node_open: - imgui.tree_pop() - - for subfile in subfiles: - if not rm.should_show_file(subfile): - continue - - subicon_name = rm.get_file_icon(subfile.name) - sub_is_selected = subfile in rm.selected_files - - subicon_texture = None - try: - subicon_texture = self.app.style_manager.load_icon(f"file_types/{subicon_name}") - except: - pass - - if subicon_texture: - imgui.image(subicon_texture, (16, 16)) - imgui.same_line() - selected = imgui.selectable(f" {subfile.name}##file_{subfile}", sub_is_selected) - else: - selected = imgui.selectable(f" [{subicon_name.upper()}] {subfile.name}##file_{subfile}", sub_is_selected) - - selected_clicked = selected[0] if isinstance(selected, tuple) else bool(selected) - self._start_resource_drag_if_needed(rm, subfile) - - if selected_clicked: - if imgui.get_io().key_ctrl: - if sub_is_selected: - rm.selected_files.discard(subfile) - else: - rm.selected_files.add(subfile) - else: - rm.selected_files.clear() - rm.selected_files.add(subfile) - rm.focused_file = subfile - - if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0): - if self._is_model_file(subfile): - self.app._import_model_for_runtime(str(subfile)) - self.app.add_info_message(f"正在导入模型: {subfile.name}") - else: - rm.open_file(subfile) - - if imgui.is_item_hovered() and imgui.is_mouse_clicked(1): - rm.show_context_menu = True - rm.context_menu_file = subfile - rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y) - imgui.open_popup("resource_context_menu") - - imgui.tree_pop() - - for file_path in files: - if not rm.should_show_file(file_path): - continue - - icon_name = rm.get_file_icon(file_path.name) - is_selected = file_path in rm.selected_files - - icon_texture = None - try: - icon_texture = self.app.style_manager.load_icon(f"file_types/{icon_name}") - except: - pass - - if icon_texture: - imgui.image(icon_texture, (16, 16)) - imgui.same_line() - selected = imgui.selectable(f"{file_path.name}##file_{file_path}", is_selected) - else: - selected = imgui.selectable(f"[{icon_name.upper()}] {file_path.name}##file_{file_path}", is_selected) - - selected_clicked = selected[0] if isinstance(selected, tuple) else bool(selected) - self._start_resource_drag_if_needed(rm, file_path) - - if selected_clicked: - if imgui.get_io().key_ctrl: - if is_selected: - rm.selected_files.discard(file_path) - else: - rm.selected_files.add(file_path) - else: - rm.selected_files.clear() - rm.selected_files.add(file_path) - rm.focused_file = file_path - - if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0): - if self._is_model_file(file_path): - self.app.add_info_message(f"正在导入模型: {file_path.name}") - self.app._import_model_for_runtime(str(file_path)) - else: - rm.open_file(file_path) - - if imgui.is_item_hovered() and imgui.is_mouse_clicked(1): - rm.show_context_menu = True - rm.context_menu_file = file_path - rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y) - imgui.open_popup("resource_context_menu") - - if rm.context_menu_file: - imgui.set_next_window_pos((rm.context_menu_position[0], rm.context_menu_position[1])) - - if imgui.begin_popup("resource_context_menu"): - if rm.context_menu_file and rm.context_menu_file.is_dir(): - if imgui.menu_item("打开")[1]: - rm.navigate_to(rm.context_menu_file) - imgui.separator() - if imgui.menu_item("重命名")[1]: - print(f"重命名文件夹: {rm.context_menu_file.name}") - if imgui.menu_item("删除")[1]: - print(f"删除文件夹: {rm.context_menu_file.name}") - elif rm.context_menu_file: - if imgui.menu_item("打开")[1]: - rm.open_file(rm.context_menu_file) - imgui.separator() - if imgui.menu_item("导入到场景")[1]: - if self._is_model_file(rm.context_menu_file): - self.app.add_info_message(f"正在导入模型: {rm.context_menu_file.name}") - self.app._import_model_for_runtime(str(rm.context_menu_file)) - if imgui.menu_item("重命名")[1]: - print(f"重命名文件: {rm.context_menu_file.name}") - if imgui.menu_item("删除")[1]: - print(f"删除文件: {rm.context_menu_file.name}") - - if rm.context_menu_file: - imgui.separator() - if imgui.menu_item("复制路径")[1]: - imgui.set_clipboard_text(str(rm.context_menu_file)) - self.app.add_info_message("路径已复制到剪贴板") - if imgui.menu_item("在文件管理器中显示")[1]: - import platform - import subprocess - if platform.system() == "Windows": - subprocess.run(["explorer", "/select,", str(rm.context_menu_file)]) - elif platform.system() == "Darwin": - subprocess.run(["open", "-R", str(rm.context_menu_file)]) - else: - subprocess.run(["xdg-open", str(rm.context_menu_file.parent)]) - - if imgui.is_mouse_clicked(0) and not imgui.is_window_hovered(): - rm.context_menu_file = None - rm.show_context_menu = False - imgui.close_current_popup() - - imgui.end_popup() - - @staticmethod - def _is_model_file(path: Path) -> bool: - return path.suffix.lower() in ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj'] - - def _append_drop_target_from_last_item(self, target_path: Path): - item_min = imgui.get_item_rect_min() - item_max = imgui.get_item_rect_max() - width = float(item_max.x - item_min.x) - height = float(item_max.y - item_min.y) - if width <= 0 or height <= 0: - return - self.app._resource_drop_targets.append(( - float(item_min.x), - float(item_min.y), - width, - height, - str(target_path), - )) - - def _start_resource_drag_if_needed(self, rm, fallback_path: Path): - if not imgui.is_item_active() or not imgui.is_mouse_dragging(0): - return - drag_files = list(rm.selected_files) if rm.selected_files else [fallback_path] - rm.start_drag(drag_files) - self.app.is_dragging = True - self.app.show_drag_overlay = True - - def _draw_web_panel(self): - """绘制 Web 面板(ImGui + 后台浏览器截图)。""" - self._ensure_web_panel_state() - if not self.app.showWebPanel: - self._stop_imgui_webview() - return - - flags = self.app.style_manager.get_window_flags("panel") - with self.app.style_manager.begin_styled_window("Web面板", self.app.showWebPanel, flags) as (_, opened): - if not opened: - self.app.showWebPanel = False - self._stop_imgui_webview() - return - - self.app.showWebPanel = True - - changed, self.app.web_panel_url_input = imgui.input_text( - "URL", self.app.web_panel_url_input, 1024 - ) - if changed: - self.app.web_panel_url_input = self.app.web_panel_url_input.strip() - - imgui.same_line() - if imgui.button("访问"): - self._navigate_web_panel() - - webview = getattr(self.app, "_imgui_webview", None) - if not webview: - if not self._start_imgui_webview(self.app.web_panel_url_input): - imgui.text_colored((1.0, 0.4, 0.4, 1.0), "Web视图启动失败") - return - webview = self.app._imgui_webview - - imgui.same_line() - if imgui.button("后退"): - webview.go_back() - imgui.same_line() - if imgui.button("前进"): - webview.go_forward() - imgui.same_line() - if imgui.button("刷新"): - webview.reload() - - current_url = webview.current_url or self.app.web_panel_url_input - if current_url: - imgui.text_colored((0.7, 0.7, 0.7, 1.0), current_url) - if webview.error: - imgui.text_colored((1.0, 0.4, 0.4, 1.0), webview.error) - - imgui.separator() - - self._update_web_panel_texture() - tex_id = getattr(self.app, "_imgui_webview_tex_id", None) - available = imgui.get_content_region_avail() - display_w = max(float(available.x), 64.0) - display_h = max(float(available.y), 64.0) - - if tex_id: - imgui.image(tex_id, (display_w, display_h)) - - if imgui.is_item_hovered(): - mouse_wheel = imgui.get_io().mouse_wheel - if abs(mouse_wheel) > 1e-4: - webview.scroll(-mouse_wheel * 120.0) - - if imgui.is_mouse_clicked(0): - item_min = imgui.get_item_rect_min() - item_max = imgui.get_item_rect_max() - item_w = max(float(item_max.x - item_min.x), 1.0) - item_h = max(float(item_max.y - item_min.y), 1.0) - mouse_pos = imgui.get_mouse_pos() - - x_ratio = self._clamp01((float(mouse_pos.x) - float(item_min.x)) / item_w) - y_ratio = self._clamp01((float(mouse_pos.y) - float(item_min.y)) / item_h) - webview.click(x_ratio, y_ratio) - elif webview.is_loading: - imgui.text("网页加载中...") - else: - imgui.text("正在初始化Web视图...") - - - def _draw_property_panel(self): - """绘制属性面板""" - # 使用面板类型的窗口标志,支持docking - flags = self.app.style_manager.get_window_flags("panel") - - with self.app.style_manager.begin_styled_window("属性面板", self.app.showPropertyPanel, flags): - self.app.showPropertyPanel = True # 确保窗口保持打开 - - # --- LUI Component Properties --- - # 优先检查 LUI 组件选择 - if hasattr(self.app, 'lui_manager'): - lui_selected_index = getattr(self.app.lui_manager, "selected_index", -1) - if lui_selected_index >= 0 and self.app.lui_manager.luiFunction: - self.app.lui_manager.luiFunction._draw_component_properties( - self.app.lui_manager, - lui_selected_index - ) - return - - # --- Scene Node Properties --- - # 获取当前选中的节点 - selected_node = None - if hasattr(self.app, 'selection') and self.app.selection and hasattr(self.app.selection, 'selectedNode'): - selected_node = self.app.selection.selectedNode - - # SSBO mode may select a proxy node for gizmo operations. - # Resolve proxy back to a real scene node so property panel stays meaningful. - try: - if (selected_node and not selected_node.isEmpty() and - selected_node.hasTag("is_ssbo_proxy")): - ssbo_editor = getattr(self.app, "ssbo_editor", None) - controller = getattr(ssbo_editor, "controller", None) if ssbo_editor else None - if ssbo_editor and controller: - resolved = None - if getattr(ssbo_editor, "selected_ids", None): - first_gid = ssbo_editor.selected_ids[0] - key = controller.id_to_name.get(first_gid) - if key: - resolved = controller.key_to_node.get(key) - if (resolved is None or resolved.isEmpty()) and getattr(ssbo_editor, "selected_name", None): - resolved = controller.key_to_node.get(ssbo_editor.selected_name) - if resolved and not resolved.isEmpty(): - selected_node = resolved - except Exception: - pass - - if selected_node and not selected_node.isEmpty(): - self._draw_node_properties(selected_node) - else: - # 无选中对象时显示提示(模仿Qt版本的空状态样式) - imgui.spacing() - imgui.spacing() - - # 居中显示提示信息 - window_width = imgui.get_window_width() - text_width = 200 # 估算文本宽度 - text_pos_x = (window_width - text_width) / 2 - - imgui.set_cursor_pos_x(text_pos_x) - imgui.text_colored((0.5, 0.5, 0.5, 1.0), "🔍 未选择任何对象") - - imgui.set_cursor_pos_x(text_pos_x - 20) - imgui.text("请从场景树中选择一个对象") - - imgui.set_cursor_pos_x(text_pos_x + 10) - imgui.text("以查看其属性") - - imgui.spacing() - imgui.spacing() - - # 添加一些分隔线和装饰 - imgui.separator() - - # 显示快速提示 - imgui.text("💡 快速提示:") - imgui.bullet_text("单击场景树中的对象进行选择") - imgui.bullet_text("使用 F 键快速聚焦到选中对象") - imgui.bullet_text("使用 Delete 键删除选中对象") - - - def _draw_node_properties(self, node): - """绘制节点属性""" - if not node or node.isEmpty(): - # 停止变换监控 - self.app.stop_transform_monitoring() - return - - # 检查是否需要重新启动变换监控 - if self.app._monitored_node != node: - self.app.stop_transform_monitoring() - self.app.start_transform_monitoring(node) - - # 获取节点基本信息 - node_name = node.getName() or "未命名节点" - node_type = self.app._get_node_type_from_node(node) - - # 添加一些间距,模仿Qt版本的布局 - imgui.spacing() - - # 物体名称组(使用Qt版本的样式) - if imgui.collapsing_header("物体名称"): - # 第一行:可见性复选框和名称输入 - user_visible = node.getPythonTag("user_visible") - if user_visible is None: - user_visible = True - node.setPythonTag("user_visible", True) - - # 可见性复选框(模仿Qt版本的样式) - changed, is_visible = imgui.checkbox("##visibility", user_visible) - if changed: - node.setPythonTag("user_visible", is_visible) - if is_visible: - node.show() - else: - node.hide() - - imgui.same_line() - imgui.text("可见") - imgui.same_line() - imgui.spacing() - imgui.same_line() - - # 名称输入框(模仿Qt版本的样式) - imgui.text("名称:") - imgui.same_line() - changed, new_name = imgui.input_text("##name", node_name, 256) - if changed and hasattr(self.app, 'selection'): - # 更新场景树中的名称 - self.app._update_node_name(node, new_name) - - # 添加分隔线 - imgui.separator() - - # 状态徽章(模仿Qt版本的徽章样式) - self.app._draw_status_badges(node) - - imgui.spacing() - - # 变换属性组 - if imgui.collapsing_header("变换 Transform"): - self.app._draw_transform_properties(node) - - # 根据节点类型显示特定属性组 - if node_type == "GUI元素": - if imgui.collapsing_header("GUI信息"): - self.app._draw_gui_properties(node) - elif node_type == "光源": - if imgui.collapsing_header("光源属性"): - self.app._draw_light_properties(node) - elif node_type == "模型": - if imgui.collapsing_header("模型属性"): - self.app._draw_model_properties(node) - - # 动画控制组(只对模型显示) - if imgui.collapsing_header("动画控制"): - self.app._draw_animation_properties(node) - - # 外观属性组(通用) - if imgui.collapsing_header("外观属性"): - self.app._draw_appearance_properties(node) - - # 碰撞检测组 - if imgui.collapsing_header("碰撞检测"): - self.app._draw_collision_properties(node) - - # 操作按钮组 - if imgui.collapsing_header("操作"): - self.app._draw_property_actions(node) - - def _draw_status_badges(self, node): - """绘制状态徽章(模仿Qt版本的徽章样式)""" - imgui.text("状态标签: ") - - # 可见性状态徽章 - is_visible = not node.is_hidden() - visibility_color = (0.176, 1.0, 0.769, 1.0) if is_visible else (0.953, 0.616, 0.471, 1.0) - visibility_text = "可见" if is_visible else "隐藏" - - imgui.same_line() - imgui.text_colored(visibility_color, f"[{visibility_text}]") - - # 节点类型徽章 - node_type = self._get_node_type_from_node(node) - type_colors = { - "GUI元素": (0.188, 0.404, 0.753, 1.0), # 主题蓝色 - "光源": (1.0, 0.8, 0.2, 1.0), # 黄色 - "模型": (0.6, 0.8, 1.0, 1.0), # 浅蓝色 - "相机": (0.8, 0.8, 0.2, 1.0), # 橙色 - "几何体": (0.5, 0.5, 0.5, 1.0), # 灰色 - } - - if node_type in type_colors: - imgui.same_line() - imgui.text_colored(type_colors[node_type], f"[{node_type}]") - - # 功能性徽章 - badges = [] - - # 碰撞体徽章 - has_collision = hasattr(node, 'getChild') and any('Collision' in child.getName() for child in node.getChildren() if child.getName()) - if has_collision: - badges.append(("碰撞", (0.2, 0.4, 0.8, 1.0))) # 蓝色 - - # 脚本徽章 - has_script = hasattr(node, 'getPythonTag') and node.getPythonTag('script') - if has_script: - badges.append(("脚本", (0.8, 0.4, 0.8, 1.0))) # 紫色 - - # 动画徽章(优化检测逻辑,避免重复创建Actor) - has_animation = False - if node_type == "模型": # 只对模型类型进行动画检测 - model_path = node.getTag("model_path") if node.hasTag("model_path") else "" - likely_anim_format = bool(model_path and model_path.lower().endswith(('.glb', '.gltf', '.fbx', '.bam', '.egg'))) - - # 优先使用场景标签(导入/加载时会写入) - if node.hasTag("has_animations"): - has_animation = node.getTag("has_animations").lower() == "true" - - # 再做轻量结构检测(不依赖 Actor) - if not has_animation: - try: - has_character = node.findAllMatches("**/+Character").getNumPaths() > 0 - has_bundle = node.findAllMatches("**/+AnimBundleNode").getNumPaths() > 0 - has_animation = has_character or has_bundle - if has_animation: - node.setTag("has_animations", "true") - node.setTag("can_create_actor_from_memory", "true") - except Exception: - pass - - # 最后才尝试 Actor 检测(只缓存“有动画”,避免把失败结果永久缓存) - cached_result = node.getPythonTag('animation') - if cached_result is True: - has_animation = True - elif not has_animation and likely_anim_format: - try: - actor = self._getActor(node) - if actor and actor.getAnimNames(): - has_animation = True - node.setTag("has_animations", "true") - node.setPythonTag('animation', True) - print(f"[动画检测] {node.getName()}: 有动画") - except Exception as e: - print(f"动画检测失败: {e}") - elif cached_result is False and not likely_anim_format: - has_animation = False - else: - # 对于非模型类型,检查已有的动画标签 - has_animation = hasattr(node, 'getPythonTag') and node.getPythonTag('animation') - - if has_animation: - badges.append(("动画", (0.4, 0.8, 0.4, 1.0))) # 绿色 - - # 材质徽章 - has_material = hasattr(node, 'getMaterial') and node.getMaterial() - if has_material: - badges.append(("材质", (0.8, 0.6, 0.2, 1.0))) # 金色 - - # 绘制功能性徽章 - for badge_text, badge_color in badges: - imgui.same_line() - imgui.text_colored(badge_color, f"[{badge_text}]") - - # 如果没有特殊徽章,显示默认状态 - if not badges: - imgui.same_line() - imgui.text_colored((0.5, 0.5, 0.5, 1.0), "[标准对象]") - - - def _draw_transform_properties(self, node): - """绘制变换属性""" - # 位置组 - if imgui.collapsing_header("位置 Position"): - # 相对位置 - imgui.text("相对位置") - pos = node.getPos() - - # X坐标 - changed, new_x = imgui.input_float("X##pos_x", pos.x, 0.1, 1.0, "%.3f") - if changed: node.setX(new_x) - - # Y坐标 - changed, new_y = imgui.input_float("Y##pos_y", pos.y, 0.1, 1.0, "%.3f") - if changed: node.setY(new_y) - - # Z坐标 - changed, new_z = imgui.input_float("Z##pos_z", pos.z, 0.1, 1.0, "%.3f") - if changed: node.setZ(new_z) - - # 世界位置 - imgui.text("世界位置") - world_pos = node.getPos(self.render) - - imgui.text(f"世界 X: {world_pos.x:.3f}") - imgui.text(f"世界 Y: {world_pos.y:.3f}") - imgui.text(f"世界 Z: {world_pos.z:.3f}") - - # 位置操作按钮 - if imgui.button("重置位置##reset_pos"): - node.setPos(0, 0, 0) - imgui.same_line() - if imgui.button("复制位置##copy_pos"): - self._clipboard_pos = (pos.x, pos.y, pos.z) - imgui.same_line() - if imgui.button("粘贴位置##paste_pos") and hasattr(self, '_clipboard_pos'): - node.setPos(self._clipboard_pos[0], self._clipboard_pos[1], self._clipboard_pos[2]) - - # 旋转组 - if imgui.collapsing_header("旋转 Rotation"): - hpr = node.getHpr() - - # HPR旋转 - imgui.text("HPR 旋转 (度)") - changed, new_h = imgui.input_float("H##rot_h", hpr.x, 1.0, 10.0, "%.1f") - if changed: node.setH(new_h) - - changed, new_p = imgui.input_float("P##rot_p", hpr.y, 1.0, 10.0, "%.1f") - if changed: node.setP(new_p) - - changed, new_r = imgui.input_float("R##rot_r", hpr.z, 1.0, 10.0, "%.1f") - if changed: node.setR(new_r) - - # 旋转操作按钮 - if imgui.button("重置旋转##reset_rot"): - node.setHpr(0, 0, 0) - imgui.same_line() - if imgui.button("随机旋转##random_rot"): - import random - node.setHpr(random.randint(0, 360), random.randint(0, 360), random.randint(0, 360)) - - # 缩放组 - if imgui.collapsing_header("缩放 Scale"): - scale = node.getScale() - - # XYZ缩放 - imgui.text("XYZ 缩放") - changed, new_sx = imgui.input_float("X##scale_x", scale.x, 0.1, 1.0, "%.3f") - if changed: node.setSx(new_sx) - - changed, new_sy = imgui.input_float("Y##scale_y", scale.y, 0.1, 1.0, "%.3f") - if changed: node.setSy(new_sy) - - changed, new_sz = imgui.input_float("Z##scale_z", scale.z, 0.1, 1.0, "%.3f") - if changed: node.setSz(new_sz) - - # 统一缩放 - if imgui.button("统一缩放##uniform_scale"): - uniform_scale = (scale.x + scale.y + scale.z) / 3.0 - node.setScale(uniform_scale, uniform_scale, uniform_scale) - imgui.same_line() - if imgui.button("重置缩放##reset_scale"): - node.setScale(1, 1, 1) - imgui.same_line() - if imgui.button("翻倍##double_scale"): - node.setScale(scale.x * 2, scale.y * 2, scale.z * 2) - - - def _draw_gui_properties(self, node): - """绘制GUI元素属性""" - # 获取GUI元素 - gui_element = None - if hasattr(node, 'getPythonTag'): - gui_element = node.getPythonTag('gui_element') - - if not gui_element: - imgui.text("无GUI元素数据") - return - - # GUI类型信息 - gui_type = getattr(gui_element, 'gui_type', 'UNKNOWN') - imgui.text(f"GUI类型: {gui_type}") - - # 基本属性 - if imgui.collapsing_header("基本属性"): - # 文本内容 (适用于按钮、标签等) - if hasattr(gui_element, 'text'): - changed, new_text = imgui.input_text("文本内容", gui_element.text, 256) - if changed and hasattr(self, 'gui_manager'): - self.gui_manager.editGUIElement(gui_element, 'text', new_text) - - # GUI ID - gui_id = getattr(gui_element, 'id', '') - changed, new_id = imgui.input_text("GUI ID", gui_id, 64) - if changed and hasattr(self, 'gui_manager'): - gui_element.id = new_id - - # 变换属性 - if imgui.collapsing_header("变换属性"): - # 位置 - pos = gui_element.getPos() - imgui.text("位置") - - if gui_type in ["button", "label", "entry", "2d_image"]: - # 2D GUI组件使用屏幕坐标 - imgui.text("屏幕坐标") - logical_x = pos.getX() / 0.1 - logical_z = pos.getZ() / 0.1 - - changed, new_x = imgui.input_float("X##gui_pos_x", logical_x, 1.0, 10.0, "%.1f") - if changed and hasattr(self, 'gui_manager'): - gui_element.setX(new_x * 0.1) - - changed, new_z = imgui.input_float("Y##gui_pos_y", logical_z, 1.0, 10.0, "%.1f") - if changed and hasattr(self, 'gui_manager'): - gui_element.setZ(new_z * 0.1) - else: - # 3D GUI组件使用世界坐标 - imgui.text("世界坐标") - changed, new_x = imgui.input_float("X##gui_world_x", pos.getX(), 0.1, 1.0, "%.3f") - if changed and hasattr(self, 'gui_manager'): - gui_element.setX(new_x) - - changed, new_y = imgui.input_float("Y##gui_world_y", pos.getY(), 0.1, 1.0, "%.3f") - if changed and hasattr(self, 'gui_manager'): - gui_element.setY(new_y) - - changed, new_z = imgui.input_float("Z##gui_world_z", pos.getZ(), 0.1, 1.0, "%.3f") - if changed and hasattr(self, 'gui_manager'): - gui_element.setZ(new_z) - - # 缩放 - scale = gui_element.getScale() - imgui.text("缩放") - changed, new_sx = imgui.input_float("X##gui_scale_x", scale.getX(), 0.1, 1.0, "%.3f") - if changed and hasattr(self, 'gui_manager'): - gui_element.setSx(new_sx) - - changed, new_sy = imgui.input_float("Y##gui_scale_y", scale.getY(), 0.1, 1.0, "%.3f") - if changed and hasattr(self, 'gui_manager'): - gui_element.setSy(new_sy) - - changed, new_sz = imgui.input_float("Z##gui_scale_z", scale.getZ(), 0.1, 1.0, "%.3f") - if changed and hasattr(self, 'gui_manager'): - gui_element.setSz(new_sz) - - # 旋转 - hpr = gui_element.getHpr() - imgui.text("旋转") - changed, new_h = imgui.input_float("H##gui_rot_h", hpr.getX(), 1.0, 10.0, "%.1f") - if changed and hasattr(self, 'gui_manager'): - gui_element.setH(new_h) - - changed, new_p = imgui.input_float("P##gui_rot_p", hpr.getY(), 1.0, 10.0, "%.1f") - if changed and hasattr(self, 'gui_manager'): - gui_element.setP(new_p) - - changed, new_r = imgui.input_float("R##gui_rot_r", hpr.getZ(), 1.0, 10.0, "%.1f") - if changed and hasattr(self, 'gui_manager'): - gui_element.setR(new_r) - - # 外观属性 - if imgui.collapsing_header("外观属性"): - # 大小 - if hasattr(gui_element, 'size'): - size = gui_element.size - imgui.text("大小") - changed, new_w = imgui.input_float("宽度", size[0], 1.0, 10.0, "%.1f") - if changed and hasattr(self, 'gui_manager'): - new_size = (new_w, size[1]) - self.gui_manager.editGUIElement(gui_element, 'size', new_size) - - changed, new_h = imgui.input_float("高度", size[1], 1.0, 10.0, "%.1f") - if changed and hasattr(self, 'gui_manager'): - new_size = (size[0], new_h) - self.gui_manager.editGUIElement(gui_element, 'size', new_size) - - # 颜色 - if hasattr(gui_element, 'getColor'): - try: - color = gui_element.getColor() - # 确保颜色是有效的 - if not color or (hasattr(color, '__len__') and len(color) < 3): - color = (1.0, 1.0, 1.0, 1.0) # 默认白色 - except: - color = (1.0, 1.0, 1.0, 1.0) # 默认白色 - - imgui.text("颜色") - # 获取颜色值 - if hasattr(color, 'getX'): - # 如果是Panda3D的Vec4对象 - r, g, b = color.getX(), color.getY(), color.getZ() - else: - # 如果是元组或其他格式 - r, g, b = color[0], color[1], color[2] - - changed, new_r = imgui.slider_float("R##gui_color_r", r, 0.0, 1.0) - if changed: gui_element.setColor(new_r, g, b, 1.0) - - changed, new_g = imgui.slider_float("G##gui_color_g", g, 0.0, 1.0) - if changed: gui_element.setColor(r, new_g, b, 1.0) - - changed, new_b = imgui.slider_float("B##gui_color_b", b, 0.0, 1.0) - if changed: gui_element.setColor(r, g, new_b, 1.0) - # 透明度 - imgui.text("透明度") - current_alpha = getattr(gui_element, 'alpha', 1.0) - changed, new_alpha = imgui.slider_float("Alpha", current_alpha, 0.0, 1.0) - if changed: - gui_element.alpha = new_alpha - if hasattr(gui_element, 'setTransparency'): - # 将0.0-1.0范围转换为Panda3D的透明度格式 - panda_transparency = int((1.0 - new_alpha) * 255) - gui_element.setTransparency(panda_transparency) - - # 渲染顺序 - imgui.text("渲染顺序") - current_sort = getattr(gui_element, 'sort', 0) - changed, new_sort = imgui.input_int("Sort Order", current_sort) - if changed: - gui_element.sort = new_sort - if hasattr(gui_element, 'setBin'): - gui_element.setBin('fixed', new_sort) - - # 字体设置(适用于文本类型的GUI元素) - if gui_type in ["button", "label", "entry"]: - imgui.text("字体设置") - - # 字体选择 - current_font = getattr(gui_element, 'font_path', '') - if imgui.button(f"字体: {Path(current_font).name if current_font else '默认'}##font_select"): - self.show_font_selector( - gui_element, - 'font_path', - current_font, - lambda font_path: self._apply_gui_font(gui_element, font_path) - ) - - # 字体大小 - current_size = getattr(gui_element, 'font_size', 12) - changed, new_size = imgui.slider_float("字体大小", current_size, 8.0, 72.0) - if changed: - gui_element.font_size = new_size - self._apply_gui_font_size(gui_element, new_size) - - # 字体样式 - imgui.text("字体样式") - is_bold = getattr(gui_element, 'font_bold', False) - changed, new_bold = imgui.checkbox("粗体", is_bold) - if changed: - gui_element.font_bold = new_bold - self._apply_gui_font_style(gui_element) - - imgui.same_line() - is_italic = getattr(gui_element, 'font_italic', False) - changed, new_italic = imgui.checkbox("斜体", is_italic) - if changed: - gui_element.font_italic = new_italic - self._apply_gui_font_style(gui_element) - - - def _draw_light_properties(self, node): - """绘制光源属性""" - imgui.text("光源属性") - - # 光源颜色 - if hasattr(node, 'getColor'): - try: - color = node.getColor() - # 确保颜色是有效的 - if not color or len(color) < 3: - color = (1.0, 1.0, 1.0, 1.0) # 默认白色 - except: - color = (1.0, 1.0, 1.0, 1.0) # 默认白色 - - changed, new_r = imgui.drag_float("颜色 R", color[0], 0.01, 0.0, 1.0) - if changed: node.setColor(new_r, color[1], color[2], color[3] if len(color) > 3 else 1.0) - - changed, new_g = imgui.drag_float("颜色 G", color[1], 0.01, 0.0, 1.0) - if changed: node.setColor(color[0], new_g, color[2], color[3] if len(color) > 3 else 1.0) - - changed, new_b = imgui.drag_float("颜色 B", color[2], 0.01, 0.0, 1.0) - if changed: node.setColor(color[0], color[1], new_b, color[3] if len(color) > 3 else 1.0) - - # 光源强度 - imgui.text("光源强度: (暂不支持编辑)") - - - def _draw_model_properties(self, node): - """绘制模型属性""" - # 获取模型信息 - model_path = node.getTag("model_path") if node.hasTag("model_path") else "未知" - - imgui.text("模型路径:") - imgui.same_line() - imgui.text_colored((0.7, 0.7, 0.7, 1.0), model_path) - - # 模型基本信息 - imgui.text("模型名称:") - imgui.same_line() - model_name = node.getName() or "未命名模型" - imgui.text_colored((0.7, 0.7, 0.7, 1.0), model_name) - - # 模型位置信息 - imgui.text("位置:") - imgui.same_line() - pos = node.getPos() - imgui.text_colored((0.7, 0.7, 0.7, 1.0), f"X:{pos.x:.2f} Y:{pos.y:.2f} Z:{pos.z:.2f}") - - # 模型缩放信息 - imgui.text("缩放:") - imgui.same_line() - scale = node.getScale() - imgui.text_colored((0.7, 0.7, 0.7, 1.0), f"X:{scale.x:.2f} Y:{scale.y:.2f} Z:{scale.z:.2f}") - - # 模型旋转信息 - imgui.text("旋转:") - imgui.same_line() - hpr = node.getHpr() - imgui.text_colored((0.7, 0.7, 0.7, 1.0), f"H:{hpr.x:.1f}° P:{hpr.y:.1f}° R:{hpr.z:.1f}°") - - - def _draw_animation_properties(self, node): - """绘制动画控制属性面板(优化版本,使用缓存避免重复计算)""" - anim_node = node - try: - if hasattr(self, "_resolve_animation_owner_model"): - resolved = self._resolve_animation_owner_model(node) - if resolved and not resolved.isEmpty(): - anim_node = resolved - except Exception: - pass - - # 路径兜底:当 anim_node 缺少路径时,从 scene_manager.models 中反查祖先模型 - try: - needs_path = (not anim_node.hasTag("model_path")) or (not anim_node.getTag("model_path")) - if needs_path and hasattr(self, "scene_manager") and self.scene_manager: - models = getattr(self.scene_manager, "models", []) - for model in list(models): - try: - if not model or model.isEmpty(): - continue - if (model == anim_node or model.isAncestorOf(anim_node) or anim_node.isAncestorOf(model)): - if model.hasTag("model_path") and model.getTag("model_path"): - anim_node.setTag("model_path", model.getTag("model_path")) - if model.hasTag("original_path") and model.getTag("original_path"): - anim_node.setTag("original_path", model.getTag("original_path")) - break - if model.hasTag("saved_model_path") and model.getTag("saved_model_path"): - anim_node.setTag("model_path", model.getTag("saved_model_path")) - break - except Exception: - continue - except Exception: - pass - - # 先刷新一次模型动画标签,避免“导入后未初始化”导致误判 - try: - if hasattr(self, "scene_manager") and self.scene_manager and hasattr(self.scene_manager, "_processModelAnimations"): - self.scene_manager._processModelAnimations(anim_node) - except Exception: - pass - - has_animation_tag = anim_node.hasTag("has_animations") and anim_node.getTag("has_animations").lower() == "true" - has_animation_nodes = False - try: - has_animation_nodes = ( - anim_node.findAllMatches("**/+Character").getNumPaths() > 0 or - anim_node.findAllMatches("**/+AnimBundleNode").getNumPaths() > 0 - ) - except Exception: - pass - - # 检查是否已经缓存了动画信息 - cached_anim_info = anim_node.getPythonTag("cached_anim_info") - cached_processed_names = anim_node.getPythonTag("cached_processed_names") - - # 如果之前缓存的是“格式未知”,但现在已有路径,强制重建缓存 - try: - now_has_path = anim_node.hasTag("model_path") and bool(anim_node.getTag("model_path")) - if now_has_path and isinstance(cached_anim_info, str) and "格式: 未知" in cached_anim_info: - cached_anim_info = None - cached_processed_names = None - anim_node.setPythonTag("cached_anim_info", None) - anim_node.setPythonTag("cached_processed_names", None) - anim_node.setPythonTag("animation", None) - self._clear_animation_cache(anim_node) - except Exception: - pass - - # 如果节点已被检测为有动画,但缓存是“无动画”,强制重新检测一次 - if (has_animation_tag or has_animation_nodes) and (cached_anim_info == "无动画" or cached_processed_names == []): - cached_anim_info = None - cached_processed_names = None - anim_node.setPythonTag("cached_anim_info", None) - anim_node.setPythonTag("cached_processed_names", None) - anim_node.setPythonTag("animation", None) - - # 只有在没有缓存时才进行完整的动画检测和处理 - if cached_anim_info is None or cached_processed_names is None: - # 获取Actor - actor = self._getActor(anim_node) - if not actor: - if has_animation_tag or has_animation_nodes: - imgui.text_colored((1.0, 0.7, 0.3, 1.0), "检测到动画结构,但当前未成功绑定Actor") - else: - imgui.text_colored((0.7, 0.7, 0.7, 1.0), "此模型不包含动画") - return - - # 获取和分析动画名称 - anim_names = actor.getAnimNames() - processed_names = self._processAnimationNames(anim_node, anim_names) - - if not processed_names: - imgui.text_colored((0.7, 0.7, 0.7, 1.0), "未检测到动画序列") - # 只在明确无动画时缓存空结果,避免误缓存导致后续无法重试 - if not (has_animation_tag or has_animation_nodes): - anim_node.setPythonTag("cached_processed_names", []) - anim_node.setPythonTag("cached_anim_info", "无动画") - return - - anim_node.setTag("has_animations", "true") - anim_node.setPythonTag("animation", True) - - # 计算并缓存动画信息 - format_info = self._getModelFormat(anim_node) - animation_info = self._analyzeAnimationQuality(actor, anim_names, format_info) - info_text = f"格式: {format_info} | 动画数量: {len(processed_names)}" - if animation_info: - info_text += f" | {animation_info}" - - # 缓存结果 - anim_node.setPythonTag("cached_anim_info", info_text) - anim_node.setPythonTag("cached_processed_names", processed_names) - - else: - # 使用缓存的数据 - info_text = cached_anim_info - processed_names = cached_processed_names - - # 如果缓存的空结果,直接返回 - if not processed_names: - imgui.text_colored((0.7, 0.7, 0.7, 1.0), "未检测到动画序列") - return - - # 显示动画信息(使用缓存的数据) - imgui.text("信息:") - imgui.same_line() - imgui.text_colored((0.7, 0.7, 0.7, 1.0), info_text) - - imgui.spacing() - - # 动画选择下拉框 - imgui.text("动画名称:") - imgui.same_line() - - # 获取当前选中的动画 - current_anim = anim_node.getPythonTag("selected_animation") - valid_original_names = [original_name for _, original_name in processed_names] - if current_anim is None or current_anim not in valid_original_names: - current_anim = processed_names[0][1] if processed_names else "" - anim_node.setPythonTag("selected_animation", current_anim) - - # 查找当前动画的索引 - current_index = 0 - for i, (display_name, original_name) in enumerate(processed_names): - if original_name == current_anim: - current_index = i - break - - # 创建下拉框选项 - animation_options = [display_name for display_name, _ in processed_names] - changed, new_index = imgui.combo("##animation_combo", current_index, animation_options) - - if changed and new_index < len(processed_names): - selected_display, selected_original = processed_names[new_index] - anim_node.setPythonTag("selected_animation", selected_original) - print(f"选择动画: {selected_display} (原始名称: {selected_original})") - - imgui.spacing() - - # 控制按钮组 - imgui.text("控制:") - - # 播放按钮 - if imgui.button("播放##play_animation"): - self._playAnimation(anim_node) - imgui.same_line() - - # 暂停按钮 - if imgui.button("暂停##pause_animation"): - self._pauseAnimation(anim_node) - imgui.same_line() - - # 停止按钮 - if imgui.button("停止##stop_animation"): - self._stopAnimation(anim_node) - imgui.same_line() - - # 循环按钮 - if imgui.button("循环##loop_animation"): - self._loopAnimation(anim_node) - - imgui.spacing() - - # 播放速度控制 - imgui.text("播放速度:") - imgui.same_line() - - # 获取当前速度 - current_speed = anim_node.getPythonTag("anim_speed") - if current_speed is None: - current_speed = 1.0 - anim_node.setPythonTag("anim_speed", current_speed) - - # 速度滑块 - changed, new_speed = imgui.slider_float("##anim_speed", current_speed, 0.1, 5.0, "%.1f") - if changed: - anim_node.setPythonTag("anim_speed", new_speed) - self._setAnimationSpeed(anim_node, new_speed) - - imgui.same_line() - imgui.text_colored((0.7, 0.7, 0.7, 1.0), "倍速") - - - def _draw_collision_properties(self, node): - """绘制碰撞检测属性""" - if not node or node.isEmpty(): - return - - try: - # 检查节点是否已有碰撞 - has_collision = self._has_collision(node) - - # 碰撞状态徽章 - imgui.text("状态:") - imgui.same_line() - if has_collision: - imgui.text_colored((0.0, 0.8, 0.0, 1.0), "🟢 已启用") - else: - imgui.text_colored((0.8, 0.0, 0.0, 1.0), "🔴 未启用") - - imgui.separator() - - # 碰撞形状选择 - imgui.text("碰撞形状:") - imgui.same_line() - - # 碰撞形状选项 - collision_shapes = ["球形 (Sphere)", "盒型 (Box)", "胶囊体 (Capsule)", "平面 (Plane)", "自动选择 (Auto)"] - - # 获取当前形状 - current_shape = self._get_current_collision_shape(node) if has_collision else getattr(self.app, '_selected_collision_shape', "球形 (Sphere)") - if has_collision: self.app._selected_collision_shape = current_shape - - # 形状选择下拉框 - current_index = collision_shapes.index(current_shape) if current_shape in collision_shapes else 0 - changed, selected_index = imgui.combo("##collision_shape", current_index, collision_shapes) - if changed: - # 始终更新选择的形状 - selected_shape = collision_shapes[selected_index] - self.app._selected_collision_shape = selected_shape - # 如果已经有碰撞体,询问用户是否要重新创建 - if has_collision: - self._remove_collision_from_node(node) - self._add_collision_to_node(node) - - imgui.separator() - - # 位置偏移控件 - imgui.text("位置偏移:") - - # 获取当前位置偏移 - pos_offset = self._get_collision_position_offset(node) - - # X位置 - changed, new_x = imgui.drag_float("X##collision_pos_x", pos_offset[0], 0.1, -100.0, 100.0, "%.2f") - if changed and has_collision: - self._update_collision_position(node, 'x', new_x) - - # Y位置 - changed, new_y = imgui.drag_float("Y##collision_pos_y", pos_offset[1], 0.1, -100.0, 100.0, "%.2f") - if changed and has_collision: - self._update_collision_position(node, 'y', new_y) - - # Z位置 - changed, new_z = imgui.drag_float("Z##collision_pos_z", pos_offset[2], 0.1, -100.0, 100.0, "%.2f") - if changed and has_collision: - self._update_collision_position(node, 'z', new_z) - - # 形状特定参数(始终显示,但根据状态启用/禁用) - shape_type = self._get_current_collision_shape_type(node) - self._draw_shape_specific_parameters(node, shape_type, has_collision) - - imgui.separator() - - # 操作按钮 - if has_collision: - # 显示/隐藏碰撞体按钮 - is_visible = self._is_collision_visible(node) - visibility_text = "隐藏碰撞体" if is_visible else "显示碰撞体" - if imgui.button(visibility_text): - self._toggle_collision_visibility(node) - - imgui.same_line() - - # 移除碰撞按钮 - if imgui.button("移除碰撞"): - self._remove_collision_from_node(node) - else: - # 添加碰撞按钮 - if imgui.button("添加碰撞"): - self._add_collision_to_node(node) - - imgui.separator() - - # 碰撞检测触发模式 - imgui.text("触发模式:") - - # 自动检测开关 - auto_enabled = self.collision_manager.model_collision_enabled if hasattr(self, 'collision_manager') else False - changed, new_auto = imgui.checkbox("自动检测", auto_enabled) - if changed and hasattr(self, 'collision_manager'): - self.collision_manager.enableModelCollisionDetection(new_auto, 0.1, 0.5) - - imgui.same_line() - - # 手动检测按钮 - if imgui.button("立即检测"): - if hasattr(self, 'collision_manager'): - self._manual_collision_detection() - - except Exception as e: - print(f"绘制碰撞属性失败: {e}") - import traceback - traceback.print_exc() - - - def _draw_shape_specific_parameters(self, node, shape_type, has_collision=True): - """绘制形状特定参数""" - try: - if shape_type == "sphere": - self._draw_sphere_parameters(node, has_collision) - elif shape_type == "box": - self._draw_box_parameters(node, has_collision) - elif shape_type == "capsule": - self._draw_capsule_parameters(node, has_collision) - elif shape_type == "plane": - self._draw_plane_parameters(node, has_collision) - except Exception as e: - print(f"绘制形状参数失败: {e}") - - - def _draw_sphere_parameters(self, node, has_collision=True): - """绘制球形参数""" - try: - imgui.text("球形参数:") - imgui.same_line() - - # 获取当前半径 - radius = self._get_sphere_radius(node) - - # 半径调整 - changed, new_radius = imgui.drag_float("半径##sphere_radius", radius, 0.1, 0.1, 100.0, "%.2f") - if changed and has_collision: - self._update_sphere_radius(node, new_radius) - - except Exception as e: - print(f"绘制球形参数失败: {e}") - - - def _draw_box_parameters(self, node, has_collision=True): - """绘制盒型参数""" - try: - imgui.text("盒型参数:") - - # 获取当前尺寸 - size = self._get_box_size(node) - - # 尺寸调整 - changed, new_x = imgui.drag_float("长度##box_length", size[0], 0.1, 0.1, 100.0, "%.2f") - if changed and has_collision: - self._update_box_size(node, 'x', new_x) - - changed, new_y = imgui.drag_float("宽度##box_width", size[1], 0.1, 0.1, 100.0, "%.2f") - if changed and has_collision: - self._update_box_size(node, 'y', new_y) - - changed, new_z = imgui.drag_float("高度##box_height", size[2], 0.1, 0.1, 100.0, "%.2f") - if changed and has_collision: - self._update_box_size(node, 'z', new_z) - - except Exception as e: - print(f"绘制盒型参数失败: {e}") - - - def _draw_capsule_parameters(self, node, has_collision=True): - """绘制胶囊体参数""" - try: - imgui.text("胶囊体参数:") - - # 获取当前参数 - radius = self._get_capsule_radius(node) - height = self._get_capsule_height(node) - - # 半径调整 - changed, new_radius = imgui.drag_float("半径##capsule_radius", radius, 0.1, 0.1, 100.0, "%.2f") - if changed and has_collision: - self._update_capsule_radius(node, new_radius) - - # 高度调整 - changed, new_height = imgui.drag_float("高度##capsule_height", height, 0.1, 0.1, 100.0, "%.2f") - if changed and has_collision: - self._update_capsule_height(node, new_height) - - except Exception as e: - print(f"绘制胶囊体参数失败: {e}") - - - def _draw_plane_parameters(self, node, has_collision=True): - """绘制平面参数""" - try: - imgui.text("平面参数:") - - # 获取当前法向量 - normal = self._get_plane_normal(node) - - # 法向量调整 - changed, new_x = imgui.drag_float("法向量 X##plane_normal_x", normal[0], 0.1, -1.0, 1.0, "%.2f") - if changed and has_collision: - self._update_plane_normal(node, 'x', new_x) - - changed, new_y = imgui.drag_float("法向量 Y##plane_normal_y", normal[1], 0.1, -1.0, 1.0, "%.2f") - if changed and has_collision: - self._update_plane_normal(node, 'y', new_y) - - changed, new_z = imgui.drag_float("法向量 Z##plane_normal_z", normal[2], 0.1, -1.0, 1.0, "%.2f") - if changed and has_collision: - self._update_plane_normal(node, 'z', new_z) - - except Exception as e: - print(f"绘制平面参数失败: {e}") - - - def _draw_property_actions(self, node): - """绘制属性操作按钮""" - # 重置变换 - if imgui.button("重置变换"): - node.setPos(0, 0, 0) - node.setHpr(0, 0, 0) - node.setScale(1, 1, 1) - - imgui.same_line() - - # 切换可见性 - is_visible = not node.is_hidden() - visibility_text = "隐藏" if is_visible else "显示" - if imgui.button(visibility_text): - if is_visible: - node.hide() - else: - node.show() - - imgui.same_line() - - # 聚焦到对象 - if imgui.button("聚焦"): - if hasattr(self, 'selection') and self.selection: - self.selection.focusCameraOnSelectedNodeAdvanced() - - # 删除对象 - imgui.same_line() - if imgui.button("删除"): - if hasattr(self.app, "_on_delete") and callable(self.app._on_delete): - self.app._on_delete() - elif hasattr(self, 'selection') and self.selection and hasattr(self.selection, "deleteSelectedNode"): - # 兼容旧选择系统接口 - self.selection.deleteSelectedNode() - - - def _draw_appearance_properties(self, node): - """绘制外观属性""" - # 颜色属性 - if hasattr(node, 'getColor'): - imgui.text("颜色") - try: - color = node.getColor() - # 确保颜色是有效的 - if not color or len(color) < 3: - color = (1.0, 1.0, 1.0, 1.0) # 默认白色 - except: - color = (1.0, 1.0, 1.0, 1.0) # 默认白色 - - # 颜色滑块 - changed, new_r = imgui.slider_float("R##color_r", color[0], 0.0, 1.0) - if changed: - new_color = (new_r, color[1], color[2], color[3] if len(color) > 3 else 1.0) - node.setColor(new_color) - color = new_color - - changed, new_g = imgui.slider_float("G##color_g", color[1], 0.0, 1.0) - if changed: - new_color = (color[0], new_g, color[2], color[3] if len(color) > 3 else 1.0) - node.setColor(new_color) - color = new_color - - changed, new_b = imgui.slider_float("B##color_b", color[2], 0.0, 1.0) - if changed: - new_color = (color[0], color[1], new_b, color[3] if len(color) > 3 else 1.0) - node.setColor(new_color) - color = new_color - - # 只有当颜色有alpha通道时才显示alpha滑块 - if len(color) > 3: - changed, new_a = imgui.slider_float("A##color_a", color[3], 0.0, 1.0) - if changed: - new_color = (color[0], color[1], color[2], new_a) - node.setColor(new_color) - color = new_color - - # 颜色预览和选择器 - imgui.text("颜色预览") - color_with_alpha = (color[0], color[1], color[2], color[3] if len(color) > 3 else 1.0) - if imgui.color_button("颜色预览##preview", color_with_alpha, 0, (100, 30)): - # 点击颜色按钮打开颜色选择器 - self.show_color_picker(node, 'color', color_with_alpha) - - imgui.same_line() - if imgui.button("选择颜色##color_picker_btn"): - self.show_color_picker(node, 'color', (color.x, color.y, color.z, color.w)) - - # 透明度 - if hasattr(node, 'setTransparency') and hasattr(node, 'getTransparency'): - imgui.text("透明度") - current_transparency = node.getTransparency() - # 将当前的透明度值转换为0.0-1.0范围用于显示 - display_transparency = 1.0 - current_transparency if current_transparency <= 1 else 0.0 - changed, new_transparency = imgui.slider_float("透明度", display_transparency, 0.0, 1.0) - if changed: - # 将0.0-1.0范围转换回Panda3D的透明度格式 - panda_transparency = int((1.0 - new_transparency) * 255) - node.setTransparency(panda_transparency) - - # 材质属性 - self._draw_material_properties(node) - - # 渲染状态 - imgui.text("渲染状态") - if imgui.button("应用材质"): - self._apply_material_to_node(node) - - imgui.same_line() - if imgui.button("重置材质"): - self._reset_material(node) - - - def _draw_material_properties(self, node): - """绘制材质属性""" - materials = node.find_all_materials() - - if not materials: - imgui.text_colored((0.5, 0.5, 0.5, 1.0), "无材质") - return - - for i, material in enumerate(materials): - material_name = material.get_name() if hasattr(material, 'get_name') and material.get_name() else f"材质{i + 1}" - - if imgui.collapsing_header(f"材质: {material_name}"): - # 材质基础颜色 - base_color = self._get_material_base_color(material) - if base_color: - imgui.text("基础颜色") - changed, new_r = imgui.slider_float(f"R##mat_r_{i}", base_color[0], 0.0, 1.0) - if changed: - self._update_material_base_color(material, 'r', new_r) - base_color = (new_r, base_color[1], base_color[2], base_color[3]) - - changed, new_g = imgui.slider_float(f"G##mat_g_{i}", base_color[1], 0.0, 1.0) - if changed: - self._update_material_base_color(material, 'g', new_g) - base_color = (base_color[0], new_g, base_color[2], base_color[3]) - - changed, new_b = imgui.slider_float(f"B##mat_b_{i}", base_color[2], 0.0, 1.0) - if changed: - self._update_material_base_color(material, 'b', new_b) - base_color = (base_color[0], base_color[1], new_b, base_color[3]) - - changed, new_a = imgui.slider_float(f"A##mat_a_{i}", base_color[3], 0.0, 1.0) - if changed: - self._update_material_base_color(material, 'a', new_a) - base_color = (base_color[0], base_color[1], base_color[2], new_a) - - # PBR属性 - if hasattr(material, 'roughness') and material.roughness is not None: - imgui.text("PBR属性") - try: - roughness_value = float(material.roughness) - changed, new_roughness = imgui.slider_float(f"粗糙度##rough_{i}", roughness_value, 0.0, 1.0) - if changed: - self._update_material_roughness(material, new_roughness) - except: - imgui.text_colored((0.7, 0.7, 0.7, 1.0), "粗糙度: 不可用") - - if hasattr(material, 'metallic') and material.metallic is not None: - try: - metallic_value = float(material.metallic) - changed, new_metallic = imgui.slider_float(f"金属性##metal_{i}", metallic_value, 0.0, 1.0) - if changed: - self._update_material_metallic(material, new_metallic) - except: - imgui.text_colored((0.7, 0.7, 0.7, 1.0), "金属性: 不可用") - - if hasattr(material, 'refractive_index') and material.refractive_index is not None: - try: - ior_value = float(material.refractive_index) - changed, new_ior = imgui.slider_float(f"折射率##ior_{i}", ior_value, 1.0, 3.0) - if changed: - self._update_material_ior(material, new_ior) - except: - imgui.text_colored((0.7, 0.7, 0.7, 1.0), "折射率: 不可用") - - # 材质预设 - imgui.text("材质预设") - presets = ["默认", "金属", "塑料", "玻璃", "木材", "混凝土"] - current_preset = 0 # 默认选择 - - if imgui.begin_combo(f"预设##preset_{i}", presets[current_preset]): - for j, preset_name in enumerate(presets): - if imgui.selectable(preset_name, j == current_preset): - self._apply_material_preset(material, preset_name) - imgui.end_combo() - - # 纹理信息 - imgui.text("纹理贴图") - if imgui.button(f"选择漫反射贴图##diffuse_{i}"): - self._select_texture_for_material(node, material, "diffuse") - - imgui.same_line() - if imgui.button(f"选择法线贴图##normal_{i}"): - self._select_texture_for_material(node, material, "normal") - - imgui.same_line() - if imgui.button(f"选择粗糙度贴图##roughness_{i}"): - self._select_texture_for_material(node, material, "roughness") - - if imgui.button(f"选择金属性贴图##metallic_{i}"): - self._select_texture_for_material(node, material, "metallic") - - imgui.same_line() - if imgui.button(f"选择自发光贴图##emission_{i}"): - self._select_texture_for_material(node, material, "emission") - - imgui.same_line() - if imgui.button(f"清除所有贴图##clear_{i}"): - self._clear_all_textures(node) - - # 着色模型选择 - self._draw_shading_model_panel(material, i) - - # 显示当前纹理信息 - self._display_current_textures(node, material) - - - def _draw_shading_model_panel(self, material, material_index): - """绘制着色模型选择面板""" - try: - imgui.text("着色模型") - - # RenderPipeline支持的着色模型 - shading_models = ["默认", "自发光", "透明"] - current_model = 0 # 默认选择 - - # 安全地获取当前着色模型 - try: - if hasattr(material, 'emission') and material.emission is not None: - current_model = int(material.emission.x) - except: - current_model = 0 - - # 着色模型选择 - if imgui.begin_combo(f"着色模型##shading_{material_index}", shading_models[current_model]): - for j, model_name in enumerate(shading_models): - if imgui.selectable(model_name, j == current_model): - self._update_shading_model(material, j) - imgui.end_combo() - - # 如果是透明着色模型,添加透明度控制 - if current_model == 3: # 透明着色模型 - imgui.text("透明度设置") - try: - if hasattr(material, 'shading_model_param0'): - current_opacity = material.shading_model_param0 - else: - current_opacity = 1.0 - - changed, new_opacity = imgui.slider_float(f"不透明度##opacity_{material_index}", current_opacity, 0.0, 1.0) - if changed: - self._update_transparency(material, new_opacity) - except: - imgui.text_colored((0.7, 0.7, 0.7, 1.0), "透明度控制不可用") - - except Exception as e: - print(f"绘制着色模型面板失败: {e}") - - diff --git a/ui/panels/editor_panels_center.py b/ui/panels/editor_panels_center.py new file mode 100644 index 00000000..0840e28d --- /dev/null +++ b/ui/panels/editor_panels_center.py @@ -0,0 +1,222 @@ +from imgui_bundle import imgui, imgui_ctx +import io +import tempfile +import time +from pathlib import Path +from panda3d.core import Filename +try: + from PIL import Image +except Exception: # pragma: no cover - pillow may be missing in minimal env + Image = None + +class EditorPanelsCenterMixin: + """Auto-split mixin from editor_panels.py.""" + + def _on_create_web_panel(self): + """创建或激活 ImGui Web 面板。""" + self._ensure_web_panel_state() + self.app.showWebPanel = True + + webview = getattr(self.app, "_imgui_webview", None) + if webview and getattr(webview, "_running", False): + return True + + return self._start_imgui_webview(self.app.web_panel_url_input) + + def _ensure_web_panel_state(self): + if not hasattr(self.app, "web_panel_url_input") or not self.app.web_panel_url_input: + self.app.web_panel_url_input = "https://www.baidu.com" + if not hasattr(self.app, "_imgui_webview"): + self.app._imgui_webview = None + if not hasattr(self.app, "_imgui_webview_tex_id"): + self.app._imgui_webview_tex_id = None + if not hasattr(self.app, "_imgui_webview_texture_path"): + self.app._imgui_webview_texture_path = None + + def _start_imgui_webview(self, url): + self._ensure_web_panel_state() + self._stop_imgui_webview() + try: + target_url = (url or "").strip() + if not target_url: + target_url = "https://www.example.com" + if not target_url.startswith(("http://", "https://", "file://")): + target_url = "https://" + target_url + + from core.imgui_webview import ImGuiWebView + webview = ImGuiWebView(width=1280, height=720) + webview.start(target_url) + self.app._imgui_webview = webview + return True + except Exception as e: + self.app.add_error_message(f"启动Web视图失败: {e}") + return False + + def _stop_imgui_webview(self): + webview = getattr(self.app, "_imgui_webview", None) + if webview: + try: + webview.stop() + except Exception: + pass + self.app._imgui_webview = None + + tex_id = getattr(self.app, "_imgui_webview_tex_id", None) + if tex_id: + try: + self.app.imgui.removeTexture(tex_id) + except Exception: + pass + self.app._imgui_webview_tex_id = None + texture_path = getattr(self.app, "_imgui_webview_texture_path", None) + if texture_path: + try: + Path(texture_path).unlink(missing_ok=True) + except Exception: + pass + self.app._imgui_webview_texture_path = None + + def _navigate_web_panel(self): + self._ensure_web_panel_state() + url = (self.app.web_panel_url_input or "").strip() + if not url: + return + + webview = getattr(self.app, "_imgui_webview", None) + if not webview or not getattr(webview, "_running", False): + self._start_imgui_webview(url) + return + + webview.navigate(url) + + def _update_web_panel_texture(self): + webview = getattr(self.app, "_imgui_webview", None) + if not webview: + return + if not webview.tex_dirty: + return + + jpeg_bytes = webview.get_screenshot_bytes() + if not jpeg_bytes: + return + + try: + if Image is None: + self.app.add_warning_message("缺少 Pillow,无法更新Web纹理") + return + img = Image.open(io.BytesIO(jpeg_bytes)).convert("RGBA") + # p3dimgui 纹理坐标系与网页截图存在Y轴方向差异,先在像素层修正 + img = img.transpose(Image.FLIP_TOP_BOTTOM) + + temp_dir = Path(tempfile.gettempdir()) / "eg_imgui_webpanel" + temp_dir.mkdir(parents=True, exist_ok=True) + temp_path = temp_dir / f"webview_{time.time_ns()}.png" + img.save(temp_path, format="PNG") + + panda_path = Filename.fromOsSpecific(str(temp_path)).getFullpath() + new_tex_id = self.app.imgui.loadTexture(panda_path) + old_tex_id = getattr(self.app, "_imgui_webview_tex_id", None) + if old_tex_id: + try: + self.app.imgui.removeTexture(old_tex_id) + except Exception: + pass + + old_texture_path = getattr(self.app, "_imgui_webview_texture_path", None) + if old_texture_path: + try: + Path(old_texture_path).unlink(missing_ok=True) + except Exception: + pass + + self.app._imgui_webview_tex_id = new_tex_id + self.app._imgui_webview_texture_path = str(temp_path) + except Exception as e: + self.app.add_warning_message(f"Web纹理更新失败: {e}") + finally: + webview.tex_dirty = False + + @staticmethod + def _clamp01(value): + return 0.0 if value < 0.0 else 1.0 if value > 1.0 else value + + def _draw_web_panel(self): + """绘制 Web 面板(ImGui + 后台浏览器截图)。""" + self._ensure_web_panel_state() + if not self.app.showWebPanel: + self._stop_imgui_webview() + return + + flags = self.app.style_manager.get_window_flags("panel") + with self.app.style_manager.begin_styled_window("Web面板", self.app.showWebPanel, flags) as (_, opened): + if not opened: + self.app.showWebPanel = False + self._stop_imgui_webview() + return + + self.app.showWebPanel = True + + changed, self.app.web_panel_url_input = imgui.input_text( + "URL", self.app.web_panel_url_input, 1024 + ) + if changed: + self.app.web_panel_url_input = self.app.web_panel_url_input.strip() + + imgui.same_line() + if imgui.button("访问"): + self._navigate_web_panel() + + webview = getattr(self.app, "_imgui_webview", None) + if not webview: + if not self._start_imgui_webview(self.app.web_panel_url_input): + imgui.text_colored((1.0, 0.4, 0.4, 1.0), "Web视图启动失败") + return + webview = self.app._imgui_webview + + imgui.same_line() + if imgui.button("后退"): + webview.go_back() + imgui.same_line() + if imgui.button("前进"): + webview.go_forward() + imgui.same_line() + if imgui.button("刷新"): + webview.reload() + + current_url = webview.current_url or self.app.web_panel_url_input + if current_url: + imgui.text_colored((0.7, 0.7, 0.7, 1.0), current_url) + if webview.error: + imgui.text_colored((1.0, 0.4, 0.4, 1.0), webview.error) + + imgui.separator() + + self._update_web_panel_texture() + tex_id = getattr(self.app, "_imgui_webview_tex_id", None) + available = imgui.get_content_region_avail() + display_w = max(float(available.x), 64.0) + display_h = max(float(available.y), 64.0) + + if tex_id: + imgui.image(tex_id, (display_w, display_h)) + + if imgui.is_item_hovered(): + mouse_wheel = imgui.get_io().mouse_wheel + if abs(mouse_wheel) > 1e-4: + webview.scroll(-mouse_wheel * 120.0) + + if imgui.is_mouse_clicked(0): + item_min = imgui.get_item_rect_min() + item_max = imgui.get_item_rect_max() + item_w = max(float(item_max.x - item_min.x), 1.0) + item_h = max(float(item_max.y - item_min.y), 1.0) + mouse_pos = imgui.get_mouse_pos() + + x_ratio = self._clamp01((float(mouse_pos.x) - float(item_min.x)) / item_w) + y_ratio = self._clamp01((float(mouse_pos.y) - float(item_min.y)) / item_h) + webview.click(x_ratio, y_ratio) + elif webview.is_loading: + imgui.text("网页加载中...") + else: + imgui.text("正在初始化Web视图...") + diff --git a/ui/panels/editor_panels_left.py b/ui/panels/editor_panels_left.py new file mode 100644 index 00000000..08b81332 --- /dev/null +++ b/ui/panels/editor_panels_left.py @@ -0,0 +1,615 @@ +from imgui_bundle import imgui, imgui_ctx +from pathlib import Path + +class EditorPanelsLeftMixin: + """Auto-split mixin from editor_panels.py.""" + + def _draw_scene_tree(self): + """绘制场景树面板""" + # 使用更少的限制性标志,允许docking + flags = (imgui.WindowFlags_.no_collapse) + + with self.app.style_manager.begin_styled_window("场景树", self.app.showSceneTree, flags): + self.app.showSceneTree = True # 确保窗口保持打开 + window_pos = imgui.get_window_pos() + window_size = imgui.get_window_size() + self.app._scene_tree_window_rect = ( + float(window_pos.x), + float(window_pos.y), + float(window_size.x), + float(window_size.y), + ) + + imgui.text("场景层级") + imgui.separator() + + # 构建动态场景树 + self._build_scene_tree() + + def _build_scene_tree(self): + """构建动态场景树""" + # 渲染节点 + if imgui.tree_node("渲染"): + # 环境光 + if hasattr(self.app, 'ambient_light') and self.app.ambient_light: + self._draw_scene_node(self.app.ambient_light, "环境光", "light") + + # 聚光灯 + if hasattr(self.app, 'scene_manager') and self.app.scene_manager: + if hasattr(self.app.scene_manager, 'Spotlight') and self.app.scene_manager.Spotlight: + for i, spotlight in enumerate(self.app.scene_manager.Spotlight): + self._draw_scene_node(spotlight, f"聚光灯_{i+1}", "light") + if hasattr(self.app.scene_manager, 'Pointlight') and self.app.scene_manager.Pointlight: + for i, pointlight in enumerate(self.app.scene_manager.Pointlight): + self._draw_scene_node(pointlight, f"点光源_{i+1}", "light") + + # 地板 + if hasattr(self.app, 'ground') and self.app.ground: + self._draw_scene_node(self.app.ground, "地板", "geometry") + + imgui.tree_pop() + + # 相机节点 + if imgui.tree_node("相机"): + if hasattr(self.app, 'camera') and self.app.camera: + self._draw_scene_node(self.app.camera, "主相机", "camera") + imgui.tree_pop() + + # 3D模型节点 + if imgui.tree_node("模型"): + models = [] + if hasattr(self.app, 'scene_manager') and self.app.scene_manager and hasattr(self.app.scene_manager, 'models'): + models.extend([m for m in self.app.scene_manager.models if m and not m.isEmpty()]) + + # SSBO模式下,模型可能不在 scene_manager.models 中,补充显示 ssbo_editor.model + ssbo_editor = getattr(self.app, "ssbo_editor", None) + ssbo_model = getattr(ssbo_editor, "model", None) if ssbo_editor else None + if ssbo_model and not ssbo_model.isEmpty() and ssbo_model.hasParent() and ssbo_model not in models: + models.append(ssbo_model) + + if models: + for i, model in enumerate(models): + self._draw_scene_node(model, model.getName() or f"模型_{i+1}", "model") + else: + imgui.text("(空)") + imgui.tree_pop() + + # if imgui.tree_node("GUI元素"): + # if hasattr(self,'gui_manager') and self.app.gui_manager and hasattr(self.app.gui_manager,'gui_elements'): + # if self.app.gui_manager.gui_elements: + # for gui_element in self.app.gui_manager.gui_elements: + # if gui_element and hasattr(gui_element,'node'): + # gui_type = getattr(gui_element,'gui_type','GUI_UNKNOWN') + # display_name = getattr(gui_element,'name',gui_type) + # self._draw_scene_node(gui_element.node,display_name,"gui",gui_type) + # else: + # imgui.text("(空)") + # else: + # imgui.text("(空)") + # imgui.tree_pop() + + # LUI元素节点 + if imgui.tree_node("GUI元素"): + if hasattr(self.app, 'lui_manager') and self.app.lui_manager.lui_enabled: + self.app.lui_manager.draw_component_tree() + imgui.tree_pop() + # if imgui.tree_node("LUI元素"): + # if hasattr(self.app, 'lui_manager') and self.app.lui_manager.lui_enabled: + # if self.app.lui_manager.components: + # for comp in self.app.lui_manager.components: + # if 'node' in comp: + # self._draw_scene_node(comp['node'], comp['name'], "ui") + + # if self.app.lui_manager.canvases: + # for canvas in self.app.lui_manager.canvases: + # if imgui.tree_node(f"Canvas: {canvas['name']}"): + # # 实际上组件已经在 node 下了,可以通过 _draw_scene_node 递归显示 + # # 但为了清晰,我们可以手动列出或者依赖递归 + # self._draw_scene_node(canvas['node'], canvas['name'], "geometry") + # imgui.tree_pop() + # else: + # imgui.text("(空)") + # else: + # imgui.text("(空)") + # imgui.tree_pop() + + def _draw_scene_node(self, node, name, node_type, gui_subtype=None): + """绘制单个场景节点""" + if not node or node.isEmpty(): + return + + # 检查是否被选中 + is_selected = (hasattr(self.app, 'selection') and self.app.selection and + hasattr(self.app.selection, 'selectedNode') and + self.app.selection.selectedNode == node) + + # 节点可见性 + is_visible = node.is_hidden() == False + + # 设置选择颜色 + if is_selected: + imgui.push_style_color(imgui.Col_.text, (0.2, 0.6, 1.0, 1.0)) + + node_open = False + try: + # 显示节点 + node_open = imgui.tree_node(name) + + # 处理节点选择 + if imgui.is_item_clicked(): + # In SSBO mode, clicking model root should finally bind gizmo to + # SSBO group proxy (not legacy model root). So run legacy + # selection first, then force SSBO root selection at the end. + force_ssbo_root_key = None + ssbo_editor_ref = None + try: + ssbo_editor = getattr(self.app, "ssbo_editor", None) + controller = getattr(ssbo_editor, "controller", None) if ssbo_editor else None + ssbo_model = getattr(ssbo_editor, "model", None) if ssbo_editor else None + root_key = getattr(controller, "tree_root_key", None) if controller else None + if ( + ssbo_editor and controller and ssbo_model and node == ssbo_model + and root_key and root_key in controller.tree_nodes + ): + force_ssbo_root_key = root_key + ssbo_editor_ref = ssbo_editor + except Exception: + pass + + if hasattr(self.app, 'selection') and self.app.selection: + self.app.selection.updateSelection(node) + + if force_ssbo_root_key and ssbo_editor_ref: + try: + ssbo_editor_ref.select_node(force_ssbo_root_key) + except Exception: + pass + # Clear LUI selection when a scene node is selected + if hasattr(self.app, 'lui_manager'): + self.app.lui_manager.selected_index = -1 + + if self.app.is_dragging and imgui.is_item_hovered(): + self.app._drag_scene_tree_hover_node = node + + # 右键菜单 + if imgui.is_item_hovered() and imgui.is_mouse_clicked(1): + self._show_node_context_menu(node, name, node_type) + + # 显示节点属性 + imgui.same_line() + if is_visible: + imgui.text_colored((0.5, 1.0, 0.5, 1.0), "可见") + else: + imgui.text_colored((0.5, 0.5, 0.5, 1.0), "隐藏") + + if node_open: + # SSBO模型使用虚拟层级显示(避免 flatten 后真实子级丢失) + if self._draw_ssbo_virtual_children(node): + pass + elif node.getNumChildren() > 0: + for i in range(node.getNumChildren()): + child = node.getChild(i) + if not child or child.isEmpty(): + continue + child_name = child.getName() or f"child_{i+1}" + # 过滤碰撞辅助节点,避免污染场景树 + if child_name.startswith("modelCollision_"): + continue + self._draw_scene_node(child, child_name, node_type) + # tree_pop moved to finally + except Exception as e: + print(f"绘制场景节点时出错: {e}") + finally: + if node_open: + imgui.tree_pop() + # Ensure style stack is balanced. + if is_selected: + imgui.pop_style_color() + + def _draw_ssbo_virtual_children(self, node): + """Draw SSBO controller tree_nodes as virtual children for scene tree.""" + ssbo_editor = getattr(self.app, "ssbo_editor", None) + if not ssbo_editor: + return False + model = getattr(ssbo_editor, "model", None) + controller = getattr(ssbo_editor, "controller", None) + if not model or model != node or not controller: + return False + + root_key = getattr(controller, "tree_root_key", None) + if not root_key or root_key not in controller.tree_nodes: + imgui.text_disabled("(无可用子节点)") + return True + + root_node = controller.tree_nodes[root_key] + if not root_node["children"]: + imgui.text_disabled("(无可用子节点)") + return True + + for child_key in root_node["children"]: + self._draw_ssbo_virtual_tree_node(ssbo_editor, controller, child_key) + return True + + def _draw_ssbo_virtual_tree_node(self, ssbo_editor, controller, key): + """Recursively draw SSBO tree_nodes hierarchy in scene tree.""" + node_data = controller.tree_nodes.get(key) + if not node_data: + return + + # Skip redundant wrapper nodes (e.g. ROOT), show their children instead. + if controller.should_hide_tree_node(key): + for child_key in node_data["children"]: + self._draw_ssbo_virtual_tree_node(ssbo_editor, controller, child_key) + return + + display = controller.display_names.get(key, key) + obj_count = len(controller.name_to_ids.get(key, [])) + children = node_data["children"] + is_selected = (getattr(ssbo_editor, "selected_name", None) == key) + + if not children: + # Leaf node: selectable + label = f"{display} ({obj_count})##{key}" + if imgui.selectable(label, is_selected)[0]: + ssbo_editor.select_node(key) + if hasattr(self.app, "lui_manager"): + self.app.lui_manager.selected_index = -1 + else: + # Branch node: tree node + flags = imgui.TreeNodeFlags_.open_on_arrow + if is_selected: + flags |= imgui.TreeNodeFlags_.selected + label = f"{display} ({obj_count})##{key}" + opened = imgui.tree_node_ex(label, flags) + if imgui.is_item_clicked(0): + ssbo_editor.select_node(key) + if hasattr(self.app, "lui_manager"): + self.app.lui_manager.selected_index = -1 + if opened: + for child_key in children: + self._draw_ssbo_virtual_tree_node(ssbo_editor, controller, child_key) + imgui.tree_pop() + + def _show_node_context_menu(self, node, name, node_type): + """显示节点右键菜单""" + self.app._context_menu_node = True + self.app._context_menu_target = node + + def _draw_resource_manager(self): + """绘制资源管理器面板""" + flags = self.app.style_manager.get_window_flags("panel") + + with self.app.style_manager.begin_styled_window("资源管理器", self.app.showResourceManager, flags): + self.app.showResourceManager = True + rm = self.app.resource_manager + + window_pos = imgui.get_window_pos() + window_size = imgui.get_window_size() + self.app._resource_manager_window_rect = ( + float(window_pos.x), + float(window_pos.y), + float(window_size.x), + float(window_size.y), + ) + self.app._resource_drop_targets.append(( + float(window_pos.x), + float(window_pos.y), + float(window_size.x), + float(window_size.y), + str(rm.current_path), + )) + + imgui.text("文件浏览器") + imgui.separator() + + if imgui.button("◀"): + rm.navigate_back() + imgui.same_line() + if imgui.button("▶"): + rm.navigate_forward() + imgui.same_line() + if imgui.button("▲"): + rm.navigate_up() + imgui.same_line() + if imgui.button("主页"): + rm.navigate_to(rm.project_root / "Resources") + imgui.same_line() + if imgui.button("刷新"): + rm.force_refresh() + + # 自动刷新开关 + imgui.same_line() + changed, rm.auto_refresh_enabled = imgui.checkbox("自动刷新", rm.auto_refresh_enabled) + if changed: + rm.set_auto_refresh(rm.auto_refresh_enabled) + + imgui.same_line() + imgui.text(" ") + imgui.same_line() + + # 路径输入框 + changed, new_path = imgui.input_text("路径", str(rm.current_path), 256) + if changed: + try: + rm.navigate_to(Path(new_path)) + except: + pass + + # 搜索框 + changed, rm.search_filter = imgui.input_text("搜索", rm.search_filter, 256) + + imgui.separator() + + if rm.refresh_if_needed(): + pass + + dirs, files = rm.get_directory_contents(rm.current_path) + + for dir_path in dirs: + if not rm.should_show_file(dir_path): + continue + + icon_name = rm.get_file_icon(dir_path.name, is_folder=True) + is_selected = dir_path in rm.selected_files + + if is_selected: + imgui.push_style_color(imgui.Col_.header, (100/255, 150/255, 200/255, 1.0)) + + icon_texture = None + try: + icon_texture = self.app.style_manager.load_icon(f"file_types/{icon_name}") + except: + pass + + if icon_texture: + imgui.image(icon_texture, (16, 16)) + imgui.same_line() + node_open = imgui.tree_node(f"{dir_path.name}##dir_{dir_path}") + else: + node_open = imgui.tree_node(f"[{icon_name.upper()}]{dir_path.name}##dir_{dir_path}") + + if is_selected: + imgui.pop_style_color() + + self._append_drop_target_from_last_item(dir_path) + self._start_resource_drag_if_needed(rm, dir_path) + + if imgui.is_item_clicked(): + if imgui.get_io().key_ctrl: + if is_selected: + rm.selected_files.discard(dir_path) + else: + rm.selected_files.add(dir_path) + else: + rm.selected_files.clear() + rm.selected_files.add(dir_path) + rm.focused_file = dir_path + + if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0): + rm.navigate_to(dir_path) + + if imgui.is_item_hovered() and imgui.is_mouse_clicked(1): + rm.show_context_menu = True + rm.context_menu_file = dir_path + rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y) + imgui.open_popup("resource_context_menu") + + if node_open: + subdirs, subfiles = rm.get_directory_contents(dir_path) + + for subdir in subdirs: + if not rm.should_show_file(subdir): + continue + + subicon_name = rm.get_file_icon(subdir.name, is_folder=True) + sub_is_selected = subdir in rm.selected_files + + subicon_texture = None + try: + subicon_texture = self.app.style_manager.load_icon(f"file_types/{subicon_name}") + except: + pass + + if subicon_texture: + imgui.image(subicon_texture, (16, 16)) + imgui.same_line() + sub_node_open = imgui.tree_node(f" {subdir.name}##dir_{subdir}") + else: + sub_node_open = imgui.tree_node(f" [{subicon_name.upper()}]{subdir.name}##dir_{subdir}") + + self._append_drop_target_from_last_item(subdir) + self._start_resource_drag_if_needed(rm, subdir) + + if imgui.is_item_clicked(): + if imgui.get_io().key_ctrl: + if sub_is_selected: + rm.selected_files.discard(subdir) + else: + rm.selected_files.add(subdir) + else: + rm.selected_files.clear() + rm.selected_files.add(subdir) + rm.focused_file = subdir + + if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0): + rm.navigate_to(subdir) + + if imgui.is_item_hovered() and imgui.is_mouse_clicked(1): + rm.show_context_menu = True + rm.context_menu_file = subdir + rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y) + imgui.open_popup("resource_context_menu") + + if sub_node_open: + imgui.tree_pop() + + for subfile in subfiles: + if not rm.should_show_file(subfile): + continue + + subicon_name = rm.get_file_icon(subfile.name) + sub_is_selected = subfile in rm.selected_files + + subicon_texture = None + try: + subicon_texture = self.app.style_manager.load_icon(f"file_types/{subicon_name}") + except: + pass + + if subicon_texture: + imgui.image(subicon_texture, (16, 16)) + imgui.same_line() + selected = imgui.selectable(f" {subfile.name}##file_{subfile}", sub_is_selected) + else: + selected = imgui.selectable(f" [{subicon_name.upper()}] {subfile.name}##file_{subfile}", sub_is_selected) + + selected_clicked = selected[0] if isinstance(selected, tuple) else bool(selected) + self._start_resource_drag_if_needed(rm, subfile) + + if selected_clicked: + if imgui.get_io().key_ctrl: + if sub_is_selected: + rm.selected_files.discard(subfile) + else: + rm.selected_files.add(subfile) + else: + rm.selected_files.clear() + rm.selected_files.add(subfile) + rm.focused_file = subfile + + if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0): + if self._is_model_file(subfile): + self.app._import_model_for_runtime(str(subfile)) + self.app.add_info_message(f"正在导入模型: {subfile.name}") + else: + rm.open_file(subfile) + + if imgui.is_item_hovered() and imgui.is_mouse_clicked(1): + rm.show_context_menu = True + rm.context_menu_file = subfile + rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y) + imgui.open_popup("resource_context_menu") + + imgui.tree_pop() + + for file_path in files: + if not rm.should_show_file(file_path): + continue + + icon_name = rm.get_file_icon(file_path.name) + is_selected = file_path in rm.selected_files + + icon_texture = None + try: + icon_texture = self.app.style_manager.load_icon(f"file_types/{icon_name}") + except: + pass + + if icon_texture: + imgui.image(icon_texture, (16, 16)) + imgui.same_line() + selected = imgui.selectable(f"{file_path.name}##file_{file_path}", is_selected) + else: + selected = imgui.selectable(f"[{icon_name.upper()}] {file_path.name}##file_{file_path}", is_selected) + + selected_clicked = selected[0] if isinstance(selected, tuple) else bool(selected) + self._start_resource_drag_if_needed(rm, file_path) + + if selected_clicked: + if imgui.get_io().key_ctrl: + if is_selected: + rm.selected_files.discard(file_path) + else: + rm.selected_files.add(file_path) + else: + rm.selected_files.clear() + rm.selected_files.add(file_path) + rm.focused_file = file_path + + if imgui.is_item_hovered() and imgui.is_mouse_double_clicked(0): + if self._is_model_file(file_path): + self.app.add_info_message(f"正在导入模型: {file_path.name}") + self.app._import_model_for_runtime(str(file_path)) + else: + rm.open_file(file_path) + + if imgui.is_item_hovered() and imgui.is_mouse_clicked(1): + rm.show_context_menu = True + rm.context_menu_file = file_path + rm.context_menu_position = (imgui.get_mouse_pos().x, imgui.get_mouse_pos().y) + imgui.open_popup("resource_context_menu") + + if rm.context_menu_file: + imgui.set_next_window_pos((rm.context_menu_position[0], rm.context_menu_position[1])) + + if imgui.begin_popup("resource_context_menu"): + if rm.context_menu_file and rm.context_menu_file.is_dir(): + if imgui.menu_item("打开")[1]: + rm.navigate_to(rm.context_menu_file) + imgui.separator() + if imgui.menu_item("重命名")[1]: + print(f"重命名文件夹: {rm.context_menu_file.name}") + if imgui.menu_item("删除")[1]: + print(f"删除文件夹: {rm.context_menu_file.name}") + elif rm.context_menu_file: + if imgui.menu_item("打开")[1]: + rm.open_file(rm.context_menu_file) + imgui.separator() + if imgui.menu_item("导入到场景")[1]: + if self._is_model_file(rm.context_menu_file): + self.app.add_info_message(f"正在导入模型: {rm.context_menu_file.name}") + self.app._import_model_for_runtime(str(rm.context_menu_file)) + if imgui.menu_item("重命名")[1]: + print(f"重命名文件: {rm.context_menu_file.name}") + if imgui.menu_item("删除")[1]: + print(f"删除文件: {rm.context_menu_file.name}") + + if rm.context_menu_file: + imgui.separator() + if imgui.menu_item("复制路径")[1]: + imgui.set_clipboard_text(str(rm.context_menu_file)) + self.app.add_info_message("路径已复制到剪贴板") + if imgui.menu_item("在文件管理器中显示")[1]: + import platform + import subprocess + if platform.system() == "Windows": + subprocess.run(["explorer", "/select,", str(rm.context_menu_file)]) + elif platform.system() == "Darwin": + subprocess.run(["open", "-R", str(rm.context_menu_file)]) + else: + subprocess.run(["xdg-open", str(rm.context_menu_file.parent)]) + + if imgui.is_mouse_clicked(0) and not imgui.is_window_hovered(): + rm.context_menu_file = None + rm.show_context_menu = False + imgui.close_current_popup() + + imgui.end_popup() + + @staticmethod + def _is_model_file(path: Path) -> bool: + return path.suffix.lower() in ['.gltf', '.glb', '.fbx', '.bam', '.egg', '.obj'] + + def _append_drop_target_from_last_item(self, target_path: Path): + item_min = imgui.get_item_rect_min() + item_max = imgui.get_item_rect_max() + width = float(item_max.x - item_min.x) + height = float(item_max.y - item_min.y) + if width <= 0 or height <= 0: + return + self.app._resource_drop_targets.append(( + float(item_min.x), + float(item_min.y), + width, + height, + str(target_path), + )) + + def _start_resource_drag_if_needed(self, rm, fallback_path: Path): + if not imgui.is_item_active() or not imgui.is_mouse_dragging(0): + return + drag_files = list(rm.selected_files) if rm.selected_files else [fallback_path] + rm.start_drag(drag_files) + self.app.is_dragging = True + self.app.show_drag_overlay = True + diff --git a/ui/panels/editor_panels_right.py b/ui/panels/editor_panels_right.py new file mode 100644 index 00000000..b0b9f013 --- /dev/null +++ b/ui/panels/editor_panels_right.py @@ -0,0 +1,1279 @@ +from imgui_bundle import imgui, imgui_ctx + +class EditorPanelsRightMixin: + """Auto-split mixin from editor_panels.py.""" + + def _draw_property_panel(self): + """绘制属性面板""" + # 使用面板类型的窗口标志,支持docking + flags = self.app.style_manager.get_window_flags("panel") + + with self.app.style_manager.begin_styled_window("属性面板", self.app.showPropertyPanel, flags): + self.app.showPropertyPanel = True # 确保窗口保持打开 + + # --- LUI Component Properties --- + # 优先检查 LUI 组件选择 + if hasattr(self.app, 'lui_manager'): + lui_selected_index = getattr(self.app.lui_manager, "selected_index", -1) + if lui_selected_index >= 0 and self.app.lui_manager.luiFunction: + self.app.lui_manager.luiFunction._draw_component_properties( + self.app.lui_manager, + lui_selected_index + ) + return + + # --- Scene Node Properties --- + # 获取当前选中的节点 + selected_node = None + if hasattr(self.app, 'selection') and self.app.selection and hasattr(self.app.selection, 'selectedNode'): + selected_node = self.app.selection.selectedNode + + # SSBO mode may select a proxy node for gizmo operations. + # Resolve proxy back to a real scene node so property panel stays meaningful. + try: + if (selected_node and not selected_node.isEmpty() and + selected_node.hasTag("is_ssbo_proxy")): + ssbo_editor = getattr(self.app, "ssbo_editor", None) + controller = getattr(ssbo_editor, "controller", None) if ssbo_editor else None + if ssbo_editor and controller: + resolved = None + if getattr(ssbo_editor, "selected_ids", None): + first_gid = ssbo_editor.selected_ids[0] + key = controller.id_to_name.get(first_gid) + if key: + resolved = controller.key_to_node.get(key) + if (resolved is None or resolved.isEmpty()) and getattr(ssbo_editor, "selected_name", None): + resolved = controller.key_to_node.get(ssbo_editor.selected_name) + if resolved and not resolved.isEmpty(): + selected_node = resolved + except Exception: + pass + + if selected_node and not selected_node.isEmpty(): + self._draw_node_properties(selected_node) + else: + # 无选中对象时显示提示(模仿Qt版本的空状态样式) + imgui.spacing() + imgui.spacing() + + # 居中显示提示信息 + window_width = imgui.get_window_width() + text_width = 200 # 估算文本宽度 + text_pos_x = (window_width - text_width) / 2 + + imgui.set_cursor_pos_x(text_pos_x) + imgui.text_colored((0.5, 0.5, 0.5, 1.0), "🔍 未选择任何对象") + + imgui.set_cursor_pos_x(text_pos_x - 20) + imgui.text("请从场景树中选择一个对象") + + imgui.set_cursor_pos_x(text_pos_x + 10) + imgui.text("以查看其属性") + + imgui.spacing() + imgui.spacing() + + # 添加一些分隔线和装饰 + imgui.separator() + + # 显示快速提示 + imgui.text("💡 快速提示:") + imgui.bullet_text("单击场景树中的对象进行选择") + imgui.bullet_text("使用 F 键快速聚焦到选中对象") + imgui.bullet_text("使用 Delete 键删除选中对象") + + def _draw_node_properties(self, node): + """绘制节点属性""" + if not node or node.isEmpty(): + # 停止变换监控 + self.app.stop_transform_monitoring() + return + + # 检查是否需要重新启动变换监控 + if self.app._monitored_node != node: + self.app.stop_transform_monitoring() + self.app.start_transform_monitoring(node) + + # 获取节点基本信息 + node_name = node.getName() or "未命名节点" + node_type = self.app._get_node_type_from_node(node) + + # 添加一些间距,模仿Qt版本的布局 + imgui.spacing() + + # 物体名称组(使用Qt版本的样式) + if imgui.collapsing_header("物体名称"): + # 第一行:可见性复选框和名称输入 + user_visible = node.getPythonTag("user_visible") + if user_visible is None: + user_visible = True + node.setPythonTag("user_visible", True) + + # 可见性复选框(模仿Qt版本的样式) + changed, is_visible = imgui.checkbox("##visibility", user_visible) + if changed: + node.setPythonTag("user_visible", is_visible) + if is_visible: + node.show() + else: + node.hide() + + imgui.same_line() + imgui.text("可见") + imgui.same_line() + imgui.spacing() + imgui.same_line() + + # 名称输入框(模仿Qt版本的样式) + imgui.text("名称:") + imgui.same_line() + changed, new_name = imgui.input_text("##name", node_name, 256) + if changed and hasattr(self.app, 'selection'): + # 更新场景树中的名称 + self.app._update_node_name(node, new_name) + + # 添加分隔线 + imgui.separator() + + # 状态徽章(模仿Qt版本的徽章样式) + self.app._draw_status_badges(node) + + imgui.spacing() + + # 变换属性组 + if imgui.collapsing_header("变换 Transform"): + self.app._draw_transform_properties(node) + + # 根据节点类型显示特定属性组 + if node_type == "GUI元素": + if imgui.collapsing_header("GUI信息"): + self.app._draw_gui_properties(node) + elif node_type == "光源": + if imgui.collapsing_header("光源属性"): + self.app._draw_light_properties(node) + elif node_type == "模型": + if imgui.collapsing_header("模型属性"): + self.app._draw_model_properties(node) + + # 动画控制组(只对模型显示) + if imgui.collapsing_header("动画控制"): + self.app._draw_animation_properties(node) + + # 外观属性组(通用) + if imgui.collapsing_header("外观属性"): + self.app._draw_appearance_properties(node) + + # 碰撞检测组 + if imgui.collapsing_header("碰撞检测"): + self.app._draw_collision_properties(node) + + # 操作按钮组 + if imgui.collapsing_header("操作"): + self.app._draw_property_actions(node) + + def _draw_status_badges(self, node): + """绘制状态徽章(模仿Qt版本的徽章样式)""" + imgui.text("状态标签: ") + + # 可见性状态徽章 + is_visible = not node.is_hidden() + visibility_color = (0.176, 1.0, 0.769, 1.0) if is_visible else (0.953, 0.616, 0.471, 1.0) + visibility_text = "可见" if is_visible else "隐藏" + + imgui.same_line() + imgui.text_colored(visibility_color, f"[{visibility_text}]") + + # 节点类型徽章 + node_type = self._get_node_type_from_node(node) + type_colors = { + "GUI元素": (0.188, 0.404, 0.753, 1.0), # 主题蓝色 + "光源": (1.0, 0.8, 0.2, 1.0), # 黄色 + "模型": (0.6, 0.8, 1.0, 1.0), # 浅蓝色 + "相机": (0.8, 0.8, 0.2, 1.0), # 橙色 + "几何体": (0.5, 0.5, 0.5, 1.0), # 灰色 + } + + if node_type in type_colors: + imgui.same_line() + imgui.text_colored(type_colors[node_type], f"[{node_type}]") + + # 功能性徽章 + badges = [] + + # 碰撞体徽章 + has_collision = hasattr(node, 'getChild') and any('Collision' in child.getName() for child in node.getChildren() if child.getName()) + if has_collision: + badges.append(("碰撞", (0.2, 0.4, 0.8, 1.0))) # 蓝色 + + # 脚本徽章 + has_script = hasattr(node, 'getPythonTag') and node.getPythonTag('script') + if has_script: + badges.append(("脚本", (0.8, 0.4, 0.8, 1.0))) # 紫色 + + # 动画徽章(优化检测逻辑,避免重复创建Actor) + has_animation = False + if node_type == "模型": # 只对模型类型进行动画检测 + model_path = node.getTag("model_path") if node.hasTag("model_path") else "" + likely_anim_format = bool(model_path and model_path.lower().endswith(('.glb', '.gltf', '.fbx', '.bam', '.egg'))) + + # 优先使用场景标签(导入/加载时会写入) + if node.hasTag("has_animations"): + has_animation = node.getTag("has_animations").lower() == "true" + + # 再做轻量结构检测(不依赖 Actor) + if not has_animation: + try: + has_character = node.findAllMatches("**/+Character").getNumPaths() > 0 + has_bundle = node.findAllMatches("**/+AnimBundleNode").getNumPaths() > 0 + has_animation = has_character or has_bundle + if has_animation: + node.setTag("has_animations", "true") + node.setTag("can_create_actor_from_memory", "true") + except Exception: + pass + + # 最后才尝试 Actor 检测(只缓存“有动画”,避免把失败结果永久缓存) + cached_result = node.getPythonTag('animation') + if cached_result is True: + has_animation = True + elif not has_animation and likely_anim_format: + try: + actor = self._getActor(node) + if actor and actor.getAnimNames(): + has_animation = True + node.setTag("has_animations", "true") + node.setPythonTag('animation', True) + print(f"[动画检测] {node.getName()}: 有动画") + except Exception as e: + print(f"动画检测失败: {e}") + elif cached_result is False and not likely_anim_format: + has_animation = False + else: + # 对于非模型类型,检查已有的动画标签 + has_animation = hasattr(node, 'getPythonTag') and node.getPythonTag('animation') + + if has_animation: + badges.append(("动画", (0.4, 0.8, 0.4, 1.0))) # 绿色 + + # 材质徽章 + has_material = hasattr(node, 'getMaterial') and node.getMaterial() + if has_material: + badges.append(("材质", (0.8, 0.6, 0.2, 1.0))) # 金色 + + # 绘制功能性徽章 + for badge_text, badge_color in badges: + imgui.same_line() + imgui.text_colored(badge_color, f"[{badge_text}]") + + # 如果没有特殊徽章,显示默认状态 + if not badges: + imgui.same_line() + imgui.text_colored((0.5, 0.5, 0.5, 1.0), "[标准对象]") + + def _draw_transform_properties(self, node): + """绘制变换属性""" + # 位置组 + if imgui.collapsing_header("位置 Position"): + # 相对位置 + imgui.text("相对位置") + pos = node.getPos() + + # X坐标 + changed, new_x = imgui.input_float("X##pos_x", pos.x, 0.1, 1.0, "%.3f") + if changed: node.setX(new_x) + + # Y坐标 + changed, new_y = imgui.input_float("Y##pos_y", pos.y, 0.1, 1.0, "%.3f") + if changed: node.setY(new_y) + + # Z坐标 + changed, new_z = imgui.input_float("Z##pos_z", pos.z, 0.1, 1.0, "%.3f") + if changed: node.setZ(new_z) + + # 世界位置 + imgui.text("世界位置") + world_pos = node.getPos(self.render) + + imgui.text(f"世界 X: {world_pos.x:.3f}") + imgui.text(f"世界 Y: {world_pos.y:.3f}") + imgui.text(f"世界 Z: {world_pos.z:.3f}") + + # 位置操作按钮 + if imgui.button("重置位置##reset_pos"): + node.setPos(0, 0, 0) + imgui.same_line() + if imgui.button("复制位置##copy_pos"): + self._clipboard_pos = (pos.x, pos.y, pos.z) + imgui.same_line() + if imgui.button("粘贴位置##paste_pos") and hasattr(self, '_clipboard_pos'): + node.setPos(self._clipboard_pos[0], self._clipboard_pos[1], self._clipboard_pos[2]) + + # 旋转组 + if imgui.collapsing_header("旋转 Rotation"): + hpr = node.getHpr() + + # HPR旋转 + imgui.text("HPR 旋转 (度)") + changed, new_h = imgui.input_float("H##rot_h", hpr.x, 1.0, 10.0, "%.1f") + if changed: node.setH(new_h) + + changed, new_p = imgui.input_float("P##rot_p", hpr.y, 1.0, 10.0, "%.1f") + if changed: node.setP(new_p) + + changed, new_r = imgui.input_float("R##rot_r", hpr.z, 1.0, 10.0, "%.1f") + if changed: node.setR(new_r) + + # 旋转操作按钮 + if imgui.button("重置旋转##reset_rot"): + node.setHpr(0, 0, 0) + imgui.same_line() + if imgui.button("随机旋转##random_rot"): + import random + node.setHpr(random.randint(0, 360), random.randint(0, 360), random.randint(0, 360)) + + # 缩放组 + if imgui.collapsing_header("缩放 Scale"): + scale = node.getScale() + + # XYZ缩放 + imgui.text("XYZ 缩放") + changed, new_sx = imgui.input_float("X##scale_x", scale.x, 0.1, 1.0, "%.3f") + if changed: node.setSx(new_sx) + + changed, new_sy = imgui.input_float("Y##scale_y", scale.y, 0.1, 1.0, "%.3f") + if changed: node.setSy(new_sy) + + changed, new_sz = imgui.input_float("Z##scale_z", scale.z, 0.1, 1.0, "%.3f") + if changed: node.setSz(new_sz) + + # 统一缩放 + if imgui.button("统一缩放##uniform_scale"): + uniform_scale = (scale.x + scale.y + scale.z) / 3.0 + node.setScale(uniform_scale, uniform_scale, uniform_scale) + imgui.same_line() + if imgui.button("重置缩放##reset_scale"): + node.setScale(1, 1, 1) + imgui.same_line() + if imgui.button("翻倍##double_scale"): + node.setScale(scale.x * 2, scale.y * 2, scale.z * 2) + + def _draw_gui_properties(self, node): + """绘制GUI元素属性""" + # 获取GUI元素 + gui_element = None + if hasattr(node, 'getPythonTag'): + gui_element = node.getPythonTag('gui_element') + + if not gui_element: + imgui.text("无GUI元素数据") + return + + # GUI类型信息 + gui_type = getattr(gui_element, 'gui_type', 'UNKNOWN') + imgui.text(f"GUI类型: {gui_type}") + + # 基本属性 + if imgui.collapsing_header("基本属性"): + # 文本内容 (适用于按钮、标签等) + if hasattr(gui_element, 'text'): + changed, new_text = imgui.input_text("文本内容", gui_element.text, 256) + if changed and hasattr(self, 'gui_manager'): + self.gui_manager.editGUIElement(gui_element, 'text', new_text) + + # GUI ID + gui_id = getattr(gui_element, 'id', '') + changed, new_id = imgui.input_text("GUI ID", gui_id, 64) + if changed and hasattr(self, 'gui_manager'): + gui_element.id = new_id + + # 变换属性 + if imgui.collapsing_header("变换属性"): + # 位置 + pos = gui_element.getPos() + imgui.text("位置") + + if gui_type in ["button", "label", "entry", "2d_image"]: + # 2D GUI组件使用屏幕坐标 + imgui.text("屏幕坐标") + logical_x = pos.getX() / 0.1 + logical_z = pos.getZ() / 0.1 + + changed, new_x = imgui.input_float("X##gui_pos_x", logical_x, 1.0, 10.0, "%.1f") + if changed and hasattr(self, 'gui_manager'): + gui_element.setX(new_x * 0.1) + + changed, new_z = imgui.input_float("Y##gui_pos_y", logical_z, 1.0, 10.0, "%.1f") + if changed and hasattr(self, 'gui_manager'): + gui_element.setZ(new_z * 0.1) + else: + # 3D GUI组件使用世界坐标 + imgui.text("世界坐标") + changed, new_x = imgui.input_float("X##gui_world_x", pos.getX(), 0.1, 1.0, "%.3f") + if changed and hasattr(self, 'gui_manager'): + gui_element.setX(new_x) + + changed, new_y = imgui.input_float("Y##gui_world_y", pos.getY(), 0.1, 1.0, "%.3f") + if changed and hasattr(self, 'gui_manager'): + gui_element.setY(new_y) + + changed, new_z = imgui.input_float("Z##gui_world_z", pos.getZ(), 0.1, 1.0, "%.3f") + if changed and hasattr(self, 'gui_manager'): + gui_element.setZ(new_z) + + # 缩放 + scale = gui_element.getScale() + imgui.text("缩放") + changed, new_sx = imgui.input_float("X##gui_scale_x", scale.getX(), 0.1, 1.0, "%.3f") + if changed and hasattr(self, 'gui_manager'): + gui_element.setSx(new_sx) + + changed, new_sy = imgui.input_float("Y##gui_scale_y", scale.getY(), 0.1, 1.0, "%.3f") + if changed and hasattr(self, 'gui_manager'): + gui_element.setSy(new_sy) + + changed, new_sz = imgui.input_float("Z##gui_scale_z", scale.getZ(), 0.1, 1.0, "%.3f") + if changed and hasattr(self, 'gui_manager'): + gui_element.setSz(new_sz) + + # 旋转 + hpr = gui_element.getHpr() + imgui.text("旋转") + changed, new_h = imgui.input_float("H##gui_rot_h", hpr.getX(), 1.0, 10.0, "%.1f") + if changed and hasattr(self, 'gui_manager'): + gui_element.setH(new_h) + + changed, new_p = imgui.input_float("P##gui_rot_p", hpr.getY(), 1.0, 10.0, "%.1f") + if changed and hasattr(self, 'gui_manager'): + gui_element.setP(new_p) + + changed, new_r = imgui.input_float("R##gui_rot_r", hpr.getZ(), 1.0, 10.0, "%.1f") + if changed and hasattr(self, 'gui_manager'): + gui_element.setR(new_r) + + # 外观属性 + if imgui.collapsing_header("外观属性"): + # 大小 + if hasattr(gui_element, 'size'): + size = gui_element.size + imgui.text("大小") + changed, new_w = imgui.input_float("宽度", size[0], 1.0, 10.0, "%.1f") + if changed and hasattr(self, 'gui_manager'): + new_size = (new_w, size[1]) + self.gui_manager.editGUIElement(gui_element, 'size', new_size) + + changed, new_h = imgui.input_float("高度", size[1], 1.0, 10.0, "%.1f") + if changed and hasattr(self, 'gui_manager'): + new_size = (size[0], new_h) + self.gui_manager.editGUIElement(gui_element, 'size', new_size) + + # 颜色 + if hasattr(gui_element, 'getColor'): + try: + color = gui_element.getColor() + # 确保颜色是有效的 + if not color or (hasattr(color, '__len__') and len(color) < 3): + color = (1.0, 1.0, 1.0, 1.0) # 默认白色 + except: + color = (1.0, 1.0, 1.0, 1.0) # 默认白色 + + imgui.text("颜色") + # 获取颜色值 + if hasattr(color, 'getX'): + # 如果是Panda3D的Vec4对象 + r, g, b = color.getX(), color.getY(), color.getZ() + else: + # 如果是元组或其他格式 + r, g, b = color[0], color[1], color[2] + + changed, new_r = imgui.slider_float("R##gui_color_r", r, 0.0, 1.0) + if changed: gui_element.setColor(new_r, g, b, 1.0) + + changed, new_g = imgui.slider_float("G##gui_color_g", g, 0.0, 1.0) + if changed: gui_element.setColor(r, new_g, b, 1.0) + + changed, new_b = imgui.slider_float("B##gui_color_b", b, 0.0, 1.0) + if changed: gui_element.setColor(r, g, new_b, 1.0) + # 透明度 + imgui.text("透明度") + current_alpha = getattr(gui_element, 'alpha', 1.0) + changed, new_alpha = imgui.slider_float("Alpha", current_alpha, 0.0, 1.0) + if changed: + gui_element.alpha = new_alpha + if hasattr(gui_element, 'setTransparency'): + # 将0.0-1.0范围转换为Panda3D的透明度格式 + panda_transparency = int((1.0 - new_alpha) * 255) + gui_element.setTransparency(panda_transparency) + + # 渲染顺序 + imgui.text("渲染顺序") + current_sort = getattr(gui_element, 'sort', 0) + changed, new_sort = imgui.input_int("Sort Order", current_sort) + if changed: + gui_element.sort = new_sort + if hasattr(gui_element, 'setBin'): + gui_element.setBin('fixed', new_sort) + + # 字体设置(适用于文本类型的GUI元素) + if gui_type in ["button", "label", "entry"]: + imgui.text("字体设置") + + # 字体选择 + current_font = getattr(gui_element, 'font_path', '') + if imgui.button(f"字体: {Path(current_font).name if current_font else '默认'}##font_select"): + self.show_font_selector( + gui_element, + 'font_path', + current_font, + lambda font_path: self._apply_gui_font(gui_element, font_path) + ) + + # 字体大小 + current_size = getattr(gui_element, 'font_size', 12) + changed, new_size = imgui.slider_float("字体大小", current_size, 8.0, 72.0) + if changed: + gui_element.font_size = new_size + self._apply_gui_font_size(gui_element, new_size) + + # 字体样式 + imgui.text("字体样式") + is_bold = getattr(gui_element, 'font_bold', False) + changed, new_bold = imgui.checkbox("粗体", is_bold) + if changed: + gui_element.font_bold = new_bold + self._apply_gui_font_style(gui_element) + + imgui.same_line() + is_italic = getattr(gui_element, 'font_italic', False) + changed, new_italic = imgui.checkbox("斜体", is_italic) + if changed: + gui_element.font_italic = new_italic + self._apply_gui_font_style(gui_element) + + def _draw_light_properties(self, node): + """绘制光源属性""" + imgui.text("光源属性") + + # 光源颜色 + if hasattr(node, 'getColor'): + try: + color = node.getColor() + # 确保颜色是有效的 + if not color or len(color) < 3: + color = (1.0, 1.0, 1.0, 1.0) # 默认白色 + except: + color = (1.0, 1.0, 1.0, 1.0) # 默认白色 + + changed, new_r = imgui.drag_float("颜色 R", color[0], 0.01, 0.0, 1.0) + if changed: node.setColor(new_r, color[1], color[2], color[3] if len(color) > 3 else 1.0) + + changed, new_g = imgui.drag_float("颜色 G", color[1], 0.01, 0.0, 1.0) + if changed: node.setColor(color[0], new_g, color[2], color[3] if len(color) > 3 else 1.0) + + changed, new_b = imgui.drag_float("颜色 B", color[2], 0.01, 0.0, 1.0) + if changed: node.setColor(color[0], color[1], new_b, color[3] if len(color) > 3 else 1.0) + + # 光源强度 + imgui.text("光源强度: (暂不支持编辑)") + + def _draw_model_properties(self, node): + """绘制模型属性""" + # 获取模型信息 + model_path = node.getTag("model_path") if node.hasTag("model_path") else "未知" + + imgui.text("模型路径:") + imgui.same_line() + imgui.text_colored((0.7, 0.7, 0.7, 1.0), model_path) + + # 模型基本信息 + imgui.text("模型名称:") + imgui.same_line() + model_name = node.getName() or "未命名模型" + imgui.text_colored((0.7, 0.7, 0.7, 1.0), model_name) + + # 模型位置信息 + imgui.text("位置:") + imgui.same_line() + pos = node.getPos() + imgui.text_colored((0.7, 0.7, 0.7, 1.0), f"X:{pos.x:.2f} Y:{pos.y:.2f} Z:{pos.z:.2f}") + + # 模型缩放信息 + imgui.text("缩放:") + imgui.same_line() + scale = node.getScale() + imgui.text_colored((0.7, 0.7, 0.7, 1.0), f"X:{scale.x:.2f} Y:{scale.y:.2f} Z:{scale.z:.2f}") + + # 模型旋转信息 + imgui.text("旋转:") + imgui.same_line() + hpr = node.getHpr() + imgui.text_colored((0.7, 0.7, 0.7, 1.0), f"H:{hpr.x:.1f}° P:{hpr.y:.1f}° R:{hpr.z:.1f}°") + + def _draw_animation_properties(self, node): + """绘制动画控制属性面板(优化版本,使用缓存避免重复计算)""" + anim_node = node + try: + if hasattr(self, "_resolve_animation_owner_model"): + resolved = self._resolve_animation_owner_model(node) + if resolved and not resolved.isEmpty(): + anim_node = resolved + except Exception: + pass + + # 路径兜底:当 anim_node 缺少路径时,从 scene_manager.models 中反查祖先模型 + try: + needs_path = (not anim_node.hasTag("model_path")) or (not anim_node.getTag("model_path")) + if needs_path and hasattr(self, "scene_manager") and self.scene_manager: + models = getattr(self.scene_manager, "models", []) + for model in list(models): + try: + if not model or model.isEmpty(): + continue + if (model == anim_node or model.isAncestorOf(anim_node) or anim_node.isAncestorOf(model)): + if model.hasTag("model_path") and model.getTag("model_path"): + anim_node.setTag("model_path", model.getTag("model_path")) + if model.hasTag("original_path") and model.getTag("original_path"): + anim_node.setTag("original_path", model.getTag("original_path")) + break + if model.hasTag("saved_model_path") and model.getTag("saved_model_path"): + anim_node.setTag("model_path", model.getTag("saved_model_path")) + break + except Exception: + continue + except Exception: + pass + + # 先刷新一次模型动画标签,避免“导入后未初始化”导致误判 + try: + if hasattr(self, "scene_manager") and self.scene_manager and hasattr(self.scene_manager, "_processModelAnimations"): + self.scene_manager._processModelAnimations(anim_node) + except Exception: + pass + + has_animation_tag = anim_node.hasTag("has_animations") and anim_node.getTag("has_animations").lower() == "true" + has_animation_nodes = False + try: + has_animation_nodes = ( + anim_node.findAllMatches("**/+Character").getNumPaths() > 0 or + anim_node.findAllMatches("**/+AnimBundleNode").getNumPaths() > 0 + ) + except Exception: + pass + + # 检查是否已经缓存了动画信息 + cached_anim_info = anim_node.getPythonTag("cached_anim_info") + cached_processed_names = anim_node.getPythonTag("cached_processed_names") + + # 如果之前缓存的是“格式未知”,但现在已有路径,强制重建缓存 + try: + now_has_path = anim_node.hasTag("model_path") and bool(anim_node.getTag("model_path")) + if now_has_path and isinstance(cached_anim_info, str) and "格式: 未知" in cached_anim_info: + cached_anim_info = None + cached_processed_names = None + anim_node.setPythonTag("cached_anim_info", None) + anim_node.setPythonTag("cached_processed_names", None) + anim_node.setPythonTag("animation", None) + self._clear_animation_cache(anim_node) + except Exception: + pass + + # 如果节点已被检测为有动画,但缓存是“无动画”,强制重新检测一次 + if (has_animation_tag or has_animation_nodes) and (cached_anim_info == "无动画" or cached_processed_names == []): + cached_anim_info = None + cached_processed_names = None + anim_node.setPythonTag("cached_anim_info", None) + anim_node.setPythonTag("cached_processed_names", None) + anim_node.setPythonTag("animation", None) + + # 只有在没有缓存时才进行完整的动画检测和处理 + if cached_anim_info is None or cached_processed_names is None: + # 获取Actor + actor = self._getActor(anim_node) + if not actor: + if has_animation_tag or has_animation_nodes: + imgui.text_colored((1.0, 0.7, 0.3, 1.0), "检测到动画结构,但当前未成功绑定Actor") + else: + imgui.text_colored((0.7, 0.7, 0.7, 1.0), "此模型不包含动画") + return + + # 获取和分析动画名称 + anim_names = actor.getAnimNames() + processed_names = self._processAnimationNames(anim_node, anim_names) + + if not processed_names: + imgui.text_colored((0.7, 0.7, 0.7, 1.0), "未检测到动画序列") + # 只在明确无动画时缓存空结果,避免误缓存导致后续无法重试 + if not (has_animation_tag or has_animation_nodes): + anim_node.setPythonTag("cached_processed_names", []) + anim_node.setPythonTag("cached_anim_info", "无动画") + return + + anim_node.setTag("has_animations", "true") + anim_node.setPythonTag("animation", True) + + # 计算并缓存动画信息 + format_info = self._getModelFormat(anim_node) + animation_info = self._analyzeAnimationQuality(actor, anim_names, format_info) + info_text = f"格式: {format_info} | 动画数量: {len(processed_names)}" + if animation_info: + info_text += f" | {animation_info}" + + # 缓存结果 + anim_node.setPythonTag("cached_anim_info", info_text) + anim_node.setPythonTag("cached_processed_names", processed_names) + + else: + # 使用缓存的数据 + info_text = cached_anim_info + processed_names = cached_processed_names + + # 如果缓存的空结果,直接返回 + if not processed_names: + imgui.text_colored((0.7, 0.7, 0.7, 1.0), "未检测到动画序列") + return + + # 显示动画信息(使用缓存的数据) + imgui.text("信息:") + imgui.same_line() + imgui.text_colored((0.7, 0.7, 0.7, 1.0), info_text) + + imgui.spacing() + + # 动画选择下拉框 + imgui.text("动画名称:") + imgui.same_line() + + # 获取当前选中的动画 + current_anim = anim_node.getPythonTag("selected_animation") + valid_original_names = [original_name for _, original_name in processed_names] + if current_anim is None or current_anim not in valid_original_names: + current_anim = processed_names[0][1] if processed_names else "" + anim_node.setPythonTag("selected_animation", current_anim) + + # 查找当前动画的索引 + current_index = 0 + for i, (display_name, original_name) in enumerate(processed_names): + if original_name == current_anim: + current_index = i + break + + # 创建下拉框选项 + animation_options = [display_name for display_name, _ in processed_names] + changed, new_index = imgui.combo("##animation_combo", current_index, animation_options) + + if changed and new_index < len(processed_names): + selected_display, selected_original = processed_names[new_index] + anim_node.setPythonTag("selected_animation", selected_original) + print(f"选择动画: {selected_display} (原始名称: {selected_original})") + + imgui.spacing() + + # 控制按钮组 + imgui.text("控制:") + + # 播放按钮 + if imgui.button("播放##play_animation"): + self._playAnimation(anim_node) + imgui.same_line() + + # 暂停按钮 + if imgui.button("暂停##pause_animation"): + self._pauseAnimation(anim_node) + imgui.same_line() + + # 停止按钮 + if imgui.button("停止##stop_animation"): + self._stopAnimation(anim_node) + imgui.same_line() + + # 循环按钮 + if imgui.button("循环##loop_animation"): + self._loopAnimation(anim_node) + + imgui.spacing() + + # 播放速度控制 + imgui.text("播放速度:") + imgui.same_line() + + # 获取当前速度 + current_speed = anim_node.getPythonTag("anim_speed") + if current_speed is None: + current_speed = 1.0 + anim_node.setPythonTag("anim_speed", current_speed) + + # 速度滑块 + changed, new_speed = imgui.slider_float("##anim_speed", current_speed, 0.1, 5.0, "%.1f") + if changed: + anim_node.setPythonTag("anim_speed", new_speed) + self._setAnimationSpeed(anim_node, new_speed) + + imgui.same_line() + imgui.text_colored((0.7, 0.7, 0.7, 1.0), "倍速") + + def _draw_collision_properties(self, node): + """绘制碰撞检测属性""" + if not node or node.isEmpty(): + return + + try: + # 检查节点是否已有碰撞 + has_collision = self._has_collision(node) + + # 碰撞状态徽章 + imgui.text("状态:") + imgui.same_line() + if has_collision: + imgui.text_colored((0.0, 0.8, 0.0, 1.0), "🟢 已启用") + else: + imgui.text_colored((0.8, 0.0, 0.0, 1.0), "🔴 未启用") + + imgui.separator() + + # 碰撞形状选择 + imgui.text("碰撞形状:") + imgui.same_line() + + # 碰撞形状选项 + collision_shapes = ["球形 (Sphere)", "盒型 (Box)", "胶囊体 (Capsule)", "平面 (Plane)", "自动选择 (Auto)"] + + # 获取当前形状 + current_shape = self._get_current_collision_shape(node) if has_collision else getattr(self.app, '_selected_collision_shape', "球形 (Sphere)") + if has_collision: self.app._selected_collision_shape = current_shape + + # 形状选择下拉框 + current_index = collision_shapes.index(current_shape) if current_shape in collision_shapes else 0 + changed, selected_index = imgui.combo("##collision_shape", current_index, collision_shapes) + if changed: + # 始终更新选择的形状 + selected_shape = collision_shapes[selected_index] + self.app._selected_collision_shape = selected_shape + # 如果已经有碰撞体,询问用户是否要重新创建 + if has_collision: + self._remove_collision_from_node(node) + self._add_collision_to_node(node) + + imgui.separator() + + # 位置偏移控件 + imgui.text("位置偏移:") + + # 获取当前位置偏移 + pos_offset = self._get_collision_position_offset(node) + + # X位置 + changed, new_x = imgui.drag_float("X##collision_pos_x", pos_offset[0], 0.1, -100.0, 100.0, "%.2f") + if changed and has_collision: + self._update_collision_position(node, 'x', new_x) + + # Y位置 + changed, new_y = imgui.drag_float("Y##collision_pos_y", pos_offset[1], 0.1, -100.0, 100.0, "%.2f") + if changed and has_collision: + self._update_collision_position(node, 'y', new_y) + + # Z位置 + changed, new_z = imgui.drag_float("Z##collision_pos_z", pos_offset[2], 0.1, -100.0, 100.0, "%.2f") + if changed and has_collision: + self._update_collision_position(node, 'z', new_z) + + # 形状特定参数(始终显示,但根据状态启用/禁用) + shape_type = self._get_current_collision_shape_type(node) + self._draw_shape_specific_parameters(node, shape_type, has_collision) + + imgui.separator() + + # 操作按钮 + if has_collision: + # 显示/隐藏碰撞体按钮 + is_visible = self._is_collision_visible(node) + visibility_text = "隐藏碰撞体" if is_visible else "显示碰撞体" + if imgui.button(visibility_text): + self._toggle_collision_visibility(node) + + imgui.same_line() + + # 移除碰撞按钮 + if imgui.button("移除碰撞"): + self._remove_collision_from_node(node) + else: + # 添加碰撞按钮 + if imgui.button("添加碰撞"): + self._add_collision_to_node(node) + + imgui.separator() + + # 碰撞检测触发模式 + imgui.text("触发模式:") + + # 自动检测开关 + auto_enabled = self.collision_manager.model_collision_enabled if hasattr(self, 'collision_manager') else False + changed, new_auto = imgui.checkbox("自动检测", auto_enabled) + if changed and hasattr(self, 'collision_manager'): + self.collision_manager.enableModelCollisionDetection(new_auto, 0.1, 0.5) + + imgui.same_line() + + # 手动检测按钮 + if imgui.button("立即检测"): + if hasattr(self, 'collision_manager'): + self._manual_collision_detection() + + except Exception as e: + print(f"绘制碰撞属性失败: {e}") + import traceback + traceback.print_exc() + + def _draw_shape_specific_parameters(self, node, shape_type, has_collision=True): + """绘制形状特定参数""" + try: + if shape_type == "sphere": + self._draw_sphere_parameters(node, has_collision) + elif shape_type == "box": + self._draw_box_parameters(node, has_collision) + elif shape_type == "capsule": + self._draw_capsule_parameters(node, has_collision) + elif shape_type == "plane": + self._draw_plane_parameters(node, has_collision) + except Exception as e: + print(f"绘制形状参数失败: {e}") + + def _draw_sphere_parameters(self, node, has_collision=True): + """绘制球形参数""" + try: + imgui.text("球形参数:") + imgui.same_line() + + # 获取当前半径 + radius = self._get_sphere_radius(node) + + # 半径调整 + changed, new_radius = imgui.drag_float("半径##sphere_radius", radius, 0.1, 0.1, 100.0, "%.2f") + if changed and has_collision: + self._update_sphere_radius(node, new_radius) + + except Exception as e: + print(f"绘制球形参数失败: {e}") + + def _draw_box_parameters(self, node, has_collision=True): + """绘制盒型参数""" + try: + imgui.text("盒型参数:") + + # 获取当前尺寸 + size = self._get_box_size(node) + + # 尺寸调整 + changed, new_x = imgui.drag_float("长度##box_length", size[0], 0.1, 0.1, 100.0, "%.2f") + if changed and has_collision: + self._update_box_size(node, 'x', new_x) + + changed, new_y = imgui.drag_float("宽度##box_width", size[1], 0.1, 0.1, 100.0, "%.2f") + if changed and has_collision: + self._update_box_size(node, 'y', new_y) + + changed, new_z = imgui.drag_float("高度##box_height", size[2], 0.1, 0.1, 100.0, "%.2f") + if changed and has_collision: + self._update_box_size(node, 'z', new_z) + + except Exception as e: + print(f"绘制盒型参数失败: {e}") + + def _draw_capsule_parameters(self, node, has_collision=True): + """绘制胶囊体参数""" + try: + imgui.text("胶囊体参数:") + + # 获取当前参数 + radius = self._get_capsule_radius(node) + height = self._get_capsule_height(node) + + # 半径调整 + changed, new_radius = imgui.drag_float("半径##capsule_radius", radius, 0.1, 0.1, 100.0, "%.2f") + if changed and has_collision: + self._update_capsule_radius(node, new_radius) + + # 高度调整 + changed, new_height = imgui.drag_float("高度##capsule_height", height, 0.1, 0.1, 100.0, "%.2f") + if changed and has_collision: + self._update_capsule_height(node, new_height) + + except Exception as e: + print(f"绘制胶囊体参数失败: {e}") + + def _draw_plane_parameters(self, node, has_collision=True): + """绘制平面参数""" + try: + imgui.text("平面参数:") + + # 获取当前法向量 + normal = self._get_plane_normal(node) + + # 法向量调整 + changed, new_x = imgui.drag_float("法向量 X##plane_normal_x", normal[0], 0.1, -1.0, 1.0, "%.2f") + if changed and has_collision: + self._update_plane_normal(node, 'x', new_x) + + changed, new_y = imgui.drag_float("法向量 Y##plane_normal_y", normal[1], 0.1, -1.0, 1.0, "%.2f") + if changed and has_collision: + self._update_plane_normal(node, 'y', new_y) + + changed, new_z = imgui.drag_float("法向量 Z##plane_normal_z", normal[2], 0.1, -1.0, 1.0, "%.2f") + if changed and has_collision: + self._update_plane_normal(node, 'z', new_z) + + except Exception as e: + print(f"绘制平面参数失败: {e}") + + def _draw_property_actions(self, node): + """绘制属性操作按钮""" + # 重置变换 + if imgui.button("重置变换"): + node.setPos(0, 0, 0) + node.setHpr(0, 0, 0) + node.setScale(1, 1, 1) + + imgui.same_line() + + # 切换可见性 + is_visible = not node.is_hidden() + visibility_text = "隐藏" if is_visible else "显示" + if imgui.button(visibility_text): + if is_visible: + node.hide() + else: + node.show() + + imgui.same_line() + + # 聚焦到对象 + if imgui.button("聚焦"): + if hasattr(self, 'selection') and self.selection: + self.selection.focusCameraOnSelectedNodeAdvanced() + + # 删除对象 + imgui.same_line() + if imgui.button("删除"): + if hasattr(self, 'selection') and self.selection: + self.selection.deleteSelectedNode() + + def _draw_appearance_properties(self, node): + """绘制外观属性""" + # 颜色属性 + if hasattr(node, 'getColor'): + imgui.text("颜色") + try: + color = node.getColor() + # 确保颜色是有效的 + if not color or len(color) < 3: + color = (1.0, 1.0, 1.0, 1.0) # 默认白色 + except: + color = (1.0, 1.0, 1.0, 1.0) # 默认白色 + + # 颜色滑块 + changed, new_r = imgui.slider_float("R##color_r", color[0], 0.0, 1.0) + if changed: + new_color = (new_r, color[1], color[2], color[3] if len(color) > 3 else 1.0) + node.setColor(new_color) + color = new_color + + changed, new_g = imgui.slider_float("G##color_g", color[1], 0.0, 1.0) + if changed: + new_color = (color[0], new_g, color[2], color[3] if len(color) > 3 else 1.0) + node.setColor(new_color) + color = new_color + + changed, new_b = imgui.slider_float("B##color_b", color[2], 0.0, 1.0) + if changed: + new_color = (color[0], color[1], new_b, color[3] if len(color) > 3 else 1.0) + node.setColor(new_color) + color = new_color + + # 只有当颜色有alpha通道时才显示alpha滑块 + if len(color) > 3: + changed, new_a = imgui.slider_float("A##color_a", color[3], 0.0, 1.0) + if changed: + new_color = (color[0], color[1], color[2], new_a) + node.setColor(new_color) + color = new_color + + # 颜色预览和选择器 + imgui.text("颜色预览") + color_with_alpha = (color[0], color[1], color[2], color[3] if len(color) > 3 else 1.0) + if imgui.color_button("颜色预览##preview", color_with_alpha, 0, (100, 30)): + # 点击颜色按钮打开颜色选择器 + self.show_color_picker(node, 'color', color_with_alpha) + + imgui.same_line() + if imgui.button("选择颜色##color_picker_btn"): + self.show_color_picker(node, 'color', (color.x, color.y, color.z, color.w)) + + # 透明度 + if hasattr(node, 'setTransparency') and hasattr(node, 'getTransparency'): + imgui.text("透明度") + current_transparency = node.getTransparency() + # 将当前的透明度值转换为0.0-1.0范围用于显示 + display_transparency = 1.0 - current_transparency if current_transparency <= 1 else 0.0 + changed, new_transparency = imgui.slider_float("透明度", display_transparency, 0.0, 1.0) + if changed: + # 将0.0-1.0范围转换回Panda3D的透明度格式 + panda_transparency = int((1.0 - new_transparency) * 255) + node.setTransparency(panda_transparency) + + # 材质属性 + self._draw_material_properties(node) + + # 渲染状态 + imgui.text("渲染状态") + if imgui.button("应用材质"): + self._apply_material_to_node(node) + + imgui.same_line() + if imgui.button("重置材质"): + self._reset_material(node) + + def _draw_material_properties(self, node): + """绘制材质属性""" + materials = node.find_all_materials() + + if not materials: + imgui.text_colored((0.5, 0.5, 0.5, 1.0), "无材质") + return + + for i, material in enumerate(materials): + material_name = material.get_name() if hasattr(material, 'get_name') and material.get_name() else f"材质{i + 1}" + + if imgui.collapsing_header(f"材质: {material_name}"): + # 材质基础颜色 + base_color = self._get_material_base_color(material) + if base_color: + imgui.text("基础颜色") + changed, new_r = imgui.slider_float(f"R##mat_r_{i}", base_color[0], 0.0, 1.0) + if changed: + self._update_material_base_color(material, 'r', new_r) + base_color = (new_r, base_color[1], base_color[2], base_color[3]) + + changed, new_g = imgui.slider_float(f"G##mat_g_{i}", base_color[1], 0.0, 1.0) + if changed: + self._update_material_base_color(material, 'g', new_g) + base_color = (base_color[0], new_g, base_color[2], base_color[3]) + + changed, new_b = imgui.slider_float(f"B##mat_b_{i}", base_color[2], 0.0, 1.0) + if changed: + self._update_material_base_color(material, 'b', new_b) + base_color = (base_color[0], base_color[1], new_b, base_color[3]) + + changed, new_a = imgui.slider_float(f"A##mat_a_{i}", base_color[3], 0.0, 1.0) + if changed: + self._update_material_base_color(material, 'a', new_a) + base_color = (base_color[0], base_color[1], base_color[2], new_a) + + # PBR属性 + if hasattr(material, 'roughness') and material.roughness is not None: + imgui.text("PBR属性") + try: + roughness_value = float(material.roughness) + changed, new_roughness = imgui.slider_float(f"粗糙度##rough_{i}", roughness_value, 0.0, 1.0) + if changed: + self._update_material_roughness(material, new_roughness) + except: + imgui.text_colored((0.7, 0.7, 0.7, 1.0), "粗糙度: 不可用") + + if hasattr(material, 'metallic') and material.metallic is not None: + try: + metallic_value = float(material.metallic) + changed, new_metallic = imgui.slider_float(f"金属性##metal_{i}", metallic_value, 0.0, 1.0) + if changed: + self._update_material_metallic(material, new_metallic) + except: + imgui.text_colored((0.7, 0.7, 0.7, 1.0), "金属性: 不可用") + + if hasattr(material, 'refractive_index') and material.refractive_index is not None: + try: + ior_value = float(material.refractive_index) + changed, new_ior = imgui.slider_float(f"折射率##ior_{i}", ior_value, 1.0, 3.0) + if changed: + self._update_material_ior(material, new_ior) + except: + imgui.text_colored((0.7, 0.7, 0.7, 1.0), "折射率: 不可用") + + # 材质预设 + imgui.text("材质预设") + presets = ["默认", "金属", "塑料", "玻璃", "木材", "混凝土"] + current_preset = 0 # 默认选择 + + if imgui.begin_combo(f"预设##preset_{i}", presets[current_preset]): + for j, preset_name in enumerate(presets): + if imgui.selectable(preset_name, j == current_preset): + self._apply_material_preset(material, preset_name) + imgui.end_combo() + + # 纹理信息 + imgui.text("纹理贴图") + if imgui.button(f"选择漫反射贴图##diffuse_{i}"): + self._select_texture_for_material(node, material, "diffuse") + + imgui.same_line() + if imgui.button(f"选择法线贴图##normal_{i}"): + self._select_texture_for_material(node, material, "normal") + + imgui.same_line() + if imgui.button(f"选择粗糙度贴图##roughness_{i}"): + self._select_texture_for_material(node, material, "roughness") + + if imgui.button(f"选择金属性贴图##metallic_{i}"): + self._select_texture_for_material(node, material, "metallic") + + imgui.same_line() + if imgui.button(f"选择自发光贴图##emission_{i}"): + self._select_texture_for_material(node, material, "emission") + + imgui.same_line() + if imgui.button(f"清除所有贴图##clear_{i}"): + self._clear_all_textures(node) + + # 着色模型选择 + self._draw_shading_model_panel(material, i) + + # 显示当前纹理信息 + self._display_current_textures(node, material) + + def _draw_shading_model_panel(self, material, material_index): + """绘制着色模型选择面板""" + try: + imgui.text("着色模型") + + # RenderPipeline支持的着色模型 + shading_models = ["默认", "自发光", "透明"] + current_model = 0 # 默认选择 + + # 安全地获取当前着色模型 + try: + if hasattr(material, 'emission') and material.emission is not None: + current_model = int(material.emission.x) + except: + current_model = 0 + + # 着色模型选择 + if imgui.begin_combo(f"着色模型##shading_{material_index}", shading_models[current_model]): + for j, model_name in enumerate(shading_models): + if imgui.selectable(model_name, j == current_model): + self._update_shading_model(material, j) + imgui.end_combo() + + # 如果是透明着色模型,添加透明度控制 + if current_model == 3: # 透明着色模型 + imgui.text("透明度设置") + try: + if hasattr(material, 'shading_model_param0'): + current_opacity = material.shading_model_param0 + else: + current_opacity = 1.0 + + changed, new_opacity = imgui.slider_float(f"不透明度##opacity_{material_index}", current_opacity, 0.0, 1.0) + if changed: + self._update_transparency(material, new_opacity) + except: + imgui.text_colored((0.7, 0.7, 0.7, 1.0), "透明度控制不可用") + + except Exception as e: + print(f"绘制着色模型面板失败: {e}") + diff --git a/ui/panels/editor_panels_top.py b/ui/panels/editor_panels_top.py new file mode 100644 index 00000000..bde4fb91 --- /dev/null +++ b/ui/panels/editor_panels_top.py @@ -0,0 +1,323 @@ +from imgui_bundle import imgui, imgui_ctx + +class EditorPanelsTopMixin: + """Auto-split mixin from editor_panels.py.""" + + def draw_menu_bar(self): + """绘制菜单栏""" + with imgui_ctx.begin_main_menu_bar() as main_menu: + if main_menu: + # 文件菜单 + with imgui_ctx.begin_menu("文件") as file_menu: + if file_menu: + if imgui.menu_item("新建项目", "Ctrl+N", False, True)[1]: + self.app._on_new_project() + if imgui.menu_item("打开项目", "Ctrl+O", False, True)[1]: + self.app._on_open_project() + imgui.separator() + if imgui.menu_item("保存", "Ctrl+S", False, True)[1]: + self.app._on_save_project() + if imgui.menu_item("另存为", "", False, True)[1]: + self.app._on_save_as_project() + imgui.separator() + if imgui.menu_item("退出", "Alt+F4", False, True)[1]: + self.app._on_exit() + + # 编辑菜单 + with imgui_ctx.begin_menu("编辑") as edit_menu: + if edit_menu: + if imgui.menu_item("撤销", "Ctrl+Z", False, True)[1]: + self.app._on_undo() + if imgui.menu_item("重做", "Ctrl+Y", False, True)[1]: + self.app._on_redo() + imgui.separator() + if imgui.menu_item("剪切", "Ctrl+X", False, True)[1]: + self.app._on_cut() + if imgui.menu_item("复制", "Ctrl+C", False, True)[1]: + self.app._on_copy() + if imgui.menu_item("粘贴", "Ctrl+V", False, True)[1]: + self.app._on_paste() + imgui.separator() + if imgui.menu_item("删除", "Del", False, True)[1]: + self.app._on_delete() + + # 创建菜单 + with imgui_ctx.begin_menu("创建") as create_menu: + if create_menu: + if imgui.menu_item("导入模型", "", False, True)[1]: + self.app._on_import_model() + + imgui.separator() + + if imgui.menu_item("空对象", "", False, True)[1]: + self.app._on_create_empty_object() + + # 3D对象子菜单 + with imgui_ctx.begin_menu("3D对象") as three_d_menu: + if three_d_menu: + if imgui.menu_item("立方体", "", False, True)[1]: + self.app._on_create_cube() + if imgui.menu_item("球体", "", False, True)[1]: + self.app._on_create_sphere() + if imgui.menu_item("圆柱体", "", False, True)[1]: + self.app._on_create_cylinder() + if imgui.menu_item("平面", "", False, True)[1]: + self.app._on_create_plane() + + # 3D GUI子菜单 + # with imgui_ctx.begin_menu("3D GUI") as three_d_gui_menu: + # if three_d_gui_menu: + # if imgui.menu_item("3D文本", "", False, True)[1]: + # self.app._on_create_3d_text() + # if imgui.menu_item("3D图片", "", False, True)[1]: + # self.app._on_create_3d_image() + + # # GUI子菜单 + # with imgui_ctx.begin_menu("GUI") as gui_menu: + # if gui_menu: + # if imgui.menu_item("创建按钮", "", False, True)[1]: + # self.app._on_create_gui_button() + # if imgui.menu_item("创建标签", "", False, True)[1]: + # self.app._on_create_gui_label() + # if imgui.menu_item("创建输入框", "", False, True)[1]: + # self.app._on_create_gui_entry() + # if imgui.menu_item("创建图片", "", False, True)[1]: + # self.app._on_create_gui_image() + # imgui.separator() + # if imgui.menu_item("创建视频屏幕", "", False, True)[1]: + # self.app._on_create_video_screen() + # if imgui.menu_item("创建2D视频屏幕", "", False, True)[1]: + # self.app._on_create_2d_video_screen() + # if imgui.menu_item("创建球形视频", "", False, True)[1]: + # self.app._on_create_spherical_video() + # if imgui.menu_item("创建虚拟屏幕", "", False, True)[1]: + # self.app._on_create_virtual_screen() + + # 光源子菜单 + with imgui_ctx.begin_menu("光源") as light_menu: + if light_menu: + if imgui.menu_item("聚光灯", "", False, True)[1]: + self.app._on_create_spot_light() + if imgui.menu_item("点光源", "", False, True)[1]: + self.app._on_create_point_light() + + # 地形子菜单 + with imgui_ctx.begin_menu("地形") as terrain_menu: + if terrain_menu: + if imgui.menu_item("创建平面地形", "", False, True)[1]: + self.app._on_create_flat_terrain() + if imgui.menu_item("从高度图创建地形", "", False, True)[1]: + self.app._on_create_heightmap_terrain() + + # 脚本子菜单 + with imgui_ctx.begin_menu("脚本") as script_menu: + if script_menu: + if imgui.menu_item("创建脚本...", "", False, True)[1]: + self.app._on_create_script() + if imgui.menu_item("加载脚本文件...", "", False, True)[1]: + self.app._on_load_script() + imgui.separator() + if imgui.menu_item("重载所有脚本", "", False, True)[1]: + self.app._on_reload_all_scripts() + _, self.app.hotReloadEnabled = imgui.menu_item("启用热重载", "", self.app.hotReloadEnabled, True) + if imgui.menu_item("脚本管理器", "", False, True)[1]: + self.app._on_open_scripts_manager() + + # 信息面板子菜单 + with imgui_ctx.begin_menu("信息面板") as info_panel_menu: + if info_panel_menu: + # if imgui.menu_item("创建2D示例面板", "", False, True)[1]: + # self.app._on_create_2d_sample_panel() + # if imgui.menu_item("创建3D实例面板", "", False, True)[1]: + # self.app._on_create_3d_sample_panel() + if imgui.menu_item("Web面板", "", False, True)[1]: + self.app._on_create_web_panel() + + # 视图菜单 + with imgui_ctx.begin_menu("视图") as view_menu: + if view_menu: + _, self.app.showToolbar = imgui.menu_item("工具栏", "", self.app.showToolbar, True) + _, self.app.showSceneTree = imgui.menu_item("场景树", "", self.app.showSceneTree, True) + _, self.app.showResourceManager = imgui.menu_item("资源管理器", "", self.app.showResourceManager, True) + _, self.app.showPropertyPanel = imgui.menu_item("属性面板", "", self.app.showPropertyPanel, True) + _, self.app.showConsole = imgui.menu_item("控制台", "", self.app.showConsole, True) + _, self.app.showScriptPanel = imgui.menu_item("脚本管理", "", self.app.showScriptPanel, True) + _, self.app.showLUIEditor = imgui.menu_item("LUI编辑器", "", self.app.showLUIEditor, True) + prev_show_web_panel = self.app.showWebPanel + _, self.app.showWebPanel = imgui.menu_item("Web面板", "", self.app.showWebPanel, True) + if prev_show_web_panel and not self.app.showWebPanel: + self._stop_imgui_webview() + elif (not prev_show_web_panel) and self.app.showWebPanel: + self._on_create_web_panel() + + # 工具菜单 + with imgui_ctx.begin_menu("工具") as tools_menu: + if tools_menu: + # 工具切换选项 + if imgui.menu_item("选择工具", "", False, True)[1]: + self.app.tool_manager.setCurrentTool("选择") + if imgui.menu_item("移动工具", "", False, True)[1]: + self.app.tool_manager.setCurrentTool("移动") + if imgui.menu_item("旋转工具", "", False, True)[1]: + self.app.tool_manager.setCurrentTool("旋转") + if imgui.menu_item("缩放工具", "", False, True)[1]: + self.app.tool_manager.setCurrentTool("缩放") + + imgui.separator() + + # 编辑工具 + # if imgui.menu_item("光照编辑", "", False, True)[1]: + # self.app.tool_manager.setCurrentTool("光照编辑") + # if imgui.menu_item("图形编辑", "", False, True)[1]: + # self.app.tool_manager.setCurrentTool("图形编辑") + + imgui.separator() + + # VR子菜单 + with imgui_ctx.begin_menu("VR") as vr_menu: + if vr_menu: + if imgui.menu_item("进入VR模式", "", False, True)[1]: + self.app._toggle_vr_mode() + if imgui.menu_item("退出VR模式", "", False, True)[1]: + self.app._exit_vr_mode() + + imgui.separator() + + if imgui.menu_item("VR状态", "", False, True)[1]: + self.app._show_vr_status() + if imgui.menu_item("VR设置", "", False, True)[1]: + self.app._show_vr_settings() + + imgui.separator() + + # VR调试子菜单 + with imgui_ctx.begin_menu("VR调试") as vr_debug_menu: + if vr_debug_menu: + _, self.app.vr_debug_enabled = imgui.menu_item("启用调试输出", "", self.app.vr_debug_enabled, True) + + if imgui.menu_item("立即显示性能报告", "", False, True)[1]: + self.app._show_vr_performance_report() + + imgui.separator() + + # 输出模式 + with imgui_ctx.begin_menu("输出模式") as output_menu: + if output_menu: + if imgui.menu_item("简短模式", "", not self.app.vr_detailed_mode, True)[1]: + self.app.vr_detailed_mode = False + if imgui.menu_item("详细模式", "", self.app.vr_detailed_mode, True)[1]: + self.app.vr_detailed_mode = True + + imgui.separator() + + _, self.app.vr_performance_monitor = imgui.menu_item("启用性能监控", "", self.app.vr_performance_monitor, True) + + # 窗口菜单 - 已隐藏 + # with imgui_ctx.begin_menu("窗口") as window_menu: + # if window_menu: + # _, self.app.showDemoWindow = imgui.menu_item("ImGui演示", "", self.app.showDemoWindow, True) + # if self.app.testTexture: + # imgui.menu_item("关闭纹理测试", "", False, True) + # else: + # imgui.menu_item("显示纹理测试", "", False, True) + + # 帮助菜单 + with imgui_ctx.begin_menu("帮助") as help_menu: + if help_menu: + imgui.menu_item("关于", "", False, True) + imgui.menu_item("文档", "", False, True) + + # 右侧显示FPS + imgui.set_cursor_pos_x(imgui.get_window_size().x - 140) + imgui.text("%.2f FPS (%.2f ms)" % (imgui.get_io().framerate, 1000.0 / imgui.get_io().framerate)) + + def draw_toolbar(self): + """绘制工具栏""" + # 工具栏可以保持无标题栏,但允许移动和调整大小 + flags = self.app.style_manager.get_window_flags("toolbar") + + with self.app.style_manager.begin_styled_window("工具栏", self.app.showToolbar, flags): + self.app.showToolbar = True # 确保窗口保持打开 + + # 选择工具按钮 + select_active = self.app.tool_manager.isSelectionTool() + if self.app.icons.get('select'): + tint_col = (1.0, 1.0, 0.0, 1.0) if select_active else (1.0, 1.0, 1.0, 1.0) + if self.app.style_manager.image_button(self.app.icons['select'], (24, 24), tint_col=tint_col): + self.app.tool_manager.setCurrentTool("选择") + if imgui.is_item_hovered(): + imgui.set_tooltip("选择工具 (Q)") + imgui.same_line() + else: + if imgui.button("选择##select_tool"): + self.app.tool_manager.setCurrentTool("选择") + if select_active: + draw_list = imgui.get_window_draw_list() + button_min = imgui.get_item_rect_min() + button_max = imgui.get_item_rect_max() + draw_list.add_rect_filled(button_min, button_max, imgui.get_color_u32((0.3, 0.6, 1.0, 0.3))) + imgui.same_line() + + # 移动工具按钮 + move_active = self.app.tool_manager.isMoveTool() + if self.app.icons.get('move'): + # 使用不同颜色表示活动状态 + tint_col = (1.0, 1.0, 0.0, 1.0) if move_active else (1.0, 1.0, 1.0, 1.0) # 活动时显示黄色 + if self.app.style_manager.image_button(self.app.icons['move'], (24, 24), tint_col=tint_col): + self.app.tool_manager.setCurrentTool("移动") + if imgui.is_item_hovered(): + imgui.set_tooltip("移动工具 (W)") + imgui.same_line() + else: + if imgui.button("移动##move_tool"): + self.app.tool_manager.setCurrentTool("移动") + if move_active: + # 为活动按钮添加背景色 + draw_list = imgui.get_window_draw_list() + button_min = imgui.get_item_rect_min() + button_max = imgui.get_item_rect_max() + draw_list.add_rect_filled(button_min, button_max, imgui.get_color_u32((0.3, 0.6, 1.0, 0.3))) + imgui.same_line() + + # 旋转工具按钮 + rotate_active = self.app.tool_manager.isRotateTool() + if self.app.icons.get('rotate'): + tint_col = (1.0, 1.0, 0.0, 1.0) if rotate_active else (1.0, 1.0, 1.0, 1.0) + if self.app.style_manager.image_button(self.app.icons['rotate'], (24, 24), tint_col=tint_col): + self.app.tool_manager.setCurrentTool("旋转") + if imgui.is_item_hovered(): + imgui.set_tooltip("旋转工具 (E)") + imgui.same_line() + else: + if imgui.button("旋转##rotate_tool"): + self.app.tool_manager.setCurrentTool("旋转") + if rotate_active: + draw_list = imgui.get_window_draw_list() + button_min = imgui.get_item_rect_min() + button_max = imgui.get_item_rect_max() + draw_list.add_rect_filled(button_min, button_max, imgui.get_color_u32((0.3, 0.6, 1.0, 0.3))) + imgui.same_line() + + # 缩放工具按钮 + scale_active = self.app.tool_manager.isScaleTool() + if self.app.icons.get('scale'): + tint_col = (1.0, 1.0, 0.0, 1.0) if scale_active else (1.0, 1.0, 1.0, 1.0) + if self.app.style_manager.image_button(self.app.icons['scale'], (24, 24), tint_col=tint_col): + self.app.tool_manager.setCurrentTool("缩放") + if imgui.is_item_hovered(): + imgui.set_tooltip("缩放工具 (R)") + else: + if imgui.button("缩放##scale_tool"): + self.app.tool_manager.setCurrentTool("缩放") + if scale_active: + draw_list = imgui.get_window_draw_list() + button_min = imgui.get_item_rect_min() + button_max = imgui.get_item_rect_max() + draw_list.add_rect_filled(button_min, button_max, imgui.get_color_u32((0.3, 0.6, 1.0, 0.3))) + + imgui.same_line() + imgui.separator() + imgui.same_line() + + # 工具按钮已移除(导入、保存、播放) + diff --git a/ui/panels/runtime_actions.py b/ui/panels/runtime_actions.py index 25b16cac..9a2d8ffa 100644 --- a/ui/panels/runtime_actions.py +++ b/ui/panels/runtime_actions.py @@ -17,10 +17,24 @@ class RuntimeActions: else: setattr(self.app, name, value) + def _get_gui_manager(self): + """统一获取 GUI 管理器,避免散落的 hasattr 判空。""" + return getattr(self.app, "gui_manager", None) + + def _append_gui_element(self, element_wrapper): + """统一维护 gui_elements 列表。""" + gui_manager = self._get_gui_manager() + if not gui_manager: + return False + if not hasattr(gui_manager, "gui_elements") or gui_manager.gui_elements is None: + gui_manager.gui_elements = [] + gui_manager.gui_elements.append(element_wrapper) + return True + def createGUIButton(self, pos=(0, 0, 0), text="按钮", size=0.1): """创建2D GUI按钮""" try: - if hasattr(self, "gui_manager") and self.gui_manager: + if self._get_gui_manager(): return self._create_simple_gui_button(pos, text, size) return None except Exception as e: @@ -55,7 +69,8 @@ class RuntimeActions: button_wrapper.position = pos button_wrapper.size = size - self.gui_manager.gui_elements.append(button_wrapper) + if not self._append_gui_element(button_wrapper): + return None print(f"✓ GUI按钮创建成功: {text}") return button_wrapper @@ -148,7 +163,7 @@ class RuntimeActions: def createGUILabel(self, pos=(0, 0, 0), text="标签", size=0.08): """创建2D GUI标签""" try: - if hasattr(self, "gui_manager") and self.gui_manager: + if self._get_gui_manager(): return self._create_simple_gui_label(pos, text, size) return None except Exception as e: @@ -183,7 +198,8 @@ class RuntimeActions: label_wrapper.position = pos label_wrapper.size = size - self.gui_manager.gui_elements.append(label_wrapper) + if not self._append_gui_element(label_wrapper): + return None print(f"✓ GUI标签创建成功: {text}") return label_wrapper @@ -194,7 +210,7 @@ class RuntimeActions: def createGUIEntry(self, pos=(0, 0, 0), placeholder="输入文本...", size=0.08): """创建2D GUI文本输入框""" try: - if hasattr(self, "gui_manager") and self.gui_manager: + if self._get_gui_manager(): return self._create_simple_gui_entry(pos, placeholder, size) return None except Exception as e: @@ -231,7 +247,8 @@ class RuntimeActions: entry_wrapper.position = pos entry_wrapper.size = size - self.gui_manager.gui_elements.append(entry_wrapper) + if not self._append_gui_element(entry_wrapper): + return None print(f"✓ GUI输入框创建成功: {placeholder}") return entry_wrapper @@ -242,7 +259,7 @@ class RuntimeActions: def createGUIImage(self, pos=(0, 0, 0), image_path=None, size=2): """创建2D GUI图片""" try: - if hasattr(self, "gui_manager") and self.gui_manager: + if self._get_gui_manager(): return self._create_simple_gui_image(pos, image_path, size) return None except Exception as e: @@ -286,7 +303,8 @@ class RuntimeActions: image_wrapper.size = size image_wrapper.image_path = image_path - self.gui_manager.gui_elements.append(image_wrapper) + if not self._append_gui_element(image_wrapper): + return None print(f"✓ GUI图片创建成功: {image_name}") return image_wrapper @@ -297,8 +315,9 @@ class RuntimeActions: def createVideoScreen(self, pos=(0, 0, 0), size=1, video_path=None): """创建视频屏幕""" try: - if hasattr(self, "gui_manager") and self.gui_manager: - return self.gui_manager.createVideoScreen(pos, size, video_path) + gui_manager = self._get_gui_manager() + if gui_manager: + return gui_manager.createVideoScreen(pos, size, video_path) return None except Exception as e: print(f"创建视频屏幕失败: {e}") @@ -307,8 +326,9 @@ class RuntimeActions: def create2DVideoScreen(self, pos=(0, 0, 0), size=0.2, video_path=None): """创建2D视频屏幕""" try: - if hasattr(self, "gui_manager") and self.gui_manager: - return self.gui_manager.createGUI2DVideoScreen(pos, size, video_path) + gui_manager = self._get_gui_manager() + if gui_manager: + return gui_manager.createGUI2DVideoScreen(pos, size, video_path) return None except Exception as e: print(f"创建2D视频屏幕失败: {e}") @@ -317,8 +337,9 @@ class RuntimeActions: def createSphericalVideo(self, pos=(0, 0, 0), radius=5.0, video_path=None): """创建360度视频""" try: - if hasattr(self, "gui_manager") and self.gui_manager: - return self.gui_manager.createSphericalVideo(pos, radius, video_path) + gui_manager = self._get_gui_manager() + if gui_manager: + return gui_manager.createSphericalVideo(pos, radius, video_path) return None except Exception as e: print(f"创建球形视频失败: {e}") @@ -327,8 +348,9 @@ class RuntimeActions: def createVirtualScreen(self, pos=(0, 0, 0), size=(2, 1), text="虚拟屏幕"): """创建虚拟屏幕""" try: - if hasattr(self, "gui_manager") and self.gui_manager: - return self.gui_manager.createGUIVirtualScreen(pos, size, text) + gui_manager = self._get_gui_manager() + if gui_manager: + return gui_manager.createGUIVirtualScreen(pos, size, text) return None except Exception as e: print(f"创建虚拟屏幕失败: {e}")