1
0
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:
Hector 2025-08-21 11:04:52 +08:00
commit c08b4e0350
5 changed files with 295 additions and 82 deletions

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

@ -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("此模型无动画")

View File

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