diff --git a/core/model_drag_drop.py b/core/model_drag_drop.py index 801e0754..9f7ea3b0 100644 --- a/core/model_drag_drop.py +++ b/core/model_drag_drop.py @@ -313,10 +313,15 @@ class ModelDragDropService: if not path.exists() or not self._is_supported_model(path): continue try: - model_node = self.app._import_model_for_runtime(str(path), prefer_scene_manager=True) + model_node = self.app._import_model_with_menu_logic( + str(path), + select_model=False, + set_origin=True, + show_info_message=False, + show_success_message=False, + ) if not model_node: continue - self._postprocess_imported_model(model_node) imported_models.append(model_node) except Exception as e: print(f"[外部拖拽] 导入模型失败 {path}: {e}") @@ -377,7 +382,13 @@ class ModelDragDropService: if not self._is_supported_model(path): continue try: - model_node = self.app._import_model_for_runtime(str(path), prefer_scene_manager=True) + model_node = self.app._import_model_with_menu_logic( + str(path), + select_model=False, + set_origin=True, + show_info_message=False, + show_success_message=False, + ) if not model_node: continue @@ -387,7 +398,6 @@ class ModelDragDropService: except Exception: model_node.reparentTo(target_parent) - self._postprocess_imported_model(model_node) imported_models.append(model_node) except Exception as e: print(f"[拖拽导入] 导入失败 {path}: {e}") @@ -446,16 +456,10 @@ class ModelDragDropService: self.app.add_error_message(f"不支持的文件格式: {path.suffix.lower()}") return False - model_node = self.app._import_model_for_runtime(str(path), prefer_scene_manager=True) + model_node = self.app._import_model_with_menu_logic(str(path)) if not model_node: - self.app.add_error_message(f"导入模型失败: {path}") return False - self._postprocess_imported_model(model_node, set_origin=True) - if getattr(self.app, "selection", None): - self.app.selection.updateSelection(model_node) - - self.app.add_success_message(f"成功导入模型: {path.name}") return True except Exception as e: self.app.add_error_message(f"导入模型时发生错误: {e}") diff --git a/imgui.ini b/imgui.ini index 64ff275b..ada48696 100644 --- a/imgui.ini +++ b/imgui.ini @@ -24,26 +24,26 @@ Size=832,45 Collapsed=0 [Window][工具栏] -Pos=327,20 -Size=1862,32 +Pos=241,20 +Size=908,74 Collapsed=0 DockId=0x0000000D,0 [Window][场景树] Pos=0,20 -Size=325,854 +Size=239,468 Collapsed=0 DockId=0x00000007,0 [Window][属性面板] -Pos=2191,20 -Size=369,1331 +Pos=1151,20 +Size=229,730 Collapsed=0 DockId=0x00000003,0 [Window][控制台] -Pos=0,876 -Size=325,475 +Pos=0,490 +Size=239,260 Collapsed=0 DockId=0x00000008,0 @@ -60,7 +60,7 @@ Collapsed=0 [Window][WindowOverViewport_11111111] Pos=0,20 -Size=2560,1331 +Size=1380,730 Collapsed=0 [Window][测试窗口1] @@ -99,8 +99,8 @@ Size=600,500 Collapsed=0 [Window][资源管理器] -Pos=327,1013 -Size=1862,338 +Pos=241,464 +Size=908,286 Collapsed=0 DockId=0x00000006,0 @@ -200,18 +200,23 @@ Pos=660,304 Size=600,400 Collapsed=0 +[Window][Web面板] +Pos=373,98 +Size=942,580 +Collapsed=0 + [Docking][Data] -DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=2560,1331 Split=X - DockNode ID=0x00000001 Parent=0x08BD597D SizeRef=1549,989 Split=X - DockNode ID=0x00000009 Parent=0x00000001 SizeRef=325,989 Split=Y Selected=0xE0015051 +DockSpace ID=0x08BD597D Window=0x1BBC0F80 Pos=0,20 Size=1380,730 Split=X + DockNode ID=0x00000001 Parent=0x08BD597D SizeRef=1689,989 Split=X + DockNode ID=0x00000009 Parent=0x00000001 SizeRef=239,989 Split=Y Selected=0xE0015051 DockNode ID=0x00000007 Parent=0x00000009 SizeRef=271,634 Selected=0xE0015051 DockNode ID=0x00000008 Parent=0x00000009 SizeRef=271,353 Selected=0x5428E753 - DockNode ID=0x0000000A Parent=0x00000001 SizeRef=1862,989 Split=Y - DockNode ID=0x0000000D Parent=0x0000000A SizeRef=1318,32 HiddenTabBar=1 Selected=0x43A39006 - DockNode ID=0x0000000E Parent=0x0000000A SizeRef=1318,955 Split=Y - DockNode ID=0x00000005 Parent=0x0000000E SizeRef=1341,957 CentralNode=1 - DockNode ID=0x00000006 Parent=0x0000000E SizeRef=1341,338 Selected=0x3A2E05C3 - DockNode ID=0x00000002 Parent=0x08BD597D SizeRef=369,989 Split=Y Selected=0x3188AB8D + DockNode ID=0x0000000A Parent=0x00000001 SizeRef=1448,989 Split=Y + DockNode ID=0x0000000D Parent=0x0000000A SizeRef=1318,74 HiddenTabBar=1 Selected=0x43A39006 + DockNode ID=0x0000000E Parent=0x0000000A SizeRef=1318,913 Split=Y + DockNode ID=0x00000005 Parent=0x0000000E SizeRef=1341,625 CentralNode=1 + DockNode ID=0x00000006 Parent=0x0000000E SizeRef=1341,286 Selected=0x3A2E05C3 + DockNode ID=0x00000002 Parent=0x08BD597D SizeRef=229,989 Split=Y Selected=0x3188AB8D DockNode ID=0x00000003 Parent=0x00000002 SizeRef=351,390 Selected=0x5DB6FF37 DockNode ID=0x00000004 Parent=0x00000002 SizeRef=351,597 Selected=0x1EB923B7 diff --git a/main.py b/main.py index 9c3779b4..3c672d82 100644 --- a/main.py +++ b/main.py @@ -262,6 +262,8 @@ class MyWorld(CoreWorld): self.showScriptPanel = not self.use_ssbo_mouse_picking self.showToolbar = True self.showResourceManager = True + self.showWebPanel = False + self.webPanelUrl = "https://www.baidu.com" # 脚本系统状态变量 self.hotReloadEnabled = True @@ -770,6 +772,10 @@ class MyWorld(CoreWorld): if self.showPropertyPanel: self._draw_property_panel() + # Web面板 + if self.showWebPanel: + self._draw_web_panel() + # 脚本面板 if self.showScriptPanel: self._draw_script_panel() @@ -876,6 +882,9 @@ class MyWorld(CoreWorld): def _draw_property_panel(self, *args, **kwargs): return self.editor_panels._draw_property_panel(*args, **kwargs) + def _draw_web_panel(self, *args, **kwargs): + return self.editor_panels._draw_web_panel(*args, **kwargs) + def _draw_node_properties(self, *args, **kwargs): return self.editor_panels._draw_node_properties(*args, **kwargs) @@ -1306,6 +1315,10 @@ class MyWorld(CoreWorld): def _import_model(self, *args, **kwargs): return self.app_actions._import_model(*args, **kwargs) + + def _import_model_with_menu_logic(self, *args, **kwargs): + return self.app_actions._import_model_with_menu_logic(*args, **kwargs) + def _draw_new_project_dialog(self, *args, **kwargs): return self.dialog_panels._draw_new_project_dialog(*args, **kwargs) diff --git a/ui/panels/app_actions.py b/ui/panels/app_actions.py index f816a1f0..e01f1971 100644 --- a/ui/panels/app_actions.py +++ b/ui/panels/app_actions.py @@ -1094,6 +1094,82 @@ class AppActions: return self.scene_manager.importModel(file_path) return None + def _import_model_with_menu_logic( + self, + file_path, + select_model=True, + set_origin=True, + show_info_message=True, + show_success_message=True, + ): + """统一的单文件导入入口,保持与菜单导入一致的处理流程。""" + try: + if not file_path: + self.add_error_message("请选择要导入的文件") + return None + + normalized_path = os.fspath(file_path) + if not os.path.exists(normalized_path): + self.add_error_message(f"文件不存在: {normalized_path}") + return None + + file_ext = os.path.splitext(normalized_path)[1].lower() + if file_ext not in self.supported_formats: + self.add_error_message(f"不支持的文件格式: {file_ext}") + return None + + if not hasattr(self, 'scene_manager') or not self.scene_manager: + self.add_error_message("场景管理器未初始化") + return None + + file_name = os.path.basename(normalized_path) + if show_info_message: + self.add_info_message(f"正在导入模型: {file_name}") + + model_node = self._import_model_for_runtime(normalized_path) + if not model_node: + self.add_error_message("模型导入失败") + return None + + if hasattr(self.scene_manager, 'processMaterials'): + self.scene_manager.processMaterials(model_node) + if show_info_message: + self.add_info_message("已应用默认材质") + + try: + model_node.clearMaterial() + model_node.clearTexture() + + if hasattr(self.scene_manager, 'processMaterials'): + self.scene_manager.processMaterials(model_node) + + try: + color = model_node.getColor() + if color and len(color) >= 4 and color == (1, 1, 1, 1): + model_node.setColor(0.8, 0.8, 0.8, 1.0) + elif not color: + model_node.setColor(0.8, 0.8, 0.8, 1.0) + except Exception: + model_node.setColor(0.8, 0.8, 0.8, 1.0) + except Exception as e: + self.add_warning_message(f"材质处理警告: {e}") + + if set_origin: + model_node.setPos(0, 0, 0) + + if hasattr(self.scene_manager, 'models'): + self.scene_manager.models.append(model_node) + + if select_model and hasattr(self, 'selection') and self.selection: + self.selection.updateSelection(model_node) + + if show_success_message: + self.add_success_message(f"模型导入成功: {file_name}") + return model_node + except Exception as e: + self.add_error_message(f"导入模型失败: {e}") + return None + def _on_import_model(self): """处理导入模型菜单项""" self.add_info_message("打开导入模型对话框") @@ -1102,77 +1178,6 @@ class AppActions: def _import_model(self): """导入模型的具体实现""" - try: - if not self.import_file_path: - self.add_error_message("请选择要导入的文件") - return - - if not os.path.exists(self.import_file_path): - self.add_error_message(f"文件不存在: {self.import_file_path}") - return - - # 检查文件格式 - file_ext = os.path.splitext(self.import_file_path)[1].lower() - if file_ext not in self.supported_formats: - self.add_error_message(f"不支持的文件格式: {file_ext}") - return - - # 调用场景管理器导入模型 - if hasattr(self, 'scene_manager') and self.scene_manager: - self.add_info_message(f"正在导入模型: {os.path.basename(self.import_file_path)}") - - # 导入模型 - model_node = self._import_model_for_runtime(self.import_file_path) - - if model_node: - # 添加材质处理确保颜色正常 - if hasattr(self.scene_manager, 'processMaterials'): - self.scene_manager.processMaterials(model_node) - self.add_info_message("已应用默认材质") - - # 额外的材质处理,确保颜色正确显示 - try: - # 强制刷新模型显示 - model_node.clearMaterial() - model_node.clearTexture() - - # 重新应用材质 - if hasattr(self.scene_manager, 'processMaterials'): - self.scene_manager.processMaterials(model_node) - - # 设置默认的基础颜色(如果模型没有颜色) - try: - color = model_node.getColor() - if color and len(color) >= 4 and color == (1, 1, 1, 1): # 默认白色 - model_node.setColor(0.8, 0.8, 0.8, 1.0) # 设置为中性灰 - elif not color: # 如果没有颜色 - model_node.setColor(0.8, 0.8, 0.8, 1.0) # 设置为中性灰 - except: - # 如果getColor失败,直接设置默认颜色 - model_node.setColor(0.8, 0.8, 0.8, 1.0) # 设置为中性灰 - - except Exception as e: - self.add_warning_message(f"材质处理警告: {e}") - - # 设置模型位置 - model_node.setPos(0, 0, 0) - - # 添加到场景管理器的模型列表 - if hasattr(self.scene_manager, 'models'): - self.scene_manager.models.append(model_node) - - # 选中新导入的模型 - if hasattr(self, 'selection') and self.selection: - self.selection.updateSelection(model_node) - - self.add_success_message(f"模型导入成功: {os.path.basename(self.import_file_path)}") - else: - self.add_error_message("模型导入失败") - else: - self.add_error_message("场景管理器未初始化") - - except Exception as e: - self.add_error_message(f"导入模型失败: {e}") - - # 清空导入路径 + model_node = self._import_model_with_menu_logic(self.import_file_path) self.import_file_path = "" + return model_node diff --git a/ui/panels/create_actions.py b/ui/panels/create_actions.py index b1945a3f..909f4af4 100644 --- a/ui/panels/create_actions.py +++ b/ui/panels/create_actions.py @@ -204,12 +204,14 @@ class CreateActions: def _on_create_web_panel(self): """创建Web面板""" try: - result = self.createWebPanel() + result = self.editor_panels._on_create_web_panel() if result: self.add_success_message("Web面板创建成功") else: self.add_error_message("Web面板创建失败") + return result except Exception as e: self.add_error_message(f"创建Web面板失败: {str(e)}") + return None # ==================== 3D对象和GUI创建方法 ==================== diff --git a/ui/panels/editor_panels.py b/ui/panels/editor_panels.py index abc7e2cf..042d63f3 100644 --- a/ui/panels/editor_panels.py +++ b/ui/panels/editor_panels.py @@ -1,6 +1,7 @@ from imgui_bundle import imgui, imgui_ctx import os from pathlib import Path +from panda3d.core import PNMImage, StringStream, Texture class EditorPanels: @@ -19,6 +20,117 @@ class EditorPanels: 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.example.com" + if not hasattr(self.app, "_imgui_webview"): + self.app._imgui_webview = None + if not hasattr(self.app, "_imgui_webview_texture"): + self.app._imgui_webview_texture = None + if not hasattr(self.app, "_imgui_webview_tex_id"): + self.app._imgui_webview_tex_id = 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 + self.app._imgui_webview_texture = 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 + + pnm = PNMImage() + if not pnm.read(StringStream(jpeg_bytes), "imgui_webview.png"): + webview.tex_dirty = False + return + # p3dimgui 纹理坐标系与网页截图存在Y轴方向差异,先在像素层修正 + pnm.flip(False, True, False) + + # 某些后端在“已注册纹理原位更新”时存在稳定性问题。 + # 改为每次创建新纹理并替换旧纹理ID,避免原位更新导致崩溃。 + texture = Texture("imgui_web_panel_texture") + texture.load(pnm) + new_tex_id = self.app.imgui.loadTexture(texture) + + 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 + + self.app._imgui_webview_texture = texture + self.app._imgui_webview_tex_id = new_tex_id + + 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: @@ -159,6 +271,12 @@ class EditorPanels: _, 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: @@ -926,6 +1044,86 @@ class EditorPanels: 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): """绘制属性面板""" @@ -2216,3 +2414,5 @@ class EditorPanels: except Exception as e: print(f"绘制着色模型面板失败: {e}") + +