继续优化:拆分editor_panels并收敛旧接口

This commit is contained in:
ayuan9957 2026-02-28 16:24:39 +08:00
parent e9784b3400
commit 3a13025009
13 changed files with 5170 additions and 2518 deletions

62
IMGUI_MODULE_ANALYSIS.md Normal file
View File

@ -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)

2465
PROJECT_FULL_CATALOG.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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):
"""处理鼠标中键按下事件"""

View File

@ -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:

12
main.py
View File

@ -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()

View File

@ -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)

File diff suppressed because it is too large Load Diff

View File

@ -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视图...")

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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()
# 工具按钮已移除(导入、保存、播放)

View File

@ -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}")