EG/ui/main_window.py
2025-08-28 17:13:03 +08:00

1454 lines
59 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
主窗口设置模块
负责主窗口的界面构建和事件绑定:
- 菜单栏、工具栏创建
- 停靠窗口设置
- 事件连接和信号处理
"""
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