动画修复

This commit is contained in:
Rowland 2025-12-12 15:15:06 +08:00
parent 7a3fbfeb91
commit 5e255e7c88

View File

@ -6,9 +6,257 @@
- 停靠窗口设置
- 事件连接和信号处理
"""
WEB_BROWSER_PROCESS_CODE = r"""
import sys
import os
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout
from PyQt5.QtCore import Qt, QUrl, QTimer
from PyQt5.QtGui import QPalette, QColor
# 禁用硬件加速
os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--disable-gpu --disable-software-rasterizer --disable-gpu-compositing --in-process-gpu"
os.environ["QT_WEBENGINE_DISABLE_GPU"] = "1"
os.environ["QT_OPENGL"] = "software"
try:
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineSettings
WEB_ENGINE_AVAILABLE = True
except ImportError:
WEB_ENGINE_AVAILABLE = False
print("⚠️ QtWebEngine 不可用")
class EmbeddedWebBrowser(QWidget):
def __init__(self, parent=None, url="https://www.baidu.com", parent_window_id=0):
super().__init__(parent)
self.setWindowTitle("Web 浏览器")
self.parent_window_id = parent_window_id
self.url = url
# 设置窗口标志 - 如果要嵌入,使用子窗口样式
if parent_window_id > 0:
# 使用 FramelessWindowHint 去除边框,便于嵌入
self.setWindowFlags(Qt.FramelessWindowHint)
else:
self.setWindowFlags(Qt.Window)
self.resize(800, 600)
# 设置背景色
palette = self.palette()
palette.setColor(QPalette.Window, QColor(25, 25, 27))
self.setPalette(palette)
self.setAutoFillBackground(True)
# 创建布局
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
if WEB_ENGINE_AVAILABLE:
# 创建 WebEngine 视图
self.web_view = QWebEngineView()
# 设置样式
self.web_view.setStyleSheet("background-color: #19191b;")
# 禁用硬件加速
settings = self.web_view.settings()
settings.setAttribute(QWebEngineSettings.Accelerated2dCanvasEnabled, False)
settings.setAttribute(QWebEngineSettings.WebGLEnabled, False)
settings.setAttribute(QWebEngineSettings.PluginsEnabled, False)
settings.setAttribute(QWebEngineSettings.JavascriptEnabled, True)
settings.setAttribute(QWebEngineSettings.AutoLoadImages, True)
settings.setAttribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls, True)
# 连接加载信号
self.web_view.loadStarted.connect(self.on_load_started)
self.web_view.loadProgress.connect(self.on_load_progress)
self.web_view.loadFinished.connect(self.on_load_finished)
layout.addWidget(self.web_view)
print("[WebEngine] WebEngine 视图已创建")
# 延迟加载网页,确保窗口完全初始化
QTimer.singleShot(500, lambda: self.load_url(url))
else:
from PyQt5.QtWidgets import QLabel
label = QLabel("QtWebEngine 不可用\n请安装: pip install PyQtWebEngine")
label.setAlignment(Qt.AlignCenter)
label.setStyleSheet("color: white; font-size: 14px;")
layout.addWidget(label)
def on_load_started(self):
print("[WebEngine] 开始加载网页...")
def on_load_progress(self, progress):
if progress % 20 == 0: # 每20%打印一次
print(f"[WebEngine] 加载进度: {progress}%")
def on_load_finished(self, success):
if success:
print("[WebEngine] 网页加载成功")
else:
print("[WebEngine] 网页加载失败")
def load_url(self, url):
if WEB_ENGINE_AVAILABLE and hasattr(self, 'web_view'):
print(f"[WebEngine] 准备加载 URL: {url}")
qurl = QUrl(url)
print(f"[WebEngine] QUrl 有效性: {qurl.isValid()}")
print(f"[WebEngine] QUrl 内容: {qurl.toString()}")
self.web_view.load(qurl)
print(f"[WebEngine] load() 方法已调用")
else:
print(f"[WebEngine] 无法加载 URL - WEB_ENGINE_AVAILABLE={WEB_ENGINE_AVAILABLE}, has_web_view={hasattr(self, 'web_view')}")
def get_native_window_id(self):
return int(self.winId())
def embed_into_parent(self):
if self.parent_window_id > 0:
try:
print(f"[WebEngine] 开始嵌入窗口到父窗口 ID: {self.parent_window_id}")
# Windows 平台使用 win32 API 嵌入窗口
if sys.platform == 'win32':
import ctypes
from ctypes import wintypes
# 获取窗口句柄
hwnd = int(self.winId())
parent_hwnd = self.parent_window_id
print(f"[WebEngine] 子窗口句柄: {hwnd}")
print(f"[WebEngine] 父窗口句柄: {parent_hwnd}")
user32 = ctypes.windll.user32
# 先获取父窗口大小
rect = wintypes.RECT()
user32.GetClientRect(parent_hwnd, ctypes.byref(rect))
width = rect.right - rect.left
height = rect.bottom - rect.top
print(f"[WebEngine] 父窗口大小: {width}x{height}")
# 设置父窗口
result = user32.SetParent(hwnd, parent_hwnd)
print(f"[WebEngine] SetParent 结果: {result}")
# 设置窗口样式为子窗口
GWL_STYLE = -16
WS_CHILD = 0x40000000
WS_VISIBLE = 0x10000000
WS_CLIPCHILDREN = 0x02000000
WS_CLIPSIBLINGS = 0x04000000
old_style = user32.GetWindowLongW(hwnd, GWL_STYLE)
print(f"[WebEngine] 旧窗口样式: {hex(old_style)}")
# 设置为子窗口样式,移除边框和标题栏
new_style = (WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS)
user32.SetWindowLongW(hwnd, GWL_STYLE, new_style)
print(f"[WebEngine] 新窗口样式: {hex(new_style)}")
# 调整窗口位置和大小,填充整个父窗口
SWP_FRAMECHANGED = 0x0020
SWP_SHOWWINDOW = 0x0040
SWP_NOZORDER = 0x0004
result = user32.SetWindowPos(
hwnd,
0, # HWND_TOP
0, 0, # x, y
width, height, # width, height
SWP_FRAMECHANGED | SWP_SHOWWINDOW | SWP_NOZORDER
)
print(f"[WebEngine] SetWindowPos 结果: {result}")
# 强制刷新窗口
user32.UpdateWindow(hwnd)
user32.InvalidateRect(hwnd, None, True)
print(f"[WebEngine] 已成功嵌入到父窗口 (Windows)")
return True
else:
# 其他平台使用 Qt 的方式
from PyQt5.QtGui import QWindow
parent_window = QWindow.fromWinId(self.parent_window_id)
if parent_window:
self.windowHandle().setParent(parent_window)
print(f"[WebEngine] 已嵌入到父窗口 (Qt): {self.parent_window_id}")
return True
except Exception as e:
print(f"[WebEngine] 嵌入窗口失败: {e}")
import traceback
traceback.print_exc()
return False
def main():
# 设置标准输出编码为 UTF-8避免 Windows GBK 编码问题
if sys.platform == 'win32':
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
print("=" * 60)
print("[WebEngine] 启动独立 WebEngine 进程")
print("=" * 60)
# 创建应用
app = QApplication(sys.argv)
# 从命令行参数获取 URL 和窗口 ID
url = sys.argv[1] if len(sys.argv) > 1 else "https://www.baidu.com"
parent_window_id = int(sys.argv[2]) if len(sys.argv) > 2 else 0
print(f"[WebEngine] 参数:")
print(f" - URL: {url}")
print(f" - 父窗口 ID: {parent_window_id}")
# 创建浏览器窗口
browser = EmbeddedWebBrowser(url=url, parent_window_id=parent_window_id)
# 显示窗口
browser.show()
# 输出窗口 ID 供父进程使用
window_id = browser.get_native_window_id()
print(f"WINDOW_ID:{window_id}")
sys.stdout.flush()
# 等待网页开始加载后再嵌入窗口
if parent_window_id > 0:
# 延迟2秒确保网页已经开始加载和渲染
QTimer.singleShot(10, browser.embed_into_parent)
print("[WebEngine] 进程已就绪")
print("=" * 60)
sys.exit(app.exec_())
if __name__ == "__main__":
main()
"""
import os
import sys
# ==================== 禁用 QtWebEngine 硬件加速 ====================
# 必须在导入 QtWebEngine 之前设置环境变量
os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--disable-gpu --disable-software-rasterizer --disable-gpu-compositing --in-process-gpu"
os.environ["QT_WEBENGINE_DISABLE_GPU"] = "1"
os.environ["QT_OPENGL"] = "software"
print("✓ QtWebEngine 硬件加速已禁用,使用软件渲染模式")
# ==================== 结束配置 ====================
from PyQt5.QtGui import QKeySequence, QIcon, QPalette, QColor
# from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QMenu, QAction,
@ -27,7 +275,7 @@ from ui.widgets import (CustomMeta3DWidget, CustomFileView, CustomTreeWidget,
UniversalMessageDialog)
from ui.icon_manager import get_icon_manager, get_icon
import yaml
# import yaml
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QListWidget, QPushButton, QDialogButtonBox, QCheckBox, QLabel, QHBoxLayout
from PyQt5.QtCore import Qt
@ -1176,19 +1424,22 @@ class MainWindow(QMainWindow):
# 帮助菜单
self.helpMenu = menubar.addMenu('帮助')
self.aboutAction = self.helpMenu.addAction('关于')
self.aboutAction.triggered.connect(self.showAboutDialog)
try:
import json
with open('new/project.json','r',encoding='utf-8')as f:
project_data = json.load(f)
version = project_data.get('version','1.0')
self.aboutAction.setText(f'关于 v{version}')
except Exception as e:
print(f"无法读取项目信息: {e}")
self.aboutAction.setText('关于 v1.0')
tool_menu = self.menuBar().addMenu("配置")
plugin_config_action = tool_menu.addAction("渲染管线插件配置")
plugin_config_action.triggered.connect(self.open_plugin_config_dialog)
def showAboutDialog(self):
msgBox = QMessageBox()
msgBox.setWindowTitle("关于")
msgBox.setText(f'元泰引擎系统\nMetaCore\n版本v1.0')
msgBox.setIcon(QMessageBox.NoIcon)
msgBox.exec_()
def open_plugin_config_dialog(self):
"""打开插件配置对话框"""
try:
@ -1512,7 +1763,12 @@ class MainWindow(QMainWindow):
self.createSamplePanelAction.triggered.connect(self.world.info_panel_manager.onCreateSampleInfoPanel)
self.create3DSamplePanelAction.triggered.connect(self.onCreate3DSampleInfoPanel)
self.webBrowserAction.triggered.connect(self.openWebBrowser)
#self.webBrowserAction.triggered.connect(self.openWebBrowser_win)
if sys.platform == 'win32':
self.webBrowserAction.triggered.connect(self.openWebBrowser_win)
else:
self.webBrowserAction.triggered.connect(self.openWebBrowser)
def getCreateMenuActions(self):
"""获取所有创建菜单动作的字典 - 供右键菜单使用"""
@ -3920,11 +4176,181 @@ class MainWindow(QMainWindow):
self.world.setCurrentTool(None)
print("工具栏: 取消选择工具")
def openWebBrowser_win(self):
"""打开Web浏览器面板使用独立进程避免与Panda3D冲突"""
try:
from PyQt5.QtWidgets import QDockWidget, QWidget, QVBoxLayout
from PyQt5.QtCore import QProcess, Qt
import subprocess
import sys
main_window = self.world.main_window
# 尝试获取主窗口引用
if main_window is None:
print("🔍 尝试获取主窗口引用...")
if hasattr(self.world, 'interface_manager'):
print(f" - interface_manager 存在: {self.world.interface_manager}")
if hasattr(self.world.interface_manager, 'main_window'):
main_window = self.world.interface_manager.main_window
print(f" - interface_manager.main_window: {main_window}")
if main_window is None and hasattr(self.world, 'main_window'):
main_window = self.world.main_window
print(f" - world.main_window: {main_window}")
if main_window is None and self.world.treeWidget:
try:
main_window = self.world.treeWidget.window()
print(f" - 从 treeWidget 获取窗口: {main_window}")
except:
pass
if main_window is None:
print("✗ 无法获取主窗口引用")
UniversalMessageDialog.show_warning(
self, "错误", "无法获取主窗口引用", False, "确定"
)
return None
else:
print(f"✅ 使用传入的主窗口引用: {main_window}")
# 检查主窗口是否有效
if not hasattr(main_window, 'addDockWidget'):
print(f"✗ 主窗口引用无效,缺少 addDockWidget 方法")
return None
# 检查是否已经存在浏览器视图
for element in self.world.gui_elements:
if hasattr(element, 'objectName') and element.objectName() == "WebBrowserView":
print("⚠ 浏览器视图已经存在")
element.show()
element.raise_()
return element
print(f"🔧 创建浏览器停靠窗口(独立进程模式),父窗口: {main_window}")
# 创建停靠窗口
browser_dock = QDockWidget("信息面板", main_window)
browser_dock.setObjectName("WebBrowserView")
# 创建容器 Widget
container_widget = QWidget()
container_layout = QVBoxLayout(container_widget)
container_layout.setContentsMargins(0, 0, 0, 0)
# 创建一个占位 Widget用于嵌入外部进程窗口
embed_widget = QWidget()
embed_widget.setMinimumSize(400, 300)
container_layout.addWidget(embed_widget)
browser_dock.setWidget(container_widget)
# 添加到主窗口
print("📍 将浏览器视图添加到主窗口")
main_window.addDockWidget(Qt.RightDockWidgetArea, browser_dock)
# 获取嵌入窗口的 ID
embed_window_id = int(embed_widget.winId())
print(f"🔑 嵌入窗口 ID: {embed_window_id}")
# 启动独立进程
print("🚀 启动独立 WebEngine 进程...")
#web_process_script = os.path.join(os.path.dirname(__file__), "web_browser_process.py")
import tempfile
temp_dir = tempfile.gettempdir()
web_process_script = os.path.join(temp_dir, "web_browser_process_temp.py")
with open(web_process_script, "w", encoding="utf-8") as f:
f.write(WEB_BROWSER_PROCESS_CODE)
# -----------------------------------------------------
# 创建 QProcess注意必须在 start 之前)
# -----------------------------------------------------
# -----------------------------------------------------
# 创建 QProcess注意必须在 start 之前)
# -----------------------------------------------------
process = QProcess(browser_dock)
browser_dock.web_process = process # 保存进程引用
# -----------------------------------------------------
# 输出信号
# -----------------------------------------------------
def on_process_output():
output = bytes(process.readAllStandardOutput()).decode('utf-8', errors='ignore')
if output.strip():
print(f"[WebProcess] {output.strip()}")
def on_process_error():
error = bytes(process.readAllStandardError()).decode('utf-8', errors='ignore')
if error.strip():
print(f"[WebProcess Error] {error.strip()}")
def on_process_finished(exit_code, exit_status):
print(f"⚠️ WebEngine 进程退出: {exit_code} {exit_status}")
process.readyReadStandardOutput.connect(on_process_output)
process.readyReadStandardError.connect(on_process_error)
process.finished.connect(on_process_finished)
# -----------------------------------------------------
# 写入临时脚本
# -----------------------------------------------------
import tempfile
temp_dir = tempfile.gettempdir()
web_process_script = os.path.join(temp_dir, "web_browser_process_temp.py")
with open(web_process_script, "w", encoding="utf-8") as f:
f.write(WEB_BROWSER_PROCESS_CODE)
# -----------------------------------------------------
# 启动子进程(这里顺序不能错)
# -----------------------------------------------------
python_exe = sys.executable
url = "https://baidu.com" # 更可控的测试站点
print(f"🚀 启动: {python_exe} {web_process_script} {url} {embed_window_id}")
process.start(python_exe, [web_process_script, url, str(embed_window_id)])
if not process.waitForStarted(3000):
print("✗ 启动 WebEngine 失败")
return None
print("✓ WebEngine 进程已启动")
# 添加到GUI元素列表以便管理
self.world.gui_elements.append(browser_dock)
# 添加清理函数
def cleanup_process():
if hasattr(browser_dock, 'web_process') and browser_dock.web_process:
print("🧹 清理 WebEngine 进程...")
browser_dock.web_process.terminate()
if not browser_dock.web_process.waitForFinished(2000):
browser_dock.web_process.kill()
browser_dock.destroyed.connect(cleanup_process)
print("✓ 网页浏览器视图已创建并集成到项目中(独立进程模式)")
return browser_dock
except Exception as e:
print(f"✗ 创建浏览器视图失败: {str(e)}")
import traceback
traceback.print_exc()
UniversalMessageDialog.show_error(
self, "错误",
f"创建浏览器视图失败:\n{str(e)}",
False, "确定"
)
return None
def openWebBrowser(self):
if not WEB_ENGINE_AVAILABLE:
return None
try:
from PyQt5.QtWebEngineWidgets import QWebEngineView
# from PyQt5.QtWebEngineWidgets import QWebEngineView
from PyQt5.QtWidgets import QDockWidget
from PyQt5.QtCore import QUrl
import os
@ -3983,8 +4409,8 @@ class MainWindow(QMainWindow):
self.web_view = QWebEngineView()
# 加载百度网页
#print("🌐 加载百度网页: https://www.baidu.com")
#self.web_view.load(QUrl("https://www.bootstrapmb.com/item/15762/preview"))
# print("🌐 加载百度网页: https://www.baidu.com")
# self.web_view.load(QUrl("https://www.bootstrapmb.com/item/15762/preview"))
self.web_view.load(QUrl("https://www.baidu.com"))
# 设置内容
@ -4007,7 +4433,6 @@ class MainWindow(QMainWindow):
return None
def getSampleWeatherData(self):
"""获取示例天气数据"""
try:
@ -6281,7 +6706,17 @@ def setup_main_window(world,path = None):
QCoreApplication.setAttribute(Qt.AA_ShareOpenGLContexts)
if sys.platform == 'win32':
QCoreApplication.setAttribute(Qt.AA_UseDesktopOpenGL)
# 添加 Chromium 参数禁用硬件加速,避免与 Panda3D 的 OpenGL 冲突
sys.argv.extend([
'--disable-gpu',
'--disable-software-rasterizer',
'--disable-gpu-compositing',
'--disable-accelerated-2d-canvas',
'--num-raster-threads=1'
])
app = QApplication(sys.argv)
print("✓ QApplication 已创建QtWebEngine 硬件加速已禁用")
main_window = MainWindow(world)
main_window.show()