1
0
forked from Rowland/EG

1.修改保存层级数

This commit is contained in:
陈横 2025-09-15 16:32:23 +08:00
parent 7fd52d3458
commit 63801cfb0a
9 changed files with 1171 additions and 263 deletions

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,6 @@ sys.path.insert(0, render_pipeline_file_path)
icons_path = os.path.join(project_root, "icons")
sys.path.insert(0, icons_path)
# 现在可以导入并运行主程序
if __name__ == "__main__":
args = sys.argv[1:]

View File

@ -104,6 +104,9 @@ class TerrainManager:
print("错误:无法生成有效的地形节点")
return None
terrain_node.setTag("is_scene_element", "1")
terrain_node.setTag("tree_item_type", "TERRAIN_NODE")
node_name = f"Terrain_{os.path.basename(heightmap_path)}_{len(self.terrains)}"
terrain_node.setName(node_name)
@ -235,6 +238,9 @@ class TerrainManager:
print("错误:无法生成有效的平面地形节点")
return None
terrain_node.setTag("is_scene_element", "1")
terrain_node.setTag("tree_item_type", "TERRAIN_NODE")
node_name = f"FlatTerrain_{len(self.terrains)}_{int(time.time() * 1000000) % 10000}"
terrain_node.setName(node_name)

View File

@ -84,6 +84,8 @@ except ImportError:
# 为GUI元素添加标识效仿3D文本方法
image_node.setTag("gui_type", "3d_image")
image_node.setTag("gui_id", f"3d_image_{len(self.gui_elements)}")
image_node.setTag("is_scene_element", "1")
image_node.setTag("tree_item_type", "GUI_3DIMAGE")
if image_path:
image_node.setTag("gui_image_path", image_path)
image_node.setTag("is_gui_element", "1")
@ -189,6 +191,7 @@ class GUIManager:
button.setTag("gui_text", text)
button.setTag("is_gui_element", "1")
button.setTag("is_scene_element", "1") # 确保这个标签被设置
button.setTag("tree_item_type", "GUI_BUTTON")
button.setTag("saved_gui_type", "button") # 添加这个标签以确保兼容性
button.setTag("gui_element_type","button")
button.setTag("created_by_user", "1")
@ -305,6 +308,7 @@ class GUIManager:
label.setTag("gui_id", f"label_{len(self.gui_elements)}")
label.setTag("gui_text", text)
label.setTag("is_gui_element", "1")
label.setTag("tree_item_type", "GUI_LABEL")
label.setTag("is_scene_element", "1")
label.setTag("created_by_user", "1")
label.setTag("gui_parent_type", "gui" if parent_gui_node else "3d")
@ -416,6 +420,7 @@ class GUIManager:
entry.setTag("gui_id", f"entry_{len(self.gui_elements)}")
entry.setTag("gui_placeholder", placeholder)
entry.setTag("is_gui_element", "1")
entry.setTag("tree_item_type", "GUI_ENTRY")
entry.setTag("is_scene_element", "1")
entry.setTag("created_by_user", "1")
entry.setTag("gui_parent_type", "gui" if parent_gui_node else "3d")
@ -549,6 +554,7 @@ class GUIManager:
image_node.setTag("gui_text", f"2D图片_{len(self.gui_elements)}")
image_node.setTag("is_gui_element", "1")
image_node.setTag("is_scene_element", "1")
image_node.setTag("tree_item_type", "GUI_IMAGE")
image_node.setTag("created_by_user", "1")
image_node.setTag("gui_parent_type", "gui" if parent_gui_node else "3d")
image_node.setName(image_name)
@ -700,6 +706,7 @@ class GUIManager:
textNodePath.setTag("gui_text", text)
textNodePath.setTag("is_gui_element", "1")
textNodePath.setTag("is_scene_element", "1")
textNodePath.setTag("tree_item_type", "GUI_3DTEXT")
textNodePath.setTag("created_by_user", "1")
# 添加到GUI元素列表
@ -836,6 +843,7 @@ class GUIManager:
image_node.setTag("gui_image_path", image_path)
image_node.setTag("is_gui_element", "1")
image_node.setTag("is_scene_element", "1")
image_node.setTag("tree_item_type", "GUI_3DIMAGE")
image_node.setTag("created_by_user", "1")
# 添加到GUI元素列表
@ -953,6 +961,7 @@ class GUIManager:
video_screen.setTag("gui_text", f"视频屏幕_{len(self.gui_elements)}")
video_screen.setTag("is_gui_element", "1")
video_screen.setTag("is_scene_element", "1")
video_screen.setTag("tree_item_type", "GUI_VIDEO_SCREEN")
video_screen.setTag("created_by_user", "1")
# 设置视频路径标签
@ -1473,6 +1482,7 @@ class GUIManager:
video_screen.setTag("gui_text", f"2D视频屏幕_{len(self.gui_elements)}")
video_screen.setTag("is_gui_element", "1")
video_screen.setTag("is_scene_element", "1")
video_screen.setTag("tree_item_type", "GUI_2D_VIDEO_SCREEN")
video_screen.setTag("created_by_user", "1")
# 设置视频路径标签
@ -1785,6 +1795,7 @@ class GUIManager:
# 设置标签以便识别和管理
sphere_np.setTag("gui_type", "spherical_video")
sphere_np.setTag("is_gui_element", "1")
sphere_np.setTag("tree_item_type", "GUI_SPHERICAL_VIDEO")
sphere_np.setTag("video_path", video_path or "")
sphere_np.setTag("original_radius", str(radius))
@ -1997,6 +2008,7 @@ class GUIManager:
virtual_screen.setTag("gui_text", text)
virtual_screen.setTag("is_gui_element", "1")
virtual_screen.setTag("is_scene_element", "1")
virtual_screen.setTag("tree_item_type", "GUI_VirtualScreen")
virtual_screen.setTag("created_by_user", "1")
# 添加到GUI元素列表

View File

@ -79,7 +79,7 @@ class ProjectManager:
# 自动保存初始场景
scene_file = os.path.join(scenes_path, "scene.bam")
if self.world.scene_manager.saveScene(scene_file):
if self.world.scene_manager.saveScene(scene_file, project_path):
# 更新配置文件中的场景路径
project_config["scene_file"] = os.path.relpath(scene_file, full_project_path)
with open(config_file, "w", encoding="utf-8") as f:
@ -302,7 +302,7 @@ class ProjectManager:
return False
# 保存场景
if self.world.scene_manager.saveScene(scene_file):
if self.world.scene_manager.saveScene(scene_file, project_path):
# 更新项目配置文件
config_file = os.path.join(project_path, "project.json")
if os.path.exists(config_file):

View File

@ -169,6 +169,7 @@ class SceneManager:
model.setTag("file", model_name)
model.setTag("is_model_root", "1")
model.setTag("is_scene_element", "1")
model.setTag("tree_item_type", "IMPORTED_MODEL_NODE")
# 记录应用的处理选项
if apply_unit_conversion:
@ -724,7 +725,7 @@ class SceneManager:
# ==================== 场景保存和加载 ====================
def saveScene(self, filename):
def saveScene(self, filename, project_path):
"""保存场景到BAM文件 - 完整版支持GUI元素"""
try:
print(f"\n=== 开始保存场景到: {filename} ===")
@ -874,6 +875,7 @@ class SceneManager:
self.world.render.ls()
print("---------------------------------")
self.take_screenshot(project_path)
# 保存场景
success = self.world.render.writeBamFile(Filename.fromOsSpecific(filename))
#success_2d = self.world.render2d.writeBamFile(Filename.fromOsSpecific(filename))
@ -902,6 +904,51 @@ class SceneManager:
traceback.print_exc()
return False
def take_screenshot(self, projectpath):
"""
截图并保存到指定的完整路径
Args:
full_path (str): 完整的文件保存路径包括文件名和扩展名
Returns:
bool: 截图是否成功
"""
try:
from panda3d.core import Filename
import os
print(f"\n=== 截图保存: {projectpath} ===")
# 确保目录存在
directory = os.path.dirname(projectpath)
if directory and not os.path.exists(directory):
os.makedirs(directory)
print(f"创建目录: {directory}")
# 规范化路径
filename = os.path.basename(os.path.normpath(projectpath))
filename = f'{filename}.png'
print(f'project_path: {projectpath}')
print(f'project_name: {filename}')
full_path = os.path.normpath(os.path.join(projectpath, filename))
p3d_filename = Filename.from_os_specific(full_path)
# 使用 Panda3D 的截图功能
success = self.world.win.saveScreenshot(p3d_filename)
if success:
print(f"✅ 成功截图并保存到: {full_path}")
return True
else:
print(f"❌ 截图保存失败: {full_path}")
return False
except Exception as e:
print(f"保存截图时发生错误: {str(e)}")
import traceback
traceback.print_exc()
return False
def loadScene(self, filename):
"""从BAM文件加载场景 - 修复版"""
try:
@ -972,7 +1019,7 @@ class SceneManager:
print("场景加载失败")
return False
# tree_widget.create_model_items(scene)
tree_widget.create_model_items(scene)
# 遍历场景中的所有模型节点
# 用于存储处理后的灯光节点,避免重复处理
processed_lights = []
@ -1226,7 +1273,7 @@ class SceneManager:
scene.removeNode()
# 更新场景树
self.updateSceneTree()
# self.updateSceneTree()
# self._get_tree_widget().create_model_items(scene)
print(f"加载完成GUI元素数量: {len(self.world.gui_elements)}")
@ -1835,6 +1882,7 @@ class SceneManager:
# 设置节点属性和标签
light_np.setTag("light_type", "spot_light")
light_np.setTag("is_scene_element", "1")
light_np.setTag("tree_item_type", "LIGHT_NODE")
light_np.setTag("light_energy", str(light.energy))
light_np.setTag("created_by_user", "1")
@ -1941,6 +1989,7 @@ class SceneManager:
# 设置节点属性和标签
light_np.setTag("light_type", "point_light")
light_np.setTag("is_scene_element", "1")
light_np.setTag("tree_item_type", "LIGHT_NODE")
light_np.setTag("light_energy", str(light.energy))
light_np.setTag("created_by_user", "1")
@ -2555,6 +2604,7 @@ except Exception as e:
# 添加标签以便场景识别和保存
tileset_node.setTag("is_scene_element", "1")
tileset_node.setTag("tree_item_type", "CESIUM_TILESET_NODE")
tileset_node.setTag("element_type", "cesium_tileset")
tileset_node.setTag("tileset_url", tileset_url)
# 使用唯一名称作为文件标识,代替索引

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@ from typing import Hashable
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import (QLabel, QLineEdit, QDoubleSpinBox, QPushButton,
QTreeWidget, QTreeWidgetItem, QMenu, QCheckBox, QComboBox, QHBoxLayout, QWidget,
QVBoxLayout, QGroupBox, QGridLayout, QSpinBox, QFileDialog)
QVBoxLayout, QGroupBox, QGridLayout, QSpinBox, QFileDialog, QMessageBox)
from PyQt5.QtCore import Qt
from deploy_libs.unicodedata import normalize
from direct.actor.Actor import Actor
@ -235,23 +235,23 @@ class PropertyPanelManager:
#材质属性
self._updateTerrainMaterialPanel(terrain_node,terrain_info)
#删除按钮
delete_btn = QPushButton("删除地形")
delete_btn.setStyleSheet("""
QPushButton{
background-color:#ff4444;
color:white;
border:none;
padding:8px;
border-radius:4px;
margin-top:10px
}
QPushButton:hover{
background-color:#ff6666;
}
""")
delete_btn.clicked.connect(lambda:self._deleteTerrain(terrain_info,item))
self._propertyLayout.addWidget(delete_btn)
# #删除按钮
# delete_btn = QPushButton("删除地形")
# delete_btn.setStyleSheet("""
# QPushButton{
# background-color:#ff4444;
# color:white;
# border:none;
# padding:8px;
# border-radius:4px;
# margin-top:10px
# }
# QPushButton:hover{
# background-color:#ff6666;
# }
# """)
# delete_btn.clicked.connect(lambda:self._deleteTerrain(terrain_info,item))
# self._propertyLayout.addWidget(delete_btn)
except Exception as e:
print(f"显示地形属性时出错: {e}")
import traceback
@ -909,23 +909,23 @@ class PropertyPanelManager:
scale_group.setLayout(scale_layout)
self._propertyLayout.addWidget(scale_group)
# 删除按钮
delete_btn = QPushButton("删除 Tileset")
delete_btn.setStyleSheet("""
QPushButton {
background-color: #ff4444;
color: white;
border: none;
padding: 8px;
border-radius: 4px;
margin-top: 10px;
}
QPushButton:hover {
background-color: #ff6666;
}
""")
delete_btn.clicked.connect(lambda: self._deleteCesiumTileset(nodePath, item))
self._propertyLayout.addWidget(delete_btn)
# # 删除按钮
# delete_btn = QPushButton("删除 Tileset")
# delete_btn.setStyleSheet("""
# QPushButton {
# background-color: #ff4444;
# color: white;
# border: none;
# padding: 8px;
# border-radius: 4px;
# margin-top: 10px;
# }
# QPushButton:hover {
# background-color: #ff6666;
# }
# """)
# delete_btn.clicked.connect(lambda: self._deleteCesiumTileset(nodePath, item))
# self._propertyLayout.addWidget(delete_btn)
# 添加弹性空间
self._propertyLayout.addStretch()
@ -1280,8 +1280,6 @@ class PropertyPanelManager:
model.setScale(current_scale.getX(), current_scale.getY(), value)
self.refreshModelValues(model)
def updateGUIPropertyPanel(self, gui_element,item):
"""更新GUI元素属性面板"""
self.clearPropertyPanel()

View File

@ -18,7 +18,7 @@ from PyQt5.QtCore import Qt, QUrl, QMimeData
from PyQt5.QtGui import QDrag, QPainter, QPixmap, QPen, QBrush
from PyQt5.sip import wrapinstance
from direct.showbase.ShowBaseGlobal import aspect2d
from panda3d.core import ModelRoot, NodePath
from panda3d.core import ModelRoot, NodePath, CollisionNode
from QPanda3D.QPanda3DWidget import QPanda3DWidget
from scene import util
@ -30,7 +30,68 @@ class NewProjectDialog(QDialog):
super().__init__(parent)
self.setWindowTitle("新建项目")
self.setMinimumWidth(500)
# 设置对话框样式与主窗口保持一致
self.setStyleSheet("""
QDialog {
background-color: #252538;
color: #e0e0ff;
}
QGroupBox {
background-color: #2d2d44;
border: 1px solid #3a3a4a;
border-radius: 6px;
margin-top: 1ex; /* 保持这个设置 */
color: #e0e0ff;
font-weight: 500;
padding-top: 10px; /* 增加顶部内边距为标题留出空间 */
}
QGroupBox::title {
subline-offset: -2px;
padding: 0 8px;
color: #c0c0e0;
font-weight: 500;
}
QLineEdit {
background-color: #2d2d44;
color: #e0e0ff;
border: 1px solid #3a3a4a;
border-radius: 4px;
padding: 6px;
}
QLineEdit:disabled {
background-color: #1e1e2e;
color: #8888aa;
}
QPushButton {
background-color: #8b5cf6;
color: white;
border: none;
padding: 6px 12px;
border-radius: 4px;
font-weight: 500;
}
QPushButton:hover {
background-color: #7c3aed;
}
QPushButton:pressed {
background-color: #6d28d9;
}
QPushButton:disabled {
background-color: #4c4c6e;
color: #8888aa;
}
QLabel {
color: #e0e0ff;
}
QLabel:disabled {
color: #8888aa;
}
QDialogButtonBox QPushButton {
min-width: 80px;
}
""")
# 创建布局
layout = QVBoxLayout(self)
@ -1251,12 +1312,60 @@ class CustomConsoleDockWidget(QWidget):
self.autoScrollBtn = QPushButton("自动滚动")
self.autoScrollBtn.setCheckable(True)
self.autoScrollBtn.setChecked(True)
self.autoScrollBtn.setStyleSheet("""
QPushButton {
background-color: #2d2d44;
color: #e0e0ff;
border: 1px solid #3a3a4a;
padding: 6px 12px;
border-radius: 4px;
font-weight: 500;
}
QPushButton:checked {
background-color: #8b5cf6;
color: white;
border: 1px solid #7c3aed;
}
QPushButton:hover {
background-color: #3a3a4a;
}
QPushButton:checked:hover {
background-color: #7c3aed;
}
QPushButton:pressed {
background-color: #6d28d9;
}
""")
toolbar.addWidget(self.autoScrollBtn)
# 时间戳开关
self.timestampBtn = QPushButton("显示时间")
self.timestampBtn.setCheckable(True)
self.timestampBtn.setChecked(True)
self.timestampBtn.setStyleSheet("""
QPushButton {
background-color: #2d2d44;
color: #e0e0ff;
border: 1px solid #3a3a4a;
padding: 6px 12px;
border-radius: 4px;
font-weight: 500;
}
QPushButton:checked {
background-color: #8b5cf6;
color: white;
border: 1px solid #7c3aed;
}
QPushButton:hover {
background-color: #3a3a4a;
}
QPushButton:checked:hover {
background-color: #7c3aed;
}
QPushButton:pressed {
background-color: #6d28d9;
}
""")
toolbar.addWidget(self.timestampBtn)
toolbar.addStretch()
@ -1419,34 +1528,54 @@ class CustomTreeWidget(QTreeWidget):
self.original_scales={}
self.setStyleSheet("""
/* 设置折叠状态下带子节点的箭头颜色 */
QTreeWidget::branch:has-children:!open {
color: #8b5cf6; /* 紫色 */
}
/* 设置展开状态下带子节点的箭头颜色 */
QTreeWidget::branch:has-children:open {
color: #9ca3af; /* 灰色,提供状态变化反馈 */
}
/* 鼠标悬停在任意箭头上时颜色变亮 */
QTreeWidget::branch:hover {
color: #a78bfa; /* 亮紫色 */
}
""")
def initData(self):
"""初始化变量"""
# 定义2D GUI元素类型
self.gui_2d_types = {
"GUI_BUTTON", # DirectButton
"GUI_LABEL", # DirectLabel
"GUI_ENTRY", # DirectEntry
"GUI_IMAGE",
"GUI_BUTTON", # GUI 按钮
"GUI_LABEL", # GUI 标签
"GUI_ENTRY", # GUI 输入框
"GUI_IMAGE", # GUI 图片
"GUI_2D_VIDEO_SCREEN", # GUI 2D视频
"GUI_SPHERICAL_VIDEO", # GUI 3D球形视频
"GUI_NODE" # 其他2D GUI容器
}
# 定义3D GUI元素类型
self.gui_3d_types = {
"GUI_3DTEXT", # 3D TextNode
"GUI_3DIMAGE",
"GUI_VIRTUAL_SCREEN" # Virtual Screen
"GUI_3DTEXT", # 3D 文本节点
"GUI_3DIMAGE", # 3D 图片节点
"GUI_VIRTUAL_SCREEN", # 3D视频
"GUI_VirtualScreen" # 3D虚拟视频
}
# 定义3D场景节点类型可以接受3D GUI元素和其他3D场景元素
self.scene_3d_types = {
"SCENE_ROOT",
"SCENE_NODE",
"LIGHT_NODE",
"LIGHT_NODE", # 灯节点
"CAMERA_NODE",
"IMPORTED_MODEL_NODE",
"IMPORTED_MODEL_NODE", # 导入模型节点
"MODEL_NODE",
"TERRAIN_NODE",
"CESIUM_TILESET_NODE"
"TERRAIN_NODE", # 地形节点
"CESIUM_TILESET_NODE" # 3D Tileset
}
# 这是一个最佳实践,它让代码的意图变得非常清晰。
@ -2192,53 +2321,60 @@ class CustomTreeWidget(QTreeWidget):
return top_item
return None
def create_model_items(self, model):
def create_model_items(self, model: NodePath):
"""
此函数保持不变
创建模型项
只寻找模型下一层带有 'is_scene_element' 标签的子节点作为分支的根
然后完整地展示这些分支
"""
if not model:
print("传入的参数model为空")
return
# 创建根节点项
# root_item = QTreeWidgetItem(self)
# root_item.setText(0, model.getName() or "Unnamed Node")
# root_item.setText(1, model.node().getTypeName())
# root_item.setIcon(0, self.item_icons.get('model', self.item_icons['default']))
# 存储NodePath引用以便后续操作
# root_item.setData(0, Qt.UserRole, model)
# 找到场景树的根节点,我们将把模型节点添加到这里
root_item = self._findSceneRoot()
if not root_item:
print("错误:未能找到场景根节点项")
return
# 递归添加子节点
self._add_children_recursive(root_item, model)
# 1. 在模型的第一层子节点中进行筛选
for child_node in model.getChildren():
if child_node.hasTag("is_scene_element"):
print(f"找到带标签的根节点:{child_node.getName()}")
return root_item
# 为这个带标签的节点创建一个树项
child_item = QTreeWidgetItem(root_item)
child_item.setText(0, child_node.getName() or "Unnamed Tagged Node")
child_item.setData(0, Qt.UserRole, child_node)
child_item.setData(0, Qt.UserRole + 1, child_node.getTag("tree_item_type"))
# self._add_node_info(child_item, child_node) # 可选信息
def _add_children_recursive(self, parent_item, node_path: NodePath):
"""递归添加子节点到树项"""
print(f'开始递归添加子节点')
# 获取所有子节点
children = node_path.getChildren()
# 2. 对这个节点的所有后代进行“无条件”递归添加 (但会跳过碰撞体)
self._add_all_children_unconditionally(child_item, child_node)
for i in range(children.getNumPaths()):
child_node: NodePath = children.getPath(i)
def _add_all_children_unconditionally(self, parent_item: QTreeWidgetItem, node_path: NodePath):
"""
此函数已更新
无条件地递归地添加一个节点下的所有子节点但会跳过碰撞节点
"""
for child_node in node_path.getChildren():
# 过滤条件
if not child_node.hasTag("is_scene_element"):
print(f"不存在------------------------{child_node.hasTag('is_scene_element')}")
continue
# 新增:检查节点是否为碰撞节点
if isinstance(child_node.node(), CollisionNode):
# print(f"跳过碰撞节点: {child_node.getName()}") # 用于调试
continue # 如果是,则跳过此节点及其所有子节点
print(f"存在------------------------{child_node.getName()}")
# 创建子项
child_item = QTreeWidgetItem(parent_item)
child_item.setText(0, child_node.getName() or f"Child_{i}")
# 存储NodePath引用
child_item.setText(0, child_node.getName() or "Unnamed Child")
child_item.setData(0, Qt.UserRole, child_node)
child_item.setData(0, Qt.UserRole + 1, child_node.getTag("tree_item_type"))
# self._add_node_info(child_item, child_node) # 可选信息
# 添加额外信息(可选)
# self._add_node_info(child_item, child_node)
# 递归处理子节点的子节点
if child_node.getNumChildren() > 0:
self._add_children_recursive(child_item, child_node)
# 继续无条件地递归
if not child_node.is_empty():
self._add_all_children_unconditionally(child_item, child_node)
# ==================== 辅助方法 ====================
def _findSceneRoot(self):
@ -2269,6 +2405,17 @@ class CustomTreeWidget(QTreeWidget):
def add_node_to_tree_widget(self, node, parent_item, node_type):
"""将node元素添加到树形控件"""
if hasattr(node, 'getTag'):
if node.hasTag('tree_item_type'):
print(f"node0: {node.getName()},{node.getTag('tree_item_type')}")
tree_type = node.getTag('tree_item_type')
else:
node.setTag('tree_item_type', node_type)
else:
print(f"node2: {node.getName()},{node_type}")
tree_type = node_type
# BLACK_LIST 和依赖项导入保持不变
BLACK_LIST = {'', '**', 'temp', 'collision'}
from panda3d.core import CollisionNode, ModelRoot
@ -2283,7 +2430,7 @@ class CustomTreeWidget(QTreeWidget):
nodeItem = QTreeWidgetItem(parentItem, [node.getName()])
nodeItem.setData(0, Qt.UserRole, node)
nodeItem.setData(0, Qt.UserRole + 1, node_type)
nodeItem.setData(0, Qt.UserRole + 1, tree_type)
for child in node.getChildren():
# 递归调用但我们只关心顶级的nodeItem
@ -2301,7 +2448,7 @@ class CustomTreeWidget(QTreeWidget):
node_name = ""
try:
if node_type == "IMPORTED_MODEL_NODE":
if tree_type == "IMPORTED_MODEL_NODE":
# getTag('file') 可能是你自己设置的tag这里假设它存在
node_name = node.getTag("file") if hasattr(node, 'getTag') and node.hasTag("file") else node.getName()
@ -2312,7 +2459,7 @@ class CustomTreeWidget(QTreeWidget):
node_name = node.getName() if hasattr(node, 'getName') else "node"
new_qt_item = QTreeWidgetItem(parent_item, [node_name])
new_qt_item.setData(0, Qt.UserRole, node)
new_qt_item.setData(0, Qt.UserRole + 1, node_type)
new_qt_item.setData(0, Qt.UserRole + 1, tree_type)
# 确保 new_qt_item 成功创建后再继续操作
if new_qt_item: