拖动导入模型帧率异常修复,添加web视图

This commit is contained in:
Hector 2026-02-26 17:57:00 +08:00
parent fe373c0e5d
commit 79b9aa44dc
6 changed files with 333 additions and 104 deletions

View File

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

View File

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

13
main.py
View File

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

View File

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

View File

@ -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创建方法 ====================

View File

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