1454 lines
59 KiB
Python
1454 lines
59 KiB
Python
"""
|
||
主窗口设置模块
|
||
|
||
负责主窗口的界面构建和事件绑定:
|
||
- 菜单栏、工具栏创建
|
||
- 停靠窗口设置
|
||
- 事件连接和信号处理
|
||
"""
|
||
|
||
import sys
|
||
|
||
from PyQt5.QtGui import QKeySequence, QIcon
|
||
from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QMenu, QAction,
|
||
QDockWidget, QTreeWidget, QListWidget, QWidget, QVBoxLayout, QTreeWidgetItem,
|
||
QLabel, QLineEdit, QFormLayout, QDoubleSpinBox, QScrollArea,
|
||
QFileSystemModel, QButtonGroup, QToolButton, QPushButton, QHBoxLayout,
|
||
QComboBox, QGroupBox, QInputDialog, QFileDialog, QMessageBox, QDesktopWidget, QDialog,
|
||
QSpinBox, QFrame)
|
||
from PyQt5.QtCore import Qt, QDir, QTimer, QSize, QPoint
|
||
from ui.widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget,CustomAssetsTreeWidget, CustomConsoleDockWidget
|
||
|
||
class MainWindow(QMainWindow):
|
||
"""主窗口类"""
|
||
|
||
def __init__(self, world):
|
||
super().__init__()
|
||
self.world = world
|
||
self.world.main_window = self # 关键:让world对象能访问主窗口
|
||
|
||
self.setupCenterWidget() # 创建中间部分Panda3D
|
||
self.setupMenus() # 创建菜单栏
|
||
self.setupDockWindows()
|
||
# self.setupToolbar()
|
||
self.connectEvents()
|
||
|
||
# 移动窗口到屏幕中央
|
||
self.move_center()
|
||
|
||
# 创建定时器来更新脚本管理面板状态
|
||
self.updateTimer = QTimer()
|
||
self.updateTimer.timeout.connect(self.updateScriptPanel)
|
||
self.updateTimer.start(500) # 每500毫秒更新一次
|
||
|
||
def setupCenterWidget(self):
|
||
"""设置窗口基本属性"""
|
||
self.setWindowTitle("引擎编辑器")
|
||
|
||
# 使用自定义的 Panda3D 部件作为中央部件
|
||
self.pandaWidget = CustomPanda3DWidget(self.world)
|
||
self.setCentralWidget(self.pandaWidget)
|
||
|
||
# 创建内嵌工具栏
|
||
self.setupEmbeddedToolbar()
|
||
|
||
def move_center(self):
|
||
"""设置窗口居中显示"""
|
||
self.setGeometry(50, 50, 1920, 1080)
|
||
|
||
screen = QDesktopWidget().screenGeometry()
|
||
self.move(
|
||
int(screen.width() / 2 - self.width() / 2),
|
||
int(screen.height() / 2 - self.height() / 2),
|
||
)
|
||
|
||
def setupEmbeddedToolbar(self):
|
||
"""创建Unity风格的内嵌工具栏"""
|
||
# 创建工具栏容器
|
||
self.embeddedToolbar = QFrame(self.pandaWidget)
|
||
self.embeddedToolbar.setObjectName("UnityToolbar")
|
||
self.embeddedToolbar.setStyleSheet("""
|
||
QFrame#UnityToolbar {
|
||
background-color: rgba(240, 240, 240, 180);
|
||
border: 1px solid rgba(200, 200, 200, 200);
|
||
border-radius: 4px;
|
||
}
|
||
QToolButton {
|
||
background-color: rgba(250, 250, 250, 150);
|
||
border: none;
|
||
color: #333333;
|
||
padding: 5px;
|
||
border-radius: 3px;
|
||
}
|
||
QToolButton:hover {
|
||
background-color: rgba(220, 220, 220, 200);
|
||
}
|
||
QToolButton:checked {
|
||
background-color: rgba(100, 150, 220, 180);
|
||
color: white;
|
||
}
|
||
QToolButton:pressed {
|
||
background-color: rgba(80, 130, 200, 200);
|
||
}
|
||
""")
|
||
|
||
# 水平布局
|
||
self.toolbarLayout = QHBoxLayout(self.embeddedToolbar)
|
||
self.toolbarLayout.setContentsMargins(5, 5, 5, 5)
|
||
self.toolbarLayout.setSpacing(2)
|
||
|
||
# 创建工具按钮组
|
||
self.toolGroup = QButtonGroup()
|
||
|
||
# 选择工具
|
||
self.selectTool = QToolButton()
|
||
self.selectTool.setIcon(QIcon("icons/select_tool.png")) # 使用图标资源
|
||
self.selectTool.setText('选择')
|
||
self.selectTool.setIconSize(QSize(16, 16))
|
||
self.selectTool.setCheckable(True)
|
||
self.selectTool.setToolTip("选择工具 (Q)")
|
||
# self.selectTool.setShortcut(QKeySequence("Q"))
|
||
self.toolGroup.addButton(self.selectTool)
|
||
self.toolbarLayout.addWidget(self.selectTool)
|
||
|
||
# 移动工具
|
||
self.moveTool = QToolButton()
|
||
self.moveTool.setIcon(QIcon("icons/move_tool.png"))
|
||
self.moveTool.setText('移动')
|
||
self.moveTool.setIconSize(QSize(16, 16))
|
||
self.moveTool.setCheckable(True)
|
||
self.moveTool.setToolTip("移动工具 (W)")
|
||
# self.moveTool.setShortcut(QKeySequence("W"))
|
||
self.toolGroup.addButton(self.moveTool)
|
||
self.toolbarLayout.addWidget(self.moveTool)
|
||
|
||
# 旋转工具
|
||
self.rotateTool = QToolButton()
|
||
self.rotateTool.setIcon(QIcon("icons/rotate_tool.png"))
|
||
self.rotateTool.setText('旋转')
|
||
self.rotateTool.setIconSize(QSize(16, 16))
|
||
self.rotateTool.setCheckable(True)
|
||
self.rotateTool.setToolTip("旋转工具 (E)")
|
||
# self.rotateTool.setShortcut(QKeySequence("E"))
|
||
self.toolGroup.addButton(self.rotateTool)
|
||
self.toolbarLayout.addWidget(self.rotateTool)
|
||
|
||
# 缩放工具
|
||
self.scaleTool = QToolButton()
|
||
self.scaleTool.setIcon(QIcon("icons/scale_tool.png"))
|
||
self.scaleTool.setText('缩放')
|
||
self.scaleTool.setIconSize(QSize(16, 16))
|
||
self.scaleTool.setCheckable(True)
|
||
self.scaleTool.setToolTip("缩放工具 (R)")
|
||
# self.scaleTool.setShortcut(QKeySequence("R"))
|
||
self.toolGroup.addButton(self.scaleTool)
|
||
self.toolbarLayout.addWidget(self.scaleTool)
|
||
|
||
# 添加分隔符
|
||
separator = QFrame()
|
||
separator.setFrameShape(QFrame.VLine)
|
||
separator.setFrameShadow(QFrame.Sunken)
|
||
separator.setStyleSheet("background-color: rgba(240, 240, 240, 255);")
|
||
separator.setFixedWidth(1)
|
||
self.toolbarLayout.addWidget(separator)
|
||
|
||
# 设置位置到左上角
|
||
self.embeddedToolbar.move(10, 10)
|
||
self.embeddedToolbar.adjustSize()
|
||
self.embeddedToolbar.show()
|
||
|
||
# 连接事件
|
||
self.toolGroup.buttonClicked.connect(self.onToolChanged)
|
||
# 设置工具栏拖拽事件
|
||
self.embeddedToolbar.mousePressEvent = self.toolbarMousePressEvent
|
||
self.embeddedToolbar.mouseMoveEvent = self.toolbarMouseMoveEvent
|
||
self.embeddedToolbar.mouseReleaseEvent = self.toolbarMouseReleaseEvent
|
||
|
||
# 默认选择"选择"工具
|
||
self.selectTool.setChecked(True)
|
||
self.world.setCurrentTool("选择")
|
||
|
||
def toolbarMousePressEvent(self, event):
|
||
"""工具栏鼠标按下事件"""
|
||
if event.button() == Qt.LeftButton:
|
||
self.toolbarDragging = True
|
||
self.dragStartPos = event.globalPos()
|
||
self.toolbarStartPos = self.embeddedToolbar.pos()
|
||
event.accept()
|
||
|
||
def toolbarMouseMoveEvent(self, event):
|
||
"""工具栏鼠标移动事件"""
|
||
if self.toolbarDragging and event.buttons() == Qt.LeftButton:
|
||
# 计算新位置
|
||
delta = event.globalPos() - self.dragStartPos
|
||
new_pos = self.toolbarStartPos + delta
|
||
|
||
# 边界检测
|
||
panda_rect = self.pandaWidget.geometry()
|
||
toolbar_size = self.embeddedToolbar.size()
|
||
|
||
# 限制在Panda3D区域内
|
||
new_pos.setX(max(0, min(new_pos.x(), panda_rect.width() - toolbar_size.width())))
|
||
new_pos.setY(max(0, min(new_pos.y(), panda_rect.height() - toolbar_size.height())))
|
||
|
||
self.embeddedToolbar.move(new_pos)
|
||
event.accept()
|
||
|
||
def toolbarMouseReleaseEvent(self, event):
|
||
"""工具栏鼠标释放事件"""
|
||
if event.button() == Qt.LeftButton and self.toolbarDragging:
|
||
self.toolbarDragging = False
|
||
|
||
# 自动吸附到最近的预设位置
|
||
self.snapToNearestPosition()
|
||
event.accept()
|
||
|
||
def snapToNearestPosition(self):
|
||
"""自动吸附到最近的预设位置"""
|
||
current_pos = self.embeddedToolbar.pos()
|
||
panda_rect = self.pandaWidget.geometry()
|
||
toolbar_size = self.embeddedToolbar.size()
|
||
|
||
margin = 10
|
||
|
||
# 定义所有预设位置
|
||
positions = {
|
||
"top_center": QPoint(
|
||
(panda_rect.width() - toolbar_size.width()) // 2,
|
||
margin
|
||
),
|
||
"top_left": QPoint(margin, margin),
|
||
"top_right": QPoint(
|
||
panda_rect.width() - toolbar_size.width() - margin,
|
||
margin
|
||
),
|
||
"bottom_center": QPoint(
|
||
(panda_rect.width() - toolbar_size.width()) // 2,
|
||
panda_rect.height() - toolbar_size.height() - margin
|
||
),
|
||
"bottom_left": QPoint(
|
||
margin,
|
||
panda_rect.height() - toolbar_size.height() - margin
|
||
),
|
||
"bottom_right": QPoint(
|
||
panda_rect.width() - toolbar_size.width() - margin,
|
||
panda_rect.height() - toolbar_size.height() - margin
|
||
)
|
||
}
|
||
|
||
# 找到最近的位置
|
||
min_distance = float('inf')
|
||
nearest_position = "top_center"
|
||
|
||
for pos_name, pos_point in positions.items():
|
||
distance = ((current_pos.x() - pos_point.x()) ** 2 +
|
||
(current_pos.y() - pos_point.y()) ** 2) ** 0.5
|
||
if distance < min_distance:
|
||
min_distance = distance
|
||
nearest_position = pos_name
|
||
|
||
# 更新位置并平滑移动
|
||
self.toolbarPosition = nearest_position
|
||
target_pos = positions[nearest_position]
|
||
|
||
# 简单的平滑移动动画
|
||
from PyQt5.QtCore import QPropertyAnimation, QEasingCurve
|
||
self.toolbarAnimation = QPropertyAnimation(self.embeddedToolbar, b"pos")
|
||
self.toolbarAnimation.setDuration(200)
|
||
self.toolbarAnimation.setStartValue(current_pos)
|
||
self.toolbarAnimation.setEndValue(target_pos)
|
||
self.toolbarAnimation.setEasingCurve(QEasingCurve.OutCubic)
|
||
self.toolbarAnimation.start()
|
||
|
||
def setupMenus(self):
|
||
"""创建菜单栏"""
|
||
menubar = self.menuBar()
|
||
|
||
# 文件菜单
|
||
self.fileMenu = menubar.addMenu('文件')
|
||
self.newAction = self.fileMenu.addAction('新建')
|
||
self.openAction = self.fileMenu.addAction('打开')
|
||
self.saveAction = self.fileMenu.addAction('保存')
|
||
self.buildAction = self.fileMenu.addAction('打包')
|
||
self.fileMenu.addSeparator()
|
||
self.exitAction = self.fileMenu.addAction('退出')
|
||
|
||
# 编辑菜单
|
||
self.editMenu = menubar.addMenu('编辑')
|
||
self.undoAction = self.editMenu.addAction('撤销')
|
||
self.redoAction = self.editMenu.addAction('重做')
|
||
self.editMenu.addSeparator()
|
||
self.cutAction = self.editMenu.addAction('剪切')
|
||
self.copyAction = self.editMenu.addAction('复制')
|
||
self.pasteAction = self.editMenu.addAction('粘贴')
|
||
|
||
# 视图菜单
|
||
self.viewMenu = menubar.addMenu('视图')
|
||
self.viewPerspectiveAction = self.viewMenu.addAction('透视图')
|
||
self.viewTopAction = self.viewMenu.addAction('俯视图')
|
||
self.viewFrontAction = self.viewMenu.addAction('前视图')
|
||
self.viewMenu.addSeparator()
|
||
self.viewGridAction = self.viewMenu.addAction('显示网格')
|
||
|
||
# 工具菜单
|
||
self.toolsMenu = menubar.addMenu('工具')
|
||
self.selectAction = self.toolsMenu.addAction('选择工具')
|
||
self.moveAction = self.toolsMenu.addAction('移动工具')
|
||
self.rotateAction = self.toolsMenu.addAction('旋转工具')
|
||
self.scaleAction = self.toolsMenu.addAction('缩放工具')
|
||
self.sunsetAction = self.toolsMenu.addAction('光照编辑')
|
||
self.pluginAction = self.toolsMenu.addAction('图形编辑')
|
||
|
||
# 统一创建菜单 - 关键修改
|
||
self.createMenu = menubar.addMenu('创建')
|
||
self.setupCreateMenuActions() # 统一创建菜单动作
|
||
|
||
self.createGUIaddMenu = self.createMenu.addMenu('GUI')
|
||
self.createButtonAction = self.createGUIaddMenu.addAction('创建按钮')
|
||
self.createLabelAction = self.createGUIaddMenu.addAction('创建标签')
|
||
self.createEntryAction = self.createGUIaddMenu.addAction('创建输入框')
|
||
self.createGUIaddMenu.addSeparator()
|
||
self.createVirtualScreenAction = self.createGUIaddMenu.addAction('创建虚拟屏幕')
|
||
self.createCesiumViewAction = self.createGUIaddMenu.addAction('创建Cesium地图')
|
||
self.toggleCesiumViewAction = self.createGUIaddMenu.addAction('开关地图')
|
||
self.refreshCesiumViewAction = self.createGUIaddMenu.addAction('刷新地图')
|
||
|
||
self.createLightaddMenu = self.createMenu.addMenu('光源')
|
||
self.createSpotLightAction = self.createLightaddMenu.addAction('聚光灯')
|
||
self.createPointLightAction = self.createLightaddMenu.addAction('点光源')
|
||
|
||
#添加地形菜单
|
||
self.createTerrainMenu = self.createMenu.addMenu('地形')
|
||
self.createFlatTerrainAction = self.createTerrainMenu.addAction('创建平面地形')
|
||
self.createHeightmapTerrainAction = self.createTerrainMenu.addAction('从高度图创建地形')
|
||
self.createTerrainMenu.addSeparator()
|
||
self.terrainEditModeAction = self.createTerrainMenu.addAction('地形编辑模式')
|
||
|
||
# GUI菜单
|
||
# GUI菜单 - 复用创建菜单的动作
|
||
self.guiMenu = menubar.addMenu('GUI')
|
||
self.guiEditModeAction = self.guiMenu.addAction('进入GUI编辑模式')
|
||
self.guiMenu.addSeparator()
|
||
self.guiMenu.addAction(self.createButtonAction)
|
||
self.guiMenu.addAction(self.createLabelAction)
|
||
self.guiMenu.addAction(self.createEntryAction)
|
||
self.guiMenu.addSeparator()
|
||
self.guiMenu.addAction(self.create3DTextAction)
|
||
self.guiMenu.addAction(self.createVirtualScreenAction)
|
||
|
||
# 脚本菜单
|
||
self.scriptMenu = menubar.addMenu('脚本')
|
||
self.createScriptAction = self.scriptMenu.addAction('创建脚本...')
|
||
self.loadScriptAction = self.scriptMenu.addAction('加载脚本文件...')
|
||
self.loadAllScriptsAction = self.scriptMenu.addAction('重载所有脚本')
|
||
self.scriptMenu.addSeparator()
|
||
self.toggleHotReloadAction = self.scriptMenu.addAction('启用热重载')
|
||
self.toggleHotReloadAction.setCheckable(True)
|
||
self.toggleHotReloadAction.setChecked(True) # 默认启用
|
||
self.scriptMenu.addSeparator()
|
||
self.openScriptsManagerAction = self.scriptMenu.addAction('脚本管理器')
|
||
|
||
self.cesiumMenu = menubar.addMenu('Cesium')
|
||
self.loadCesiumTilesetAction = self.cesiumMenu.addAction('加载3Dtiles')
|
||
self.loadCesiumTilesetAction.triggered.connect(self.onLoadCesiumTileset)
|
||
|
||
# 添加地球视图相关菜单项
|
||
self.createCesiumViewAction = self.cesiumMenu.addAction('创建地球视图')
|
||
self.createCesiumViewAction.triggered.connect(self.onCreateCesiumView)
|
||
|
||
self.toggleCesiumViewAction = self.cesiumMenu.addAction('开关地球视图')
|
||
self.toggleCesiumViewAction.triggered.connect(self.onToggleCesiumView)
|
||
|
||
self.refreshCesiumViewAction = self.cesiumMenu.addAction('刷新地球视图')
|
||
self.refreshCesiumViewAction.triggered.connect(self.onRefreshCesiumView)
|
||
|
||
# 添加模型相关菜单项
|
||
self.addModelToCesiumAction = self.cesiumMenu.addAction('添加模型到地球')
|
||
self.addModelToCesiumAction.triggered.connect(self.onAddModelClicked)
|
||
|
||
|
||
# 帮助菜单
|
||
self.helpMenu = menubar.addMenu('帮助')
|
||
self.aboutAction = self.helpMenu.addAction('关于')
|
||
|
||
def setupCreateMenuActions(self):
|
||
"""统一设置创建菜单的所有动作 - 避免重复代码"""
|
||
# 基础对象
|
||
self.createEnptyaddAction = self.createMenu.addAction('空对象')
|
||
|
||
# 3D对象子菜单
|
||
self.create3dObjectaddMenu = self.createMenu.addMenu('3D对象')
|
||
# 可以在这里添加更多3D对象类型
|
||
|
||
# 3D GUI子菜单
|
||
self.create3dGUIaddMenu = self.createMenu.addMenu('3D GUI')
|
||
self.create3DTextAction = self.create3dGUIaddMenu.addAction('3D文本')
|
||
|
||
# GUI子菜单
|
||
self.createGUIaddMenu = self.createMenu.addMenu('GUI')
|
||
self.createButtonAction = self.createGUIaddMenu.addAction('创建按钮')
|
||
self.createLabelAction = self.createGUIaddMenu.addAction('创建标签')
|
||
self.createEntryAction = self.createGUIaddMenu.addAction('创建输入框')
|
||
self.createGUIaddMenu.addSeparator()
|
||
self.createVideoScreen = self.createGUIaddMenu.addAction('创建视频屏幕')
|
||
self.createSphericalVideo = self.createGUIaddMenu.addAction('创建球形视频')
|
||
self.createGUIaddMenu.addSeparator()
|
||
self.createVirtualScreenAction = self.createGUIaddMenu.addAction('创建虚拟屏幕')
|
||
|
||
# 光源子菜单
|
||
self.createLightaddMenu = self.createMenu.addMenu('光源')
|
||
self.createSpotLightAction = self.createLightaddMenu.addAction('聚光灯')
|
||
self.createPointLightAction = self.createLightaddMenu.addAction('点光源')
|
||
|
||
# 统一连接信号到处理方法
|
||
self.connectCreateMenuActions()
|
||
|
||
def connectCreateMenuActions(self):
|
||
"""统一连接创建菜单的信号到处理方法"""
|
||
# 连接到world对象的创建方法
|
||
# self.createEnptyaddAction.triggered.connect(self.world.createEmptyObject)
|
||
self.create3DTextAction.triggered.connect(lambda: self.world.createGUI3DText())
|
||
self.createButtonAction.triggered.connect(lambda: self.world.createGUIButton())
|
||
self.createLabelAction.triggered.connect(lambda: self.world.createGUILabel())
|
||
self.createEntryAction.triggered.connect(lambda: self.world.createGUIEntry())
|
||
# self.createVideoScreen.triggered.connect(self.world.createVideoScreen)
|
||
# self.createSphericalVideo.triggered.connect(self.world.createSphericalVideo)
|
||
# self.createVirtualScreenAction.triggered.connect(self.world.createVirtualScreen)
|
||
self.createSpotLightAction.triggered.connect(lambda :self.world.createSpotLight())
|
||
self.createPointLightAction.triggered.connect(lambda :self.world.createPointLight())
|
||
|
||
# # self.createVideoScreen.triggered.connect(lambda: self.world.video_manager.create_video_screen(
|
||
# # pos=(0, 0, 2),
|
||
# # size=(4, 3),
|
||
# # name=f"video_screen_{len(self.world.video_manager.video_objects) if hasattr(self.world, 'video_manager') else 0}"
|
||
# # ))
|
||
# # self.createSphericalVideo.triggered.connect(lambda: self.world.createGUISphericalVideo())
|
||
# self.createVirtualScreenAction.triggered.connect(lambda: self.world.create_spherical_video())
|
||
|
||
def getCreateMenuActions(self):
|
||
"""获取所有创建菜单动作的字典 - 供右键菜单使用"""
|
||
return {
|
||
'createEmpty': self.createEnptyaddAction,
|
||
'create3DText': self.create3DTextAction,
|
||
'createButton': self.createButtonAction,
|
||
'createLabel': self.createLabelAction,
|
||
'createEntry': self.createEntryAction,
|
||
'createVideoScreen': self.createVideoScreen,
|
||
'createSphericalVideo': self.createSphericalVideo,
|
||
'createVirtualScreen': self.createVirtualScreenAction,
|
||
'createSpotLight': self.createSpotLightAction,
|
||
'createPointLight': self.createPointLightAction,
|
||
}
|
||
|
||
def setupDockWindows(self):
|
||
"""创建停靠窗口"""
|
||
# 创建左侧停靠窗口(层级窗口)
|
||
self.leftDock = QDockWidget("层级", self)
|
||
self.treeWidget = CustomTreeWidget(self.world)
|
||
self.world.setTreeWidget(self.treeWidget) # 设置树形控件引用
|
||
self.leftDock.setWidget(self.treeWidget)
|
||
self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, self.leftDock)
|
||
|
||
# 创建右侧停靠窗口(属性窗口)
|
||
self.rightDock = QDockWidget("属性", self)
|
||
|
||
# 创建属性面板的主容器和布局
|
||
self.propertyContainer = QWidget()
|
||
self.propertyContainer.setObjectName("PropertyContainer")
|
||
|
||
try:
|
||
self.propertyLayout = QVBoxLayout(self.propertyContainer)
|
||
print(f"✓ 属性布局创建完成: {self.propertyLayout}")
|
||
|
||
# 添加初始提示信息
|
||
tipLabel = QLabel("选择对象以查看属性")
|
||
tipLabel.setStyleSheet("color: gray; padding: 10px;") # 使用灰色字体
|
||
self.propertyLayout.addWidget(tipLabel)
|
||
print("✓ 提示标签添加完成")
|
||
|
||
# 创建滚动区域并设置属性
|
||
self.scrollArea = QScrollArea()
|
||
self.scrollArea.setWidgetResizable(True)
|
||
self.scrollArea.setWidget(self.propertyContainer)
|
||
print("✓ 滚动区域设置完成")
|
||
|
||
# 设置滚动区域为停靠窗口的主部件
|
||
self.rightDock.setWidget(self.scrollArea)
|
||
self.rightDock.setMinimumWidth(300)
|
||
self.addDockWidget(Qt.RightDockWidgetArea, self.rightDock)
|
||
print("✓ 右侧停靠窗口添加完成")
|
||
|
||
# 设置属性面板到世界对象
|
||
if hasattr(self.world, 'setPropertyLayout'):
|
||
print("开始设置属性布局到世界对象...")
|
||
self.world.setPropertyLayout(self.propertyLayout)
|
||
print("✓ 属性布局设置完成")
|
||
else:
|
||
print("⚠ 世界对象没有 setPropertyLayout 方法")
|
||
|
||
except Exception as e:
|
||
print(f"✗ 设置属性面板时出错: {e}")
|
||
import traceback
|
||
traceback.print_exc()
|
||
|
||
# 创建基本的属性面板作为后备方案
|
||
fallback_widget = QLabel("属性面板初始化失败\n请查看控制台日志")
|
||
fallback_widget.setStyleSheet("color: red; background-color: white; padding: 10px;")
|
||
self.rightDock.setWidget(fallback_widget)
|
||
self.addDockWidget(Qt.RightDockWidgetArea, self.rightDock)
|
||
|
||
# 创建脚本管理停靠窗口
|
||
self.scriptDock = QDockWidget("脚本管理", self)
|
||
|
||
# 创建脚本面板的主容器和布局(与属性面板相同结构)
|
||
self.scriptContainer = QWidget()
|
||
self.scriptContainer.setObjectName("ScriptContainer")
|
||
self.scriptLayout = QVBoxLayout(self.scriptContainer)
|
||
|
||
# 创建滚动区域并设置属性
|
||
self.scriptScrollArea = QScrollArea()
|
||
self.scriptScrollArea.setWidgetResizable(True)
|
||
self.scriptScrollArea.setWidget(self.scriptContainer)
|
||
|
||
# 设置滚动区域为停靠窗口的主部件
|
||
self.scriptDock.setWidget(self.scriptScrollArea)
|
||
self.scriptDock.setMinimumWidth(300)
|
||
|
||
# 设置脚本面板内容 - 这里添加调用
|
||
self.setupScriptPanel(self.scriptLayout)
|
||
self.addDockWidget(Qt.RightDockWidgetArea, self.scriptDock)
|
||
|
||
# 将右侧停靠窗口设为标签形式
|
||
self.tabifyDockWidget(self.rightDock, self.scriptDock)
|
||
|
||
# # 创建底部停靠窗口(资源窗口)
|
||
# self.bottomDock = QDockWidget("资源", self)
|
||
# self.bottomDock.setAllowedAreas(Qt.BottomDockWidgetArea)
|
||
#
|
||
# # 创建文件系统模型
|
||
# self.fileModel = QFileSystemModel()
|
||
# self.fileModel.setRootPath(QDir.homePath()) # 设置为用户主目录
|
||
#
|
||
# # 创建树形视图显示文件系统
|
||
# self.fileView = CustomFileView(self.world)
|
||
# self.fileView.setModel(self.fileModel)
|
||
# self.fileView.setRootIndex(self.fileModel.index(QDir.homePath())) # 设置为用户主目录索引
|
||
#
|
||
# # 设置列宽
|
||
# self.fileView.setColumnWidth(0, 250) # 名称列
|
||
# self.fileView.setColumnWidth(1, 100) # 大小列
|
||
# self.fileView.setColumnWidth(2, 100) # 类型列
|
||
# self.fileView.setColumnWidth(3, 150) # 修改日期列
|
||
#
|
||
# # 设置视图属性
|
||
# self.fileView.setMinimumHeight(200) # 设置最小高度
|
||
#
|
||
# self.bottomDock.setWidget(self.fileView)
|
||
# self.addDockWidget(Qt.BottomDockWidgetArea, self.bottomDock)
|
||
|
||
# 创建底部停靠窗口(资源窗口)
|
||
self.bottomDock = QDockWidget("资源", self)
|
||
self.fileView = CustomAssetsTreeWidget(self.world)
|
||
self.bottomDock.setWidget(self.fileView)
|
||
self.addDockWidget(Qt.BottomDockWidgetArea, self.bottomDock)
|
||
|
||
# 创建底部停靠控制台
|
||
self.consoleDock = QDockWidget("控制台", self)
|
||
self.consoleView = CustomConsoleDockWidget(self.world)
|
||
self.consoleDock.setWidget(self.consoleView)
|
||
self.addDockWidget(Qt.BottomDockWidgetArea, self.consoleDock)
|
||
|
||
def setupToolbar(self):
|
||
"""创建工具栏"""
|
||
self.toolbar = self.addToolBar('工具栏')
|
||
|
||
# 创建工具按钮组
|
||
self.toolGroup = QButtonGroup()
|
||
|
||
# 选择工具
|
||
self.selectTool = QToolButton()
|
||
self.selectTool.setText("选择")
|
||
self.selectTool.setCheckable(True)
|
||
self.toolGroup.addButton(self.selectTool)
|
||
self.toolbar.addWidget(self.selectTool)
|
||
|
||
# 旋转工具
|
||
self.rotateTool = QToolButton()
|
||
self.rotateTool.setText("旋转")
|
||
self.rotateTool.setCheckable(True)
|
||
self.toolGroup.addButton(self.rotateTool)
|
||
self.toolbar.addWidget(self.rotateTool)
|
||
|
||
# 缩放工具
|
||
self.scaleTool = QToolButton()
|
||
self.scaleTool.setText("缩放")
|
||
self.scaleTool.setCheckable(True)
|
||
self.toolGroup.addButton(self.scaleTool)
|
||
self.toolbar.addWidget(self.scaleTool)
|
||
|
||
# 添加分隔符
|
||
self.toolbar.addSeparator()
|
||
|
||
# GUI创建工具
|
||
self.createButtonTool = QToolButton()
|
||
self.createButtonTool.setText("创建按钮")
|
||
self.toolbar.addWidget(self.createButtonTool)
|
||
|
||
self.createLabelTool = QToolButton()
|
||
self.createLabelTool.setText("创建标签")
|
||
self.toolbar.addWidget(self.createLabelTool)
|
||
|
||
self.create3DTextTool = QToolButton()
|
||
self.create3DTextTool.setText("3D文本")
|
||
self.toolbar.addWidget(self.create3DTextTool)
|
||
|
||
self.create3DImageTool = QToolButton()
|
||
self.create3DImageTool.setText("3D图片")
|
||
self.toolbar.addWidget(self.create3DImageTool)
|
||
|
||
self.create2DImageTool = QToolButton()
|
||
self.create2DImageTool.setText("2D图片")
|
||
self.toolbar.addWidget(self.create2DImageTool)
|
||
|
||
self.createSpotLight = QToolButton()
|
||
self.createSpotLight.setText("聚光灯")
|
||
self.toolbar.addWidget(self.createSpotLight)
|
||
|
||
self.createPointLight = QToolButton()
|
||
self.createPointLight.setText("点光灯")
|
||
self.toolbar.addWidget(self.createPointLight)
|
||
|
||
# Cesium 工具按钮
|
||
self.cesiumViewTool = QToolButton()
|
||
self.cesiumViewTool.setText("地图视图")
|
||
self.cesiumViewTool.clicked.connect(self.onCreateCesiumView)
|
||
self.toolbar.addWidget(self.cesiumViewTool)
|
||
|
||
self.refreshCesiumTool = QToolButton()
|
||
self.refreshCesiumTool.setText("刷新地图")
|
||
self.refreshCesiumTool.clicked.connect(self.onRefreshCesiumView)
|
||
self.toolbar.addWidget(self.refreshCesiumTool)
|
||
|
||
self.addModelTool = QToolButton()
|
||
self.addModelTool.setText("添加模型")
|
||
self.addModelTool.clicked.connect(self.onAddModelClicked)
|
||
self.toolbar.addWidget(self.addModelTool)
|
||
|
||
#地形
|
||
# 地形工具
|
||
self.createFlatTerrainTool = QToolButton()
|
||
self.createFlatTerrainTool.setText("平面地形")
|
||
self.toolbar.addWidget(self.createFlatTerrainTool)
|
||
|
||
self.createHeightmapTerrainTool = QToolButton()
|
||
self.createHeightmapTerrainTool.setText("高度图地形")
|
||
self.toolbar.addWidget(self.createHeightmapTerrainTool)
|
||
|
||
self.terrainEditTool = QToolButton()
|
||
self.terrainEditTool.setText("地形编辑")
|
||
self.terrainEditTool.setCheckable(True)
|
||
self.toolbar.addWidget(self.terrainEditTool)
|
||
|
||
# 默认选择"选择"工具
|
||
self.selectTool.setChecked(True)
|
||
self.world.setCurrentTool("选择")
|
||
|
||
def setupScriptPanel(self, layout):
|
||
"""创建脚本管理面板"""
|
||
# 脚本状态组
|
||
statusGroup = QGroupBox("脚本系统状态")
|
||
statusLayout = QVBoxLayout()
|
||
|
||
self.scriptStatusLabel = QLabel("脚本系统: 已启动")
|
||
self.scriptStatusLabel.setStyleSheet("color: green; font-weight: bold;")
|
||
statusLayout.addWidget(self.scriptStatusLabel)
|
||
|
||
self.hotReloadLabel = QLabel("热重载: 已启用")
|
||
self.hotReloadLabel.setStyleSheet("color: blue;")
|
||
statusLayout.addWidget(self.hotReloadLabel)
|
||
|
||
statusGroup.setLayout(statusLayout)
|
||
layout.addWidget(statusGroup)
|
||
|
||
# 脚本创建组
|
||
createGroup = QGroupBox("创建脚本")
|
||
createLayout = QVBoxLayout()
|
||
|
||
# 脚本名称输入
|
||
nameLayout = QHBoxLayout()
|
||
nameLayout.addWidget(QLabel("脚本名称:"))
|
||
self.scriptNameEdit = QLineEdit()
|
||
self.scriptNameEdit.setPlaceholderText("输入脚本名称...")
|
||
nameLayout.addWidget(self.scriptNameEdit)
|
||
createLayout.addLayout(nameLayout)
|
||
|
||
# 模板选择
|
||
templateLayout = QHBoxLayout()
|
||
templateLayout.addWidget(QLabel("模板:"))
|
||
self.templateCombo = QComboBox()
|
||
self.templateCombo.addItems(["basic", "movement", "animation"])
|
||
templateLayout.addWidget(self.templateCombo)
|
||
createLayout.addLayout(templateLayout)
|
||
|
||
# 创建按钮
|
||
self.createScriptBtn = QPushButton("创建脚本")
|
||
self.createScriptBtn.clicked.connect(self.onCreateScript)
|
||
createLayout.addWidget(self.createScriptBtn)
|
||
|
||
createGroup.setLayout(createLayout)
|
||
layout.addWidget(createGroup)
|
||
|
||
# 可用脚本组
|
||
scriptsGroup = QGroupBox("可用脚本")
|
||
scriptsLayout = QVBoxLayout()
|
||
|
||
# 脚本列表
|
||
self.scriptsList = QListWidget()
|
||
self.scriptsList.itemDoubleClicked.connect(self.onScriptDoubleClick)
|
||
scriptsLayout.addWidget(self.scriptsList)
|
||
|
||
# 脚本操作按钮
|
||
scriptButtonsLayout = QHBoxLayout()
|
||
|
||
self.loadScriptBtn = QPushButton("加载脚本")
|
||
self.loadScriptBtn.clicked.connect(self.onLoadScript)
|
||
scriptButtonsLayout.addWidget(self.loadScriptBtn)
|
||
|
||
self.reloadAllBtn = QPushButton("重载全部")
|
||
self.reloadAllBtn.clicked.connect(self.onReloadAllScripts)
|
||
scriptButtonsLayout.addWidget(self.reloadAllBtn)
|
||
|
||
scriptsLayout.addLayout(scriptButtonsLayout)
|
||
scriptsGroup.setLayout(scriptsLayout)
|
||
layout.addWidget(scriptsGroup)
|
||
|
||
# 脚本挂载组
|
||
mountGroup = QGroupBox("脚本挂载")
|
||
mountLayout = QVBoxLayout()
|
||
|
||
# 当前选中对象显示
|
||
self.selectedObjectLabel = QLabel("未选择对象")
|
||
self.selectedObjectLabel.setStyleSheet("color: gray; font-style: italic;")
|
||
mountLayout.addWidget(self.selectedObjectLabel)
|
||
|
||
# 脚本选择和挂载
|
||
mountControlLayout = QHBoxLayout()
|
||
|
||
self.mountScriptCombo = QComboBox()
|
||
self.mountScriptCombo.setEnabled(False)
|
||
mountControlLayout.addWidget(self.mountScriptCombo)
|
||
|
||
self.mountBtn = QPushButton("挂载")
|
||
self.mountBtn.setEnabled(False)
|
||
self.mountBtn.clicked.connect(self.onMountScript)
|
||
mountControlLayout.addWidget(self.mountBtn)
|
||
|
||
mountLayout.addLayout(mountControlLayout)
|
||
|
||
# 已挂载脚本列表
|
||
self.mountedScriptsList = QListWidget()
|
||
self.mountedScriptsList.setMaximumHeight(100)
|
||
mountLayout.addWidget(QLabel("已挂载脚本:"))
|
||
mountLayout.addWidget(self.mountedScriptsList)
|
||
|
||
# 卸载按钮
|
||
self.unmountBtn = QPushButton("卸载选中脚本")
|
||
self.unmountBtn.clicked.connect(self.onUnmountScript)
|
||
mountLayout.addWidget(self.unmountBtn)
|
||
|
||
mountGroup.setLayout(mountLayout)
|
||
layout.addWidget(mountGroup)
|
||
|
||
# 添加拉伸以填充剩余空间
|
||
layout.addStretch()
|
||
|
||
# 初始化脚本列表
|
||
self.refreshScriptsList()
|
||
|
||
def connectEvents(self):
|
||
"""连接事件信号"""
|
||
# 导入项目管理功能函数
|
||
from main import createNewProject, saveProject, openProject, buildPackage
|
||
|
||
# 连接文件菜单事件
|
||
self.newAction.triggered.connect(lambda: createNewProject(self))
|
||
self.openAction.triggered.connect(lambda: openProject(self))
|
||
self.saveAction.triggered.connect(lambda: saveProject(self))
|
||
self.buildAction.triggered.connect(lambda: buildPackage(self))
|
||
self.exitAction.triggered.connect(QApplication.instance().quit)
|
||
|
||
# 连接工具事件
|
||
self.sunsetAction.triggered.connect(lambda: self.world.setCurrentTool("光照编辑"))
|
||
self.pluginAction.triggered.connect(lambda: self.world.setCurrentTool("图形编辑"))
|
||
|
||
# 连接GUI编辑模式事件
|
||
self.guiEditModeAction.triggered.connect(lambda: self.world.toggleGUIEditMode())
|
||
|
||
# 连接创建事件 - 使用菜单动作而不是不存在的工具栏按钮
|
||
self.createSpotLightAction.triggered.connect(lambda: self.world.createSpotLight())
|
||
self.createPointLightAction.triggered.connect(lambda: self.world.createPointLight())
|
||
self.createButtonAction.triggered.connect(lambda: self.world.createGUIButton())
|
||
self.createLabelAction.triggered.connect(lambda: self.world.createGUILabel())
|
||
self.createEntryAction.triggered.connect(lambda: self.world.createGUIEntry())
|
||
self.create3DTextAction.triggered.connect(lambda: self.world.createGUI3DText())
|
||
self.createVirtualScreenAction.triggered.connect(lambda: self.world.createGUIVirtualScreen())
|
||
self.createCesiumViewAction.triggered.connect(self.onCreateCesiumView)
|
||
self.toggleCesiumViewAction.triggered.connect(self.onToggleCesiumView)
|
||
self.refreshCesiumViewAction.triggered.connect(self.onRefreshCesiumView)
|
||
|
||
# 连接地形创建事件
|
||
self.createFlatTerrainAction.triggered.connect(self.onCreateFlatTerrain)
|
||
self.createHeightmapTerrainAction.triggered.connect(self.onCreateHeightmapTerrain)
|
||
self.terrainEditModeAction.triggered.connect(self.onTerrainEditMode)
|
||
|
||
# 连接树节点点击信号
|
||
self.treeWidget.itemSelectionChanged.connect(
|
||
lambda: self.world.onTreeItemClicked(self.treeWidget.currentItem(), 0))
|
||
print("已连接点击信号")
|
||
|
||
# 连接工具切换信号
|
||
self.toolGroup.buttonClicked.connect(self.onToolChanged)
|
||
|
||
# 连接脚本菜单事件
|
||
self.createScriptAction.triggered.connect(self.onCreateScriptDialog)
|
||
self.loadScriptAction.triggered.connect(self.onLoadScriptFile)
|
||
self.loadAllScriptsAction.triggered.connect(self.onReloadAllScripts)
|
||
self.toggleHotReloadAction.triggered.connect(self.onToggleHotReload)
|
||
self.openScriptsManagerAction.triggered.connect(self.onOpenScriptsManager)
|
||
|
||
|
||
def onCreateCesiumView(self):
|
||
if hasattr(self.world,'gui_manager') and self.world.gui_manager:
|
||
self.world.gui_manager.createCesiumView()
|
||
else:
|
||
QMessageBox.warning(self,"错误","GUI管理其不可用")
|
||
|
||
def onToggleCesiumView(self):
|
||
"""切换 Cesium 视图显示状态"""
|
||
if hasattr(self.world, 'gui_manager') and self.world.gui_manager:
|
||
self.world.gui_manager.toggleCesiumView()
|
||
else:
|
||
QMessageBox.warning(self, "错误", "GUI 管理器不可用")
|
||
|
||
def onRefreshCesiumView(self):
|
||
"""刷新 Cesium 视图"""
|
||
if hasattr(self.world, 'gui_manager') and self.world.gui_manager:
|
||
self.world.gui_manager.refreshCesiumView()
|
||
else:
|
||
QMessageBox.warning(self, "错误", "GUI 管理器不可用")
|
||
|
||
def onUpdateCesiumURL(self):
|
||
"""更新 Cesium URL"""
|
||
url, ok = QInputDialog.getText(self, "更新 Cesium URL", "输入新的 URL:",
|
||
QLineEdit.Normal, "http://localhost:8080/Apps/HelloWorld.html")
|
||
if ok and url:
|
||
if hasattr(self.world, 'gui_manager') and self.world.gui_manager:
|
||
self.world.gui_manager.updateCesiumURL(url)
|
||
else:
|
||
QMessageBox.warning(self, "错误", "GUI 管理器不可用")
|
||
|
||
def onAddModelClicked(self):
|
||
"""处理加入模型按钮点击事件"""
|
||
# 检查 Cesium 视图是否存在
|
||
cesium_view_exists = False
|
||
if hasattr(self.world, 'gui_manager') and self.world.gui_manager:
|
||
for element in self.world.gui_manager.gui_elements:
|
||
if hasattr(element, 'objectName') and element.objectName() == "CesiumView":
|
||
cesium_view_exists = True
|
||
break
|
||
|
||
if not cesium_view_exists:
|
||
reply = QMessageBox.question(
|
||
self,
|
||
'提示',
|
||
'Cesium 地图视图尚未打开,是否先打开地图视图?',
|
||
QMessageBox.Yes | QMessageBox.No,
|
||
QMessageBox.Yes
|
||
)
|
||
|
||
if reply == QMessageBox.Yes:
|
||
self.onCreateCesiumView()
|
||
# 给一点时间让 Cesium 视图加载
|
||
QTimer.singleShot(1000, self.showAddModelDialog)
|
||
return
|
||
else:
|
||
return
|
||
|
||
self.showAddModelDialog()
|
||
|
||
def showAddModelDialog(self):
|
||
"""显示添加模型对话框"""
|
||
# 打开文件选择对话框
|
||
file_path, _ = QFileDialog.getOpenFileName(
|
||
self,
|
||
"选择 3D 模型文件",
|
||
"",
|
||
"3D 模型文件 (*.glb *.gltf *.obj);;所有文件 (*)"
|
||
)
|
||
|
||
if file_path:
|
||
# 获取模型位置信息
|
||
coords, ok = self.getModelCoordinates()
|
||
if ok:
|
||
longitude, latitude, height, scale = coords
|
||
|
||
# 生成唯一的模型 ID
|
||
import uuid
|
||
model_id = f"model_{uuid.uuid4().hex[:8]}"
|
||
|
||
try:
|
||
# 添加模型到 Cesium
|
||
if hasattr(self.world, 'gui_manager') and self.world.gui_manager:
|
||
success = self.world.gui_manager.addModelToCesium(
|
||
model_id,
|
||
file_path,
|
||
longitude,
|
||
latitude,
|
||
height,
|
||
scale
|
||
)
|
||
|
||
if success:
|
||
QMessageBox.information(
|
||
self,
|
||
"成功",
|
||
f"模型已成功添加到地图!\n模型ID: {model_id}"
|
||
)
|
||
else:
|
||
QMessageBox.warning(
|
||
self,
|
||
"失败",
|
||
"添加模型失败,请检查控制台输出"
|
||
)
|
||
except Exception as e:
|
||
QMessageBox.critical(
|
||
self,
|
||
"错误",
|
||
f"添加模型时发生错误:\n{str(e)}"
|
||
)
|
||
|
||
def getModelCoordinates(self):
|
||
"""获取模型坐标信息的对话框"""
|
||
# 创建对话框
|
||
dialog = QDialog(self)
|
||
dialog.setWindowTitle("设置模型位置")
|
||
dialog.setModal(True)
|
||
dialog.resize(300, 200)
|
||
|
||
layout = QVBoxLayout(dialog)
|
||
|
||
# 经度
|
||
lon_layout = QHBoxLayout()
|
||
lon_layout.addWidget(QLabel("经度:"))
|
||
lon_spin = QDoubleSpinBox()
|
||
lon_spin.setRange(-180, 180)
|
||
lon_spin.setValue(116.3975) # 默认北京位置
|
||
lon_layout.addWidget(lon_spin)
|
||
layout.addLayout(lon_layout)
|
||
|
||
# 纬度
|
||
lat_layout = QHBoxLayout()
|
||
lat_layout.addWidget(QLabel("纬度:"))
|
||
lat_spin = QDoubleSpinBox()
|
||
lat_spin.setRange(-90, 90)
|
||
lat_spin.setValue(39.9085) # 默认北京位置
|
||
lat_layout.addWidget(lat_spin)
|
||
layout.addLayout(lat_layout)
|
||
|
||
# 高度
|
||
height_layout = QHBoxLayout()
|
||
height_layout.addWidget(QLabel("高度(米):"))
|
||
height_spin = QDoubleSpinBox()
|
||
height_spin.setRange(-10000, 100000)
|
||
height_spin.setValue(0)
|
||
height_layout.addWidget(height_spin)
|
||
layout.addLayout(height_layout)
|
||
|
||
# 缩放
|
||
scale_layout = QHBoxLayout()
|
||
scale_layout.addWidget(QLabel("缩放:"))
|
||
scale_spin = QDoubleSpinBox()
|
||
scale_spin.setRange(0.001, 100000)
|
||
scale_spin.setValue(1.0)
|
||
scale_spin.setSingleStep(0.1)
|
||
scale_layout.addWidget(scale_spin)
|
||
layout.addLayout(scale_layout)
|
||
|
||
# 按钮
|
||
button_layout = QHBoxLayout()
|
||
ok_button = QPushButton("确定")
|
||
cancel_button = QPushButton("取消")
|
||
button_layout.addWidget(ok_button)
|
||
button_layout.addWidget(cancel_button)
|
||
layout.addLayout(button_layout)
|
||
|
||
# 连接信号
|
||
ok_button.clicked.connect(dialog.accept)
|
||
cancel_button.clicked.connect(dialog.reject)
|
||
|
||
# 显示对话框
|
||
if dialog.exec_() == QDialog.Accepted:
|
||
return (
|
||
lon_spin.value(),
|
||
lat_spin.value(),
|
||
height_spin.value(),
|
||
scale_spin.value()
|
||
), True
|
||
else:
|
||
return None, False
|
||
|
||
def onLoadCesiumTileset(self):
|
||
url,ok = QInputDialog.getText(
|
||
self,
|
||
"加载 Cesium 3D Tiles",
|
||
"输入 tileset.json URL:",
|
||
QLineEdit.Normal,
|
||
"https://assets.ion.cesium.com/96128/tileset.json"
|
||
)
|
||
|
||
if ok and url:
|
||
try:
|
||
# 生成唯一的 tileset 名称
|
||
import uuid
|
||
tileset_name = f"tileset_{uuid.uuid4().hex[:8]}"
|
||
|
||
# 加载 tileset
|
||
if hasattr(self.world, 'addCesiumTileset'):
|
||
success = self.world.addCesiumTileset(tileset_name, url, (0, 0, 0))
|
||
if success:
|
||
QMessageBox.information(
|
||
self,
|
||
"成功",
|
||
f"Cesium 3D Tiles 已加载到场景中!\n名称: {tileset_name}"
|
||
)
|
||
else:
|
||
QMessageBox.warning(
|
||
self,
|
||
"失败",
|
||
"加载 Cesium 3D Tiles 失败"
|
||
)
|
||
except Exception as e:
|
||
QMessageBox.critical(
|
||
self,
|
||
"错误",
|
||
f"加载 Cesium 3D Tiles 时发生错误:\n{str(e)}"
|
||
)
|
||
|
||
|
||
def onToolChanged(self, button):
|
||
"""工具切换事件处理"""
|
||
if button.isChecked():
|
||
tool_name = button.text()
|
||
self.world.setCurrentTool(tool_name)
|
||
print(f"工具栏: 选择了 {tool_name} 工具")
|
||
else:
|
||
self.world.setCurrentTool(None)
|
||
print("工具栏: 取消选择工具")
|
||
|
||
# ==================== 脚本管理事件处理 ====================
|
||
|
||
def refreshScriptsList(self):
|
||
"""刷新脚本列表"""
|
||
self.scriptsList.clear()
|
||
self.mountScriptCombo.clear()
|
||
|
||
available_scripts = self.world.getAvailableScripts()
|
||
for script_name in available_scripts:
|
||
self.scriptsList.addItem(script_name)
|
||
self.mountScriptCombo.addItem(script_name)
|
||
|
||
def updateScriptPanel(self):
|
||
"""更新脚本面板状态"""
|
||
# 更新热重载状态
|
||
hot_reload_enabled = self.world.script_manager.hot_reload_enabled
|
||
self.hotReloadLabel.setText(f"热重载: {'已启用' if hot_reload_enabled else '已禁用'}")
|
||
self.hotReloadLabel.setStyleSheet(f"color: {'blue' if hot_reload_enabled else 'gray'};")
|
||
|
||
# 更新热重载菜单状态
|
||
self.toggleHotReloadAction.setChecked(hot_reload_enabled)
|
||
|
||
# 更新选中对象信息
|
||
selected_object = getattr(self.world.selection, 'selectedObject', None)
|
||
if selected_object:
|
||
self.selectedObjectLabel.setText(f"选中对象: {selected_object.getName()}")
|
||
self.selectedObjectLabel.setStyleSheet("color: green; font-weight: bold;")
|
||
self.mountScriptCombo.setEnabled(True)
|
||
self.mountBtn.setEnabled(True)
|
||
|
||
# 更新已挂载脚本列表
|
||
self.updateMountedScriptsList(selected_object)
|
||
else:
|
||
self.selectedObjectLabel.setText("未选择对象")
|
||
self.selectedObjectLabel.setStyleSheet("color: gray; font-style: italic;")
|
||
self.mountScriptCombo.setEnabled(False)
|
||
self.mountBtn.setEnabled(False)
|
||
self.mountedScriptsList.clear()
|
||
|
||
def updateMountedScriptsList(self, game_object):
|
||
"""更新已挂载脚本列表"""
|
||
# 保存当前选中项的脚本名(去除状态前缀)
|
||
current_item = self.mountedScriptsList.currentItem()
|
||
selected_script_name = None
|
||
if current_item:
|
||
# 提取脚本名(移除 "✓ " 或 "✗ " 前缀)
|
||
selected_script_name = current_item.text()[2:]
|
||
|
||
# 清空并重新填充列表
|
||
self.mountedScriptsList.clear()
|
||
scripts = self.world.getScripts(game_object)
|
||
for script_component in scripts:
|
||
script_name = script_component.script_name
|
||
enabled = "✓" if script_component.enabled else "✗"
|
||
item_text = f"{enabled} {script_name}"
|
||
self.mountedScriptsList.addItem(item_text)
|
||
|
||
# 恢复选中状态(根据脚本名匹配)
|
||
if selected_script_name:
|
||
for i in range(self.mountedScriptsList.count()):
|
||
item = self.mountedScriptsList.item(i)
|
||
# 提取当前项的脚本名进行比较
|
||
current_script_name = item.text()[2:]
|
||
if current_script_name == selected_script_name:
|
||
self.mountedScriptsList.setCurrentItem(item)
|
||
break
|
||
|
||
def onCreateScript(self):
|
||
"""创建脚本按钮事件"""
|
||
script_name = self.scriptNameEdit.text().strip()
|
||
if not script_name:
|
||
QMessageBox.warning(self, "错误", "请输入脚本名称!")
|
||
return
|
||
|
||
template = self.templateCombo.currentText()
|
||
|
||
try:
|
||
success = self.world.createScript(script_name, template)
|
||
if success:
|
||
QMessageBox.information(self, "成功", f"脚本 '{script_name}' 创建成功!")
|
||
self.scriptNameEdit.clear()
|
||
self.refreshScriptsList()
|
||
else:
|
||
QMessageBox.warning(self, "错误", f"脚本 '{script_name}' 创建失败!")
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "错误", f"创建脚本时出错: {str(e)}")
|
||
|
||
def onCreateScriptDialog(self):
|
||
"""菜单创建脚本事件"""
|
||
script_name, ok = QInputDialog.getText(self, "创建脚本", "输入脚本名称:")
|
||
if ok and script_name.strip():
|
||
try:
|
||
success = self.world.createScript(script_name.strip(), "basic")
|
||
if success:
|
||
QMessageBox.information(self, "成功", f"脚本 '{script_name}' 创建成功!")
|
||
self.refreshScriptsList()
|
||
else:
|
||
QMessageBox.warning(self, "错误", f"脚本 '{script_name}' 创建失败!")
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "错误", f"创建脚本时出错: {str(e)}")
|
||
|
||
def onLoadScript(self):
|
||
"""加载脚本按钮事件"""
|
||
current_item = self.scriptsList.currentItem()
|
||
if not current_item:
|
||
QMessageBox.warning(self, "错误", "请选择要加载的脚本!")
|
||
return
|
||
|
||
script_name = current_item.text()
|
||
try:
|
||
success = self.world.reloadScript(script_name)
|
||
if success:
|
||
QMessageBox.information(self, "成功", f"脚本 '{script_name}' 重载成功!")
|
||
else:
|
||
QMessageBox.warning(self, "错误", f"脚本 '{script_name}' 重载失败!")
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "错误", f"重载脚本时出错: {str(e)}")
|
||
|
||
def onLoadScriptFile(self):
|
||
"""加载脚本文件菜单事件"""
|
||
file_path, _ = QFileDialog.getOpenFileName(
|
||
self, "选择脚本文件", "", "Python文件 (*.py)"
|
||
)
|
||
if file_path:
|
||
try:
|
||
success = self.world.loadScript(file_path)
|
||
if success:
|
||
QMessageBox.information(self, "成功", "脚本文件加载成功!")
|
||
self.refreshScriptsList()
|
||
else:
|
||
QMessageBox.warning(self, "错误", "脚本文件加载失败!")
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "错误", f"加载脚本文件时出错: {str(e)}")
|
||
|
||
def onReloadAllScripts(self):
|
||
"""重载所有脚本事件"""
|
||
try:
|
||
scripts_loaded = self.world.loadAllScripts()
|
||
QMessageBox.information(self, "成功", f"重载完成,共加载 {len(scripts_loaded)} 个脚本!")
|
||
self.refreshScriptsList()
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "错误", f"重载脚本时出错: {str(e)}")
|
||
|
||
def onToggleHotReload(self):
|
||
"""切换热重载状态"""
|
||
enabled = self.toggleHotReloadAction.isChecked()
|
||
self.world.enableHotReload(enabled)
|
||
status = "启用" if enabled else "禁用"
|
||
QMessageBox.information(self, "热重载", f"热重载已{status}")
|
||
|
||
def onOpenScriptsManager(self):
|
||
"""打开脚本管理器"""
|
||
# 显示脚本管理停靠窗口
|
||
self.scriptDock.show()
|
||
self.scriptDock.raise_()
|
||
|
||
def onScriptDoubleClick(self, item):
|
||
"""脚本列表双击事件"""
|
||
# 可以在这里添加打开外部编辑器的功能
|
||
script_name = item.text()
|
||
QMessageBox.information(self, "提示", f"双击了脚本: {script_name}\n\n可以使用外部编辑器编辑脚本文件。")
|
||
|
||
def onMountScript(self):
|
||
"""挂载脚本事件"""
|
||
selected_object = getattr(self.world.selection, 'selectedObject', None)
|
||
if not selected_object:
|
||
QMessageBox.warning(self, "错误", "请先选择一个对象!")
|
||
return
|
||
|
||
script_name = self.mountScriptCombo.currentText()
|
||
if not script_name:
|
||
QMessageBox.warning(self, "错误", "请选择要挂载的脚本!")
|
||
return
|
||
|
||
try:
|
||
success = self.world.addScript(selected_object, script_name)
|
||
if success:
|
||
QMessageBox.information(self, "成功", f"脚本 '{script_name}' 已挂载到对象!")
|
||
self.updateMountedScriptsList(selected_object)
|
||
|
||
# 同时更新属性面板
|
||
if self.treeWidget and self.treeWidget.currentItem():
|
||
self.world.updatePropertyPanel(self.treeWidget.currentItem())
|
||
else:
|
||
QMessageBox.warning(self, "错误", f"挂载脚本失败!")
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "错误", f"挂载脚本时出错: {str(e)}")
|
||
|
||
def onUnmountScript(self):
|
||
"""卸载脚本事件"""
|
||
selected_object = getattr(self.world.selection, 'selectedObject', None)
|
||
if not selected_object:
|
||
QMessageBox.warning(self, "错误", "请先选择一个对象!")
|
||
return
|
||
|
||
current_item = self.mountedScriptsList.currentItem()
|
||
if not current_item:
|
||
QMessageBox.warning(self, "错误", "请选择要卸载的脚本!")
|
||
return
|
||
|
||
# 解析脚本名称(移除状态标记)
|
||
item_text = current_item.text()
|
||
script_name = item_text[2:] # 移除 "✓ " 或 "✗ " 前缀
|
||
|
||
try:
|
||
success = self.world.removeScript(selected_object, script_name)
|
||
if success:
|
||
QMessageBox.information(self, "成功", f"脚本 '{script_name}' 已从对象卸载!")
|
||
self.updateMountedScriptsList(selected_object)
|
||
|
||
# 同时更新属性面板
|
||
if self.treeWidget and self.treeWidget.currentItem():
|
||
self.world.updatePropertyPanel(self.treeWidget.currentItem())
|
||
else:
|
||
QMessageBox.warning(self, "错误", f"卸载脚本失败!")
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "错误", f"卸载脚本时出错: {str(e)}")
|
||
|
||
def closeEvent(self, event):
|
||
"""处理窗口关闭事件"""
|
||
try:
|
||
print("🔄 正在关闭应用程序...")
|
||
|
||
# 清理工具管理器中的进程
|
||
if hasattr(self.world, 'tool_manager') and self.world.tool_manager:
|
||
print("🧹 清理工具管理器进程...")
|
||
self.world.tool_manager.cleanup_processes()
|
||
|
||
# 停止更新定时器
|
||
if hasattr(self, 'updateTimer') and self.updateTimer:
|
||
self.updateTimer.stop()
|
||
print("⏹️ 更新定时器已停止")
|
||
|
||
# 清理Panda3D资源
|
||
if hasattr(self, 'pandaWidget') and self.pandaWidget:
|
||
print("🧹 清理Panda3D资源...")
|
||
self.pandaWidget.cleanup()
|
||
|
||
print("✅ 应用程序清理完成")
|
||
event.accept()
|
||
|
||
except Exception as e:
|
||
print(f"⚠️ 关闭应用程序时出错: {e}")
|
||
event.accept() # 即使出错也要关闭
|
||
|
||
def onCreateFlatTerrain(self):
|
||
"""创建平面地形"""
|
||
dialog = QDialog(self)
|
||
dialog.setWindowTitle("创建平面地形")
|
||
dialog.setModal(True)
|
||
dialog.resize(300,200)
|
||
|
||
layout = QVBoxLayout(dialog)
|
||
|
||
width_layout = QHBoxLayout()
|
||
width_layout.addWidget(QLabel("宽度:"))
|
||
width_spin = QDoubleSpinBox()
|
||
width_spin.setRange(0,10000)
|
||
width_spin.setValue(0.3)
|
||
width_layout.addWidget(width_spin)
|
||
layout.addLayout(width_layout)
|
||
|
||
# 高度
|
||
height_layout = QHBoxLayout()
|
||
height_layout.addWidget(QLabel("高度:"))
|
||
height_spin = QDoubleSpinBox()
|
||
height_spin.setRange(0, 10000)
|
||
height_spin.setValue(0.3)
|
||
height_layout.addWidget(height_spin)
|
||
layout.addLayout(height_layout)
|
||
|
||
# 分辨率
|
||
resolution_layout = QHBoxLayout()
|
||
resolution_layout.addWidget(QLabel("分辨率:"))
|
||
resolution_spin = QSpinBox()
|
||
resolution_spin.setRange(16, 2048)
|
||
resolution_spin.setValue(256)
|
||
resolution_spin.setSingleStep(16)
|
||
resolution_layout.addWidget(resolution_spin)
|
||
layout.addLayout(resolution_layout)
|
||
|
||
# 按钮
|
||
button_layout = QHBoxLayout()
|
||
ok_button = QPushButton("创建")
|
||
cancel_button = QPushButton("取消")
|
||
button_layout.addWidget(ok_button)
|
||
button_layout.addWidget(cancel_button)
|
||
layout.addLayout(button_layout)
|
||
|
||
# 连接信号
|
||
ok_button.clicked.connect(dialog.accept)
|
||
cancel_button.clicked.connect(dialog.reject)
|
||
|
||
# 显示对话框
|
||
if dialog.exec_() == QDialog.Accepted:
|
||
width = width_spin.value()
|
||
height = height_spin.value()
|
||
resolution = resolution_spin.value()
|
||
|
||
# 调用世界对象创建地形
|
||
terrain_info = self.world.createFlatTerrain((width, height), resolution)
|
||
if terrain_info:
|
||
QMessageBox.information(self, "成功", "平面地形创建成功!")
|
||
else:
|
||
QMessageBox.warning(self, "错误", "平面地形创建失败!")
|
||
|
||
def onCreateHeightmapTerrain(self):
|
||
"""从高度图创建地形"""
|
||
file_path,_=QFileDialog.getOpenFileName(
|
||
self,
|
||
"选择高度图文件",
|
||
"",
|
||
"图像文件 (*.png *.jpg *.jpeg *.bmp *.tga);;所有文件 (*)"
|
||
)
|
||
|
||
if file_path:
|
||
#创建对话框获取地形参数
|
||
dialog = QDialog(self)
|
||
dialog.setWindowTitle("设置地形参数")
|
||
dialog.setModal(True)
|
||
dialog.resize(300,250)
|
||
|
||
layout = QVBoxLayout(dialog)
|
||
|
||
x_scale_layout = QHBoxLayout()
|
||
x_scale_layout.addWidget(QLabel("X缩放:"))
|
||
x_scale_spin = QDoubleSpinBox()
|
||
x_scale_spin.setRange(0.1,1000)
|
||
x_scale_spin.setValue(0.3)
|
||
x_scale_spin.setSingleStep(10)
|
||
x_scale_layout.addWidget(x_scale_spin)
|
||
layout.addLayout(x_scale_layout)
|
||
|
||
# Y缩放
|
||
y_scale_layout = QHBoxLayout()
|
||
y_scale_layout.addWidget(QLabel("Y缩放:"))
|
||
y_scale_spin = QDoubleSpinBox()
|
||
y_scale_spin.setRange(0.1, 1000)
|
||
y_scale_spin.setValue(0.3)
|
||
y_scale_spin.setSingleStep(10)
|
||
y_scale_layout.addWidget(y_scale_spin)
|
||
layout.addLayout(y_scale_layout)
|
||
|
||
# Z缩放
|
||
z_scale_layout = QHBoxLayout()
|
||
z_scale_layout.addWidget(QLabel("Z缩放:"))
|
||
z_scale_spin = QDoubleSpinBox()
|
||
z_scale_spin.setRange(0.1, 1000)
|
||
z_scale_spin.setValue(50)
|
||
z_scale_spin.setSingleStep(5)
|
||
z_scale_layout.addWidget(z_scale_spin)
|
||
layout.addLayout(z_scale_layout)
|
||
|
||
# 按钮
|
||
button_layout = QHBoxLayout()
|
||
ok_button = QPushButton("创建")
|
||
cancel_button = QPushButton("取消")
|
||
button_layout.addWidget(ok_button)
|
||
button_layout.addWidget(cancel_button)
|
||
layout.addLayout(button_layout)
|
||
|
||
# 连接信号
|
||
ok_button.clicked.connect(dialog.accept)
|
||
cancel_button.clicked.connect(dialog.reject)
|
||
|
||
# 显示对话框
|
||
if dialog.exec_() == QDialog.Accepted:
|
||
x_scale = x_scale_spin.value()
|
||
y_scale = y_scale_spin.value()
|
||
z_scale = z_scale_spin.value()
|
||
|
||
# 调用世界对象创建地形
|
||
terrain_info = self.world.createTerrainFromHeightMap(
|
||
file_path,
|
||
(x_scale, y_scale, z_scale)
|
||
)
|
||
if terrain_info:
|
||
QMessageBox.information(self, "成功", "高度图地形创建成功!")
|
||
else:
|
||
QMessageBox.warning(self, "错误", "高度图地形创建失败!")
|
||
|
||
def onTerrainEditMode(self):
|
||
"""地形编辑模式"""
|
||
# 检查当前是否已经处于地形编辑模式
|
||
if self.world.currentTool == "地形编辑":
|
||
# 退出地形编辑模式
|
||
self.world.setCurrentTool(None)
|
||
self.terrainEditTool.setChecked(False)
|
||
self.terrainEditTool.setText("地形编辑")
|
||
QMessageBox.information(self, "地形编辑", "已退出地形编辑模式")
|
||
else:
|
||
# 进入地形编辑模式
|
||
self.world.setCurrentTool("地形编辑")
|
||
self.terrainEditTool.setChecked(True)
|
||
self.terrainEditTool.setText("退出地形编辑")
|
||
QMessageBox.information(self, "地形编辑",
|
||
"已进入地形编辑模式\n\n使用鼠标左键抬高地形\n使用鼠标右键降低地形")
|
||
def setup_main_window(world):
|
||
"""设置主窗口的便利函数"""
|
||
app = QApplication.instance()
|
||
if app is None:
|
||
app = QApplication(sys.argv)
|
||
|
||
main_window = MainWindow(world)
|
||
main_window.show()
|
||
|
||
return app, main_window |