forked from Rowland/EG
Merge remote-tracking branch 'origin/main_ch_eg' into addRender
# Conflicts: # RenderPipelineFile/config/daytime.yaml # ui/property_panel.py # ui/widgets.py
This commit is contained in:
commit
c08b4e0350
File diff suppressed because one or more lines are too long
@ -161,14 +161,14 @@ class InterfaceManager:
|
||||
cameraItem.setData(0, Qt.UserRole, self.world.cam)
|
||||
print("添加相机节点")
|
||||
|
||||
# 添加模型节点组
|
||||
modelsItem = QTreeWidgetItem(sceneRoot, ['模型'])
|
||||
print(f"模型列表中的模型数量: {len(self.world.models)}")
|
||||
|
||||
# 添加GUI元素节点组
|
||||
guiItem = QTreeWidgetItem(sceneRoot, ['GUI元素'])
|
||||
|
||||
lightItem = QTreeWidgetItem(sceneRoot,['灯光'])
|
||||
# # 添加模型节点组
|
||||
# modelsItem = QTreeWidgetItem(sceneRoot, ['模型'])
|
||||
# print(f"模型列表中的模型数量: {len(self.world.models)}")
|
||||
#
|
||||
# # 添加GUI元素节点组
|
||||
# guiItem = QTreeWidgetItem(sceneRoot, ['GUI元素'])
|
||||
#
|
||||
# lightItem = QTreeWidgetItem(sceneRoot,['灯光'])
|
||||
|
||||
BLACK_LIST = {'','**','temp','collision'}
|
||||
|
||||
@ -207,17 +207,17 @@ class InterfaceManager:
|
||||
# print(f"跳过节点: {child.getName()}")
|
||||
|
||||
for model in self.world.models:
|
||||
addNodeToTree(model, modelsItem,force=True)
|
||||
addNodeToTree(model, sceneRoot,force=True)
|
||||
|
||||
# 添加所有GUI元素
|
||||
for gui in self.world.gui_elements:
|
||||
gui_type = gui.getTag("gui_type") or "unknown"
|
||||
gui_text = gui.getTag("gui_text") or "GUI元素"
|
||||
item = QTreeWidgetItem(guiItem, [f"{gui_type}: {gui_text}"])
|
||||
item = QTreeWidgetItem(sceneRoot, [f"{gui_type}: {gui_text}"])
|
||||
item.setData(0, Qt.UserRole, gui)
|
||||
|
||||
for light in self.world.Spotlight + self.world.Pointlight:
|
||||
addNodeToTree(light, lightItem, force=True)
|
||||
addNodeToTree(light, sceneRoot, force=True)
|
||||
|
||||
# 添加地板节点
|
||||
if hasattr(self.world, 'ground') and self.world.ground:
|
||||
|
||||
@ -92,18 +92,42 @@ class MainWindow(QMainWindow):
|
||||
self.scaleAction = self.toolsMenu.addAction('缩放工具')
|
||||
self.sunsetAction = self.toolsMenu.addAction('光照编辑')
|
||||
self.pluginAction = self.toolsMenu.addAction('图形编辑')
|
||||
|
||||
|
||||
# 创建菜单
|
||||
self.createMenu = menubar.addMenu('创建')
|
||||
self.createEnptyaddAction = self.createMenu.addAction('空对象')
|
||||
self.create3dObjectaddMenu = self.createMenu.addMenu('3D对象')
|
||||
|
||||
self.create3dGUIaddMenu = self.createMenu.addMenu('3D GUI')
|
||||
self.create3DTextAction = self.create3dGUIaddMenu.addAction('3D文本')
|
||||
|
||||
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.createLightaddMenu = self.createMenu.addMenu('光源')
|
||||
self.createSpotLightAction = self.createLightaddMenu.addAction('聚光灯')
|
||||
self.createPointLightAction = self.createLightaddMenu.addAction('点光源')
|
||||
|
||||
# GUI菜单
|
||||
self.guiMenu = menubar.addMenu('GUI')
|
||||
self.guiEditModeAction = self.guiMenu.addAction('进入GUI编辑模式')
|
||||
self.guiMenu.addSeparator()
|
||||
self.createButtonAction = self.guiMenu.addAction('创建按钮')
|
||||
self.createLabelAction = self.guiMenu.addAction('创建标签')
|
||||
self.createEntryAction = self.guiMenu.addAction('创建输入框')
|
||||
# self.createButtonAction = self.guiMenu.addAction('创建按钮')
|
||||
# self.createLabelAction = self.guiMenu.addAction('创建标签')
|
||||
# self.createEntryAction = self.guiMenu.addAction('创建输入框')
|
||||
self.guiMenu.addAction(self.createButtonAction)
|
||||
self.guiMenu.addAction(self.createLabelAction)
|
||||
self.guiMenu.addAction(self.createEntryAction)
|
||||
self.guiMenu.addSeparator()
|
||||
self.create3DTextAction = self.guiMenu.addAction('创建3D文本')
|
||||
self.createVirtualScreenAction = self.guiMenu.addAction('创建虚拟屏幕')
|
||||
|
||||
# self.create3DTextAction = self.guiMenu.addAction('创建3D文本')
|
||||
self.guiMenu.addAction(self.create3DTextAction)
|
||||
# self.createVirtualScreenAction = self.guiMenu.addAction('创建虚拟屏幕')
|
||||
self.guiMenu.addAction(self.createVirtualScreenAction)
|
||||
|
||||
# 脚本菜单
|
||||
self.scriptMenu = menubar.addMenu('脚本')
|
||||
self.createScriptAction = self.scriptMenu.addAction('创建脚本...')
|
||||
@ -131,6 +155,7 @@ class MainWindow(QMainWindow):
|
||||
# self.leftDock.setMinimumWidth(300)
|
||||
self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, self.leftDock)
|
||||
|
||||
|
||||
# 创建右侧停靠窗口(属性窗口)
|
||||
self.rightDock = QDockWidget("属性", self)
|
||||
self.rightDock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)
|
||||
@ -388,7 +413,11 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# 连接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())
|
||||
# 连接GUI创建按钮事件
|
||||
self.createButtonAction.triggered.connect(lambda: self.world.createGUIButton())
|
||||
self.createLabelAction.triggered.connect(lambda: self.world.createGUILabel())
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
from collections import deque
|
||||
from traceback import print_exc
|
||||
from types import new_class
|
||||
from typing import Hashable
|
||||
@ -22,6 +21,25 @@ class PropertyPanelManager:
|
||||
self._propertyLayout = None
|
||||
self._actor_cache={}
|
||||
|
||||
# 定义紧凑样式
|
||||
self.compact_style = """
|
||||
QDoubleSpinBox {
|
||||
min-width: 45px;
|
||||
}
|
||||
QPushButton {
|
||||
min-width: 10px;
|
||||
}
|
||||
QComboBox {
|
||||
min-width: 60px;
|
||||
}
|
||||
QLineEdit {
|
||||
min-width: 60px;
|
||||
}
|
||||
QCheckBox {
|
||||
min-width: 20px;
|
||||
}
|
||||
"""
|
||||
|
||||
def setPropertyLayout(self, layout):
|
||||
"""设置属性面板布局引用"""
|
||||
print("开始设置属性布局")
|
||||
@ -69,6 +87,10 @@ class PropertyPanelManager:
|
||||
|
||||
self.clearPropertyPanel()
|
||||
|
||||
# 应用紧凑样式到属性面板容器
|
||||
if self._propertyLayout.parent():
|
||||
self._propertyLayout.parent().setStyleSheet(self.compact_style)
|
||||
|
||||
itemText = item.text(0)
|
||||
|
||||
# 如果点击的是场景根节点,显示提示信息
|
||||
@ -4265,6 +4287,14 @@ class PropertyPanelManager:
|
||||
# 忽略 Actor 加载错误,很多模型都不是角色动画
|
||||
print(f"[信息] 该模型不包含骨骼动画: {actor_error}")
|
||||
|
||||
# 只有在没有骨骼动画时才检测非骨骼动画
|
||||
if not has_skeletal_anim:
|
||||
non_skeletal_anims = self._detectNonSkeletalAnimations(origin_model)
|
||||
if non_skeletal_anims and self._validateNonSkeletalAnimations(origin_model, non_skeletal_anims):
|
||||
self._buildNonSkeletalUI(origin_model, non_skeletal_anims, animation_layout)
|
||||
has_animation = True
|
||||
print(f"[信息] 检测到非骨骼动画: {list(non_skeletal_anims.keys())}")
|
||||
|
||||
# 如果都没有动画
|
||||
if not has_animation:
|
||||
no_anim_label = QLabel("此模型无动画")
|
||||
|
||||
276
ui/widgets.py
276
ui/widgets.py
@ -15,7 +15,7 @@ from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QGroupBox, QHBoxLayout,
|
||||
QTreeView, QTreeWidget, QTreeWidgetItem, QWidget,
|
||||
QFileDialog, QMessageBox, QAbstractItemView)
|
||||
from PyQt5.QtCore import Qt, QUrl
|
||||
from PyQt5.QtGui import QDrag, QPainter, QPixmap
|
||||
from PyQt5.QtGui import QDrag, QPainter, QPixmap, QPen, QBrush
|
||||
from PyQt5.sip import wrapinstance
|
||||
from QPanda3D.QPanda3DWidget import QPanda3DWidget
|
||||
|
||||
@ -319,34 +319,31 @@ class CustomTreeWidget(QTreeWidget):
|
||||
self.setHeaderHidden(True)
|
||||
# 启用多选和拖拽
|
||||
self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||
self.setDropIndicatorShown(True)
|
||||
self.setDropIndicatorShown(True) # 启用拖放指示线
|
||||
|
||||
def setupDragDrop(self):
|
||||
"""设置拖拽功能"""
|
||||
# 使用自定义拖拽模式
|
||||
self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove) # 或者使用 DragDrop
|
||||
self.setDefaultDropAction(Qt.DropAction.MoveAction)
|
||||
|
||||
self.setDragEnabled(True)
|
||||
self.setAcceptDrops(True)
|
||||
|
||||
def dropEvent(self, event):
|
||||
"""处理拖放事件"""
|
||||
# 获取拖动的项和目标项
|
||||
dragged_item = self.currentItem()
|
||||
target_item = self.itemAt(event.pos())
|
||||
|
||||
|
||||
if not dragged_item or not target_item:
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
# 获取节点引用
|
||||
|
||||
if not self.isValidParentChild(dragged_item, target_item):
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
dragged_node = dragged_item.data(0, Qt.UserRole)
|
||||
amtarget_node = target_item.data(0, Qt.UserRole)
|
||||
|
||||
# 如果目标是模型根节点,使用 render 作为新父节点
|
||||
if target_item.text(0) == "模型":
|
||||
target_node = self.world.render
|
||||
else:
|
||||
target_node = target_item.data(0, Qt.UserRole)
|
||||
|
||||
target_node = target_item.data(0, Qt.UserRole)
|
||||
|
||||
if not dragged_node or not target_node:
|
||||
event.ignore()
|
||||
return
|
||||
@ -372,65 +369,222 @@ class CustomTreeWidget(QTreeWidget):
|
||||
else:
|
||||
event.ignore()
|
||||
|
||||
|
||||
print(f"dragged_node: {dragged_node}, target_node: {target_node}")
|
||||
|
||||
# 记录拖拽前的父节点
|
||||
old_parent_item = dragged_item.parent()
|
||||
old_parent_node = old_parent_item.data(0, Qt.UserRole) if old_parent_item else None
|
||||
|
||||
# 执行Qt默认拖拽
|
||||
super().dropEvent(event)
|
||||
|
||||
# 检查拖拽后的父节点
|
||||
new_parent_item = dragged_item.parent()
|
||||
new_parent_node = new_parent_item.data(0, Qt.UserRole) if new_parent_item else None
|
||||
|
||||
# 同步Panda3D场景图的父子关系
|
||||
try:
|
||||
# 检查是否是跨层级拖拽(父节点发生变化)
|
||||
if old_parent_node != new_parent_node:
|
||||
print(f"跨层级拖拽:从 {old_parent_node} 移动到 {new_parent_node}")
|
||||
|
||||
# 保存世界坐标位置
|
||||
world_pos = dragged_node.getPos(self.world.render)
|
||||
world_hpr = dragged_node.getHpr(self.world.render)
|
||||
world_scale = dragged_node.getScale(self.world.render)
|
||||
|
||||
# 重新父化到新的父节点
|
||||
if new_parent_node:
|
||||
dragged_node.reparentTo(new_parent_node)
|
||||
else:
|
||||
# 如果新父节点为None,重新父化到render
|
||||
dragged_node.reparentTo(self.world.render)
|
||||
|
||||
# 恢复世界坐标位置
|
||||
dragged_node.setPos(self.world.render, world_pos)
|
||||
dragged_node.setHpr(self.world.render, world_hpr)
|
||||
dragged_node.setScale(self.world.render, world_scale)
|
||||
|
||||
print(f"✅ Panda3D父子关系已更新")
|
||||
else:
|
||||
print(f"同层级移动:父节点未变化,跳过Panda3D重新父化")
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ 同步Panda3D场景图失败: {e}")
|
||||
# 不影响Qt树的更新,继续执行
|
||||
|
||||
# 事后验证:确保节点仍在"场景"根节点下
|
||||
self._ensureUnderSceneRoot(dragged_item)
|
||||
|
||||
event.accept()
|
||||
|
||||
# try:
|
||||
# world_pos = dragged_node.getPos(self.world.render)
|
||||
#
|
||||
# parent_of_dragged = dragged_node.getParent()
|
||||
# target_node.wrtReparentTo(parent_of_dragged)
|
||||
#
|
||||
# # 拖动节点到目标节点下
|
||||
# dragged_node.wrtReparentTo(target_node)
|
||||
# dragged_node.setPos(self.world.render, world_pos)
|
||||
#
|
||||
# # 更新 Qt 树控件
|
||||
# super().dropEvent(event)
|
||||
#
|
||||
# # 更新属性面板
|
||||
# self.world.updatePropertyPanel(dragged_item)
|
||||
#
|
||||
# event.accept()
|
||||
#
|
||||
# except Exception as e:
|
||||
# print(f"重设父节点失败: {e}")
|
||||
# event.ignore()
|
||||
|
||||
def _ensureUnderSceneRoot(self, item):
|
||||
"""确保节点在场景根节点下,如果不是则自动修正"""
|
||||
if not item:
|
||||
return
|
||||
|
||||
# 检查是否成为了顶级节点
|
||||
if not item.parent():
|
||||
# 如果节点名称不是"场景",说明意外成为了顶级节点
|
||||
if item.text(0) != "场景":
|
||||
print(f"⚠️ 检测到节点 {item.text(0)} 意外成为顶级节点,正在修正...")
|
||||
|
||||
# 找到场景根节点
|
||||
scene_root = None
|
||||
for i in range(self.topLevelItemCount()):
|
||||
top_item = self.topLevelItem(i)
|
||||
if top_item.text(0) == "场景":
|
||||
scene_root = top_item
|
||||
break
|
||||
|
||||
if scene_root:
|
||||
# 将节点移回场景根节点下
|
||||
self.takeTopLevelItem(self.indexOfTopLevelItem(item))
|
||||
scene_root.addChild(item)
|
||||
print(f"✅ 已将节点 {item.text(0)} 移回场景根节点下")
|
||||
|
||||
|
||||
def isValidParentChild(self, dragged_item, target_item):
|
||||
"""检查是否是有效的父子关系"""
|
||||
# 不能拖放到自己上
|
||||
"""检查是否是有效的父子关系(防止循环)"""
|
||||
|
||||
# 1. 禁止拖放到自身
|
||||
if dragged_item == target_item:
|
||||
return False
|
||||
|
||||
# 不能拖放到自己的子节点上
|
||||
parent = target_item
|
||||
while parent:
|
||||
if parent == dragged_item:
|
||||
return False
|
||||
parent = parent.parent()
|
||||
|
||||
# 检查目标项
|
||||
if target_item.text(0) == "场景":
|
||||
return False # 不能拖放到场景根节点
|
||||
|
||||
# 允许拖放到模型根节点或其他模型节点
|
||||
if target_item.text(0) == "模型":
|
||||
return True
|
||||
|
||||
# 检查目标项的父节点
|
||||
target_parent = target_item.parent()
|
||||
if not target_parent:
|
||||
|
||||
# 2. 禁止拖到根节点之外(根节点本身除外)
|
||||
target_root = self._getRootNode(target_item)
|
||||
if target_root != "场景":
|
||||
print(f"❌ 目标节点 {target_item.text(0)} 不在场景下")
|
||||
return False
|
||||
|
||||
# 允许在模型节点下的任何位置调整父子关系
|
||||
while target_parent:
|
||||
if target_parent.text(0) == "模型":
|
||||
return True
|
||||
target_parent = target_parent.parent()
|
||||
|
||||
return False
|
||||
|
||||
|
||||
# 3. 禁止拖拽"场景"根节点
|
||||
dragged_root = self._getRootNode(dragged_item)
|
||||
if dragged_item.text(0) == "场景" or dragged_root != "场景":
|
||||
print(f"❌ 禁止拖拽场景根节点或根节点外的节点")
|
||||
return False
|
||||
|
||||
# 4. Qt 树循环检查
|
||||
current = target_item
|
||||
while current:
|
||||
if current == dragged_item:
|
||||
print(f"❌ Qt 树检测:{target_item.text(0)} 是 {dragged_item.text(0)} 的后代")
|
||||
return False
|
||||
current = current.parent()
|
||||
|
||||
return True
|
||||
|
||||
def _getRootNode(self, item):
|
||||
"""获取树中节点的根节点文本"""
|
||||
current = item
|
||||
while current.parent():
|
||||
current = current.parent()
|
||||
return current.text(0)
|
||||
|
||||
def dragEnterEvent(self, event):
|
||||
"""处理拖入事件"""
|
||||
if event.source() == self:
|
||||
event.accept()
|
||||
else:
|
||||
event.ignore()
|
||||
|
||||
|
||||
def dragMoveEvent(self, event):
|
||||
"""处理拖动事件"""
|
||||
if event.source() == self:
|
||||
event.accept()
|
||||
else:
|
||||
if event.source() != self:
|
||||
event.ignore()
|
||||
|
||||
return
|
||||
|
||||
# 获取当前拖拽的项目和目标位置
|
||||
target_item = self.itemAt(event.pos())
|
||||
selected_items = self.selectedItems()
|
||||
|
||||
# 检查是否拖拽到多选区域内的项目
|
||||
if target_item and target_item in selected_items:
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
# 检查其他禁止条件
|
||||
if target_item and selected_items:
|
||||
for dragged_item in selected_items:
|
||||
if not self.isValidParentChild(dragged_item, target_item):
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
super().dragMoveEvent(event)
|
||||
event.accept()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
"""处理键盘按键事件"""
|
||||
if event.key() == Qt.Key_Delete:
|
||||
currentItem = self.currentItem()
|
||||
if currentItem and currentItem.parent():
|
||||
# 检查是否是模型节点或其子节点
|
||||
if self.world.interface_manager.isModelOrChild(currentItem):
|
||||
nodePath = currentItem.data(0, Qt.UserRole)
|
||||
if nodePath:
|
||||
print("正在删除节点...")
|
||||
self.world.interface_manager.deleteNode(nodePath, currentItem)
|
||||
print("删除完成")
|
||||
# currentItem = self.currentItem()
|
||||
# if currentItem and currentItem.parent():
|
||||
# # 检查是否是模型节点或其子节点
|
||||
# if self.world.interface_manager.isModelOrChild(currentItem):
|
||||
# nodePath = currentItem.data(0, Qt.UserRole)
|
||||
# if nodePath:
|
||||
# print("正在删除节点...")
|
||||
# self.world.interface_manager.deleteNode(nodePath, currentItem)
|
||||
# print("删除完成")
|
||||
selected_items = self.selectedItems()
|
||||
if selected_items:
|
||||
# 执行删除操作
|
||||
self.delete_items(selected_items)
|
||||
else:
|
||||
# 没有选中任何项目,执行默认操作
|
||||
super().keyPressEvent(event)
|
||||
else:
|
||||
super().keyPressEvent(event)
|
||||
super().keyPressEvent(event)
|
||||
|
||||
def delete_items(self, selected_items):
|
||||
"""删除选中的项目"""
|
||||
if not selected_items:
|
||||
return
|
||||
|
||||
# 准备确认对话框的内容
|
||||
item_count = len(selected_items)
|
||||
if item_count == 1:
|
||||
item_names = f'"{selected_items[0].text(0)}"'
|
||||
title = "确认删除"
|
||||
message = f"确定要删除节点 {item_names} 吗?"
|
||||
else:
|
||||
item_names = "、".join([f'"{item.text(0)}"' for item in selected_items[:3]])
|
||||
if item_count > 3:
|
||||
item_names += f" 等 {item_count} 个节点"
|
||||
title = "确认批量删除"
|
||||
message = f"确定要删除以下 {item_count} 个节点吗?\n\n{item_names}"
|
||||
|
||||
# 创建确认对话框
|
||||
reply = QMessageBox.question(
|
||||
self,
|
||||
title,
|
||||
message,
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.No # 默认选择"取消",防止误删
|
||||
)
|
||||
|
||||
# 只有用户确认后才执行删除
|
||||
if reply == QMessageBox.Yes:
|
||||
pass
|
||||
print(f"✅ 已删除 {item_count} 个节点")
|
||||
Loading…
Reference in New Issue
Block a user