forked from Rowland/EG
剪切复制粘贴
This commit is contained in:
parent
b5545b4b60
commit
ec21df8f54
File diff suppressed because one or more lines are too long
BIN
Resources/models/DancingTwerk.glb
Normal file
BIN
Resources/models/DancingTwerk.glb
Normal file
Binary file not shown.
BIN
Resources/models/Haqijingzhu.glb
Normal file
BIN
Resources/models/Haqijingzhu.glb
Normal file
Binary file not shown.
BIN
Resources/models/JQB_auto_converted.glb
Normal file
BIN
Resources/models/JQB_auto_converted.glb
Normal file
Binary file not shown.
@ -145,7 +145,7 @@ class InfoPanelManager(DirectObject):
|
||||
text_scale=0.045,
|
||||
text_fg=content_color,
|
||||
text_align=TextNode.ALeft,
|
||||
text_wordwrap=500, # 设置一个非常大的值,几乎不自动换行
|
||||
text_wordwrap=0, # 设置一个非常大的值,几乎不自动换行
|
||||
pos=(-size[0] / 2 + 0.03, 0, size[1] / 2 - title_bar_height - 0.05),
|
||||
parent=panel_node,
|
||||
relief=None,
|
||||
@ -529,9 +529,7 @@ class InfoPanelManager(DirectObject):
|
||||
-size[0] / 2 + 0.03, 0, size[1] / 2 - title_bar_height - 0.05
|
||||
)
|
||||
|
||||
# 设置一个非常大的换行值,几乎不自动换行
|
||||
panel_data['content_label']['text_wordwrap'] = 500
|
||||
print(f"更新面板换行: 设置为500(几乎不换行)")
|
||||
panel_data['content_label']['text_wordwrap'] = 0
|
||||
|
||||
# 如果有背景图片,也需要更新其大小
|
||||
if 'bg_image' in panel_data and panel_data['bg_image']:
|
||||
@ -575,8 +573,9 @@ class InfoPanelManager(DirectObject):
|
||||
if 'content_size' in properties:
|
||||
panel_data['content_label']['text_scale'] = properties['content_size']
|
||||
props['content_size'] = properties['content_size']
|
||||
current_size = props.get('size',(1.0,0.6))
|
||||
# 当字体大小改变时,仍然保持较大的换行值
|
||||
panel_data['content_label']['text_wordwrap'] = 500
|
||||
panel_data['content_label']['text_wordwrap'] = 0
|
||||
|
||||
# 更新背景图片
|
||||
if 'bg_image' in properties:
|
||||
|
||||
@ -2761,4 +2761,23 @@ class SelectionSystem:
|
||||
|
||||
# 清理其他资源
|
||||
self.clearSelectionBox()
|
||||
self.clearGizmo()
|
||||
self.clearGizmo()
|
||||
def clearSelection(self):
|
||||
"""清除当前选择"""
|
||||
try:
|
||||
self.selectedNode = None
|
||||
self.selectedObject = None
|
||||
self.clearSelectionBox()
|
||||
self.clearGizmo()
|
||||
|
||||
# 清除树形控件中的选择
|
||||
if (hasattr(self.world, 'interface_manager') and
|
||||
self.world.interface_manager and
|
||||
hasattr(self.world.interface_manager, 'treeWidget') and
|
||||
self.world.interface_manager.treeWidget):
|
||||
self.world.interface_manager.treeWidget.setCurrentItem(None)
|
||||
|
||||
print("已清除选择")
|
||||
except Exception as e:
|
||||
print(f"清除选择失败: {e}")
|
||||
|
||||
|
||||
@ -24,80 +24,80 @@ except ImportError:
|
||||
WEB_ENGINE_AVAILABLE = False
|
||||
print("⚠️ QtWebEngineWidgets 不可用,Cesium 集成功能将被禁用")
|
||||
|
||||
def createGUI3DImage(self, pos=(0, 0, 0), image_path=None, size=1.0):
|
||||
from panda3d.core import CardMaker, Material, LColor,TransparencyAttrib
|
||||
|
||||
# 参数类型检查和转换
|
||||
if isinstance(size, (list, tuple)):
|
||||
if len(size) >= 2:
|
||||
x_size, y_size = float(size[0]), float(size[1])
|
||||
else:
|
||||
x_size = y_size = float(size[0]) if size else 1.0
|
||||
else:
|
||||
x_size = y_size = float(size)
|
||||
|
||||
# 创建卡片
|
||||
cm = CardMaker('gui_3d_image')
|
||||
cm.setFrame(-x_size/2, x_size/2, -y_size/2, y_size/2)
|
||||
|
||||
# 创建3D图像节点
|
||||
image_node = self.world.render.attachNewNode(cm.generate())
|
||||
image_node.setPos(*pos)
|
||||
|
||||
# 为3D图像创建独立的材质
|
||||
material = Material(f"image-material-{len(self.gui_elements)}")
|
||||
material.setBaseColor(LColor(1, 1, 1, 1))
|
||||
material.setDiffuse(LColor(1, 1, 1, 1))
|
||||
material.setAmbient(LColor(0.5, 0.5, 0.5, 1))
|
||||
material.setSpecular(LColor(0.1, 0.1, 0.1, 1.0))
|
||||
material.setShininess(10.0)
|
||||
material.setEmission(LColor(0, 0, 0, 1)) # 无自发光
|
||||
image_node.setMaterial(material, 1)
|
||||
|
||||
image_node.setTransparency(TransparencyAttrib.MAlpha)
|
||||
|
||||
# 如果提供了图像路径,则加载纹理
|
||||
if image_path:
|
||||
self.update3DImageTexture(image_node, image_path)
|
||||
|
||||
# 应用PBR效果(如果可用)
|
||||
try:
|
||||
if hasattr(self, 'render_pipeline') and self.render_pipeline:
|
||||
self.render_pipeline.set_effect(
|
||||
image_node,
|
||||
"effects/default.yaml",
|
||||
{
|
||||
"normal_mapping": True,
|
||||
"render_gbuffer": True,
|
||||
"alpha_testing": False,
|
||||
"parallax_mapping": False,
|
||||
"render_shadow": False,
|
||||
"render_envmap": True,
|
||||
"disable_children_effects": True
|
||||
},
|
||||
50
|
||||
)
|
||||
print("✓ GUI 3D图像PBR效果已应用")
|
||||
except Exception as e:
|
||||
print(f"⚠️ GUI 3D图像PBR效果应用失败: {e}")
|
||||
|
||||
# 为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")
|
||||
|
||||
self.gui_elements.append(image_node)
|
||||
|
||||
# 更新场景树
|
||||
if hasattr(self.world, 'updateSceneTree'):
|
||||
self.world.updateSceneTree()
|
||||
|
||||
print(f"✓ 3D图像创建完成: {image_path or '无纹理'} (世界位置: {pos})")
|
||||
return image_node
|
||||
# def createGUI3DImage(self, pos=(0, 0, 0), image_path=None, size=1.0):
|
||||
# from panda3d.core import CardMaker, Material, LColor,TransparencyAttrib
|
||||
#
|
||||
# # 参数类型检查和转换
|
||||
# if isinstance(size, (list, tuple)):
|
||||
# if len(size) >= 2:
|
||||
# x_size, y_size = float(size[0]), float(size[1])
|
||||
# else:
|
||||
# x_size = y_size = float(size[0]) if size else 1.0
|
||||
# else:
|
||||
# x_size = y_size = float(size)
|
||||
#
|
||||
# # 创建卡片
|
||||
# cm = CardMaker('gui_3d_image')
|
||||
# cm.setFrame(-x_size/2, x_size/2, -y_size/2, y_size/2)
|
||||
#
|
||||
# # 创建3D图像节点
|
||||
# image_node = self.world.render.attachNewNode(cm.generate())
|
||||
# image_node.setPos(*pos)
|
||||
#
|
||||
# # 为3D图像创建独立的材质
|
||||
# material = Material(f"image-material-{len(self.gui_elements)}")
|
||||
# material.setBaseColor(LColor(1, 1, 1, 1))
|
||||
# material.setDiffuse(LColor(1, 1, 1, 1))
|
||||
# material.setAmbient(LColor(0.5, 0.5, 0.5, 1))
|
||||
# material.setSpecular(LColor(0.1, 0.1, 0.1, 1.0))
|
||||
# material.setShininess(10.0)
|
||||
# material.setEmission(LColor(0, 0, 0, 1)) # 无自发光
|
||||
# image_node.setMaterial(material, 1)
|
||||
#
|
||||
# image_node.setTransparency(TransparencyAttrib.MAlpha)
|
||||
#
|
||||
# # 如果提供了图像路径,则加载纹理
|
||||
# if image_path:
|
||||
# self.update3DImageTexture(image_node, image_path)
|
||||
#
|
||||
# # 应用PBR效果(如果可用)
|
||||
# try:
|
||||
# if hasattr(self, 'render_pipeline') and self.render_pipeline:
|
||||
# self.render_pipeline.set_effect(
|
||||
# image_node,
|
||||
# "effects/default.yaml",
|
||||
# {
|
||||
# "normal_mapping": True,
|
||||
# "render_gbuffer": True,
|
||||
# "alpha_testing": False,
|
||||
# "parallax_mapping": False,
|
||||
# "render_shadow": False,
|
||||
# "render_envmap": True,
|
||||
# "disable_children_effects": True
|
||||
# },
|
||||
# 50
|
||||
# )
|
||||
# print("✓ GUI 3D图像PBR效果已应用")
|
||||
# except Exception as e:
|
||||
# print(f"⚠️ GUI 3D图像PBR效果应用失败: {e}")
|
||||
#
|
||||
# # 为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")
|
||||
#
|
||||
# self.gui_elements.append(image_node)
|
||||
#
|
||||
# # 更新场景树
|
||||
# if hasattr(self.world, 'updateSceneTree'):
|
||||
# self.world.updateSceneTree()
|
||||
#
|
||||
# print(f"✓ 3D图像创建完成: {image_path or '无纹理'} (世界位置: {pos})")
|
||||
# return image_node
|
||||
|
||||
class GUIManager:
|
||||
"""GUI元素管理系统类"""
|
||||
|
||||
16
main.py
16
main.py
@ -895,6 +895,22 @@ class MyWorld(CoreWorld):
|
||||
except Exception as e:
|
||||
print(f"创建默认自动朝向巡检路线失败: {e}")
|
||||
|
||||
def _serializeNode(self, node):
|
||||
"""序列化节点数据"""
|
||||
try:
|
||||
return self.world.scene_manager.serializeNode(node)
|
||||
except Exception as e:
|
||||
print(f"序列化节点失败: {e}")
|
||||
return None
|
||||
|
||||
def _deserializeNode(self, node_data, parent_node):
|
||||
"""反序列化节点数据"""
|
||||
try:
|
||||
return self.world.scene_manager.deserializeNode(node_data, parent_node)
|
||||
except Exception as e:
|
||||
print(f"反序列化节点失败: {e}")
|
||||
return None
|
||||
|
||||
|
||||
# ==================== 项目管理功能代理 ====================
|
||||
# 以下函数代理到project_manager模块的对应功能
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
"""
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QTreeWidgetItem
|
||||
@ -165,16 +166,16 @@ class SceneManager:
|
||||
model.setTag("converted_to_glb", "true")
|
||||
|
||||
# 特殊处理FBX模型
|
||||
if filepath.lower().endswith('.fbx'):
|
||||
print("检测到FBX模型,应用特殊处理...")
|
||||
|
||||
# 将模型缩放设置为原来的1/100
|
||||
model.setScale(0.01)
|
||||
print("设置模型缩放为 0.01 (原始大小的1/100)")
|
||||
|
||||
# 设置模型旋转为 (0, 90, 0)
|
||||
model.setHpr(0, 90, 0)
|
||||
print("设置模型旋转为 (0, 90, 0)")
|
||||
# if filepath.lower().endswith('.fbx'):
|
||||
# print("检测到FBX模型,应用特殊处理...")
|
||||
#
|
||||
# # 将模型缩放设置为原来的1/100
|
||||
# model.setScale(0.01)
|
||||
# print("设置模型缩放为 0.01 (原始大小的1/100)")
|
||||
#
|
||||
# # 设置模型旋转为 (0, 90, 0)
|
||||
# model.setHpr(0, 90, 0)
|
||||
# print("设置模型旋转为 (0, 90, 0)")
|
||||
|
||||
# # 可选的单位转换(主要针对FBX
|
||||
# if apply_unit_conversion and filepath.lower().endswith('.fbx'):
|
||||
@ -2294,6 +2295,9 @@ class SceneManager:
|
||||
light.casts_shadows = True
|
||||
light.shadow_map_resolution = 256
|
||||
|
||||
light_pos = light_node.getPos()
|
||||
light.setPos(light_pos)
|
||||
|
||||
# 添加到渲染管线
|
||||
render_pipeline = get_render_pipeline()
|
||||
render_pipeline.add_light(light)
|
||||
@ -3456,4 +3460,729 @@ except Exception as e:
|
||||
return None
|
||||
|
||||
|
||||
def serializeNode(self, node):
|
||||
"""序列化节点为字典数据"""
|
||||
try:
|
||||
node_data = {
|
||||
'name': node.getName(),
|
||||
'type': type(node.node()).__name__,
|
||||
'pos': (node.getX(), node.getY(), node.getZ()),
|
||||
'hpr': (node.getH(), node.getP(), node.getR()),
|
||||
'scale': (node.getSx(), node.getSy(), node.getSz()),
|
||||
'tags': {},
|
||||
'children': []
|
||||
}
|
||||
|
||||
# 保存所有标签
|
||||
for tag_key in node.getTagKeys():
|
||||
node_data['tags'][tag_key] = node.getTag(tag_key)
|
||||
|
||||
# 特殊处理不同类型的节点
|
||||
if hasattr(node.node(), 'getClassType'):
|
||||
node_class = node.node().getClassType().getName()
|
||||
node_data['node_class'] = node_class
|
||||
|
||||
# 递归序列化子节点
|
||||
for child in node.getChildren():
|
||||
# 跳过辅助节点
|
||||
if not child.getName().startswith(('gizmo', 'selectionBox', 'grid')):
|
||||
child_data = self.serializeNode(child)
|
||||
if child_data:
|
||||
node_data['children'].append(child_data)
|
||||
|
||||
return node_data
|
||||
|
||||
except Exception as e:
|
||||
print(f"序列化节点 {node.getName()} 失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def deserializeNode(self, node_data, parent_node):
|
||||
"""从字典数据反序列化节点"""
|
||||
try:
|
||||
# 创建新节点
|
||||
node_name = node_data.get('name', 'node')
|
||||
new_node = parent_node.attachNewNode(node_name)
|
||||
|
||||
# 设置变换
|
||||
pos = node_data.get('pos', (0, 0, 0))
|
||||
hpr = node_data.get('hpr', (0, 0, 0))
|
||||
scale = node_data.get('scale', (1, 1, 1))
|
||||
|
||||
new_node.setPos(*pos)
|
||||
new_node.setHpr(*hpr)
|
||||
new_node.setScale(*scale)
|
||||
|
||||
# 恢复标签
|
||||
for tag_key, tag_value in node_data.get('tags', {}).items():
|
||||
new_node.setTag(tag_key, tag_value)
|
||||
|
||||
# 根据节点类型进行特殊处理
|
||||
node_type = node_data.get('type', '')
|
||||
node_class = node_data.get('node_class', '')
|
||||
|
||||
# 特殊处理光源节点
|
||||
if 'light_type' in node_data.get('tags', {}):
|
||||
light_type = node_data['tags']['light_type']
|
||||
if light_type == 'spot_light':
|
||||
self._recreateSpotLight(new_node)
|
||||
elif light_type == 'point_light':
|
||||
self._recreatePointLight(new_node)
|
||||
|
||||
# 递归创建子节点
|
||||
for child_data in node_data.get('children', []):
|
||||
self.deserializeNode(child_data, new_node)
|
||||
|
||||
return new_node
|
||||
|
||||
except Exception as e:
|
||||
print(f"反序列化节点 {node_data.get('name', 'unknown')} 失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def serializeNodeForCopy(self, node):
|
||||
"""序列化节点用于复制操作,完整保存视觉属性"""
|
||||
try:
|
||||
if not node or node.isEmpty():
|
||||
return None
|
||||
|
||||
node_data = {
|
||||
'name': node.getName(),
|
||||
'type': type(node.node()).__name__,
|
||||
'pos': (node.getX(), node.getY(), node.getZ()),
|
||||
'hpr': (node.getH(), node.getP(), node.getR()),
|
||||
'scale': (node.getSx(), node.getSy(), node.getSz()),
|
||||
'tags': {},
|
||||
'children': []
|
||||
}
|
||||
|
||||
# 保存所有标签
|
||||
try:
|
||||
if hasattr(node, 'getTagKeys'):
|
||||
for tag_key in node.getTagKeys():
|
||||
node_data['tags'][tag_key] = node.getTag(tag_key)
|
||||
except Exception as e:
|
||||
print(f"获取标签时出错: {e}")
|
||||
|
||||
# 保存视觉属性
|
||||
try:
|
||||
# 保存颜色属性
|
||||
if hasattr(node, 'getColor'):
|
||||
color = node.getColor()
|
||||
node_data['color'] = (color.getX(), color.getY(), color.getZ(), color.getW())
|
||||
|
||||
# 保存材质属性
|
||||
if hasattr(node, 'getMaterial'):
|
||||
material = node.getMaterial()
|
||||
if material:
|
||||
material_data = {}
|
||||
material_data['base_color'] = (
|
||||
material.getBaseColor().getX(),
|
||||
material.getBaseColor().getY(),
|
||||
material.getBaseColor().getZ(),
|
||||
material.getBaseColor().getW()
|
||||
)
|
||||
material_data['ambient'] = (
|
||||
material.getAmbient().getX(),
|
||||
material.getAmbient().getY(),
|
||||
material.getAmbient().getZ(),
|
||||
material.getAmbient().getW()
|
||||
)
|
||||
material_data['diffuse'] = (
|
||||
material.getDiffuse().getX(),
|
||||
material.getDiffuse().getY(),
|
||||
material.getDiffuse().getZ(),
|
||||
material.getDiffuse().getW()
|
||||
)
|
||||
material_data['specular'] = (
|
||||
material.getSpecular().getX(),
|
||||
material.getSpecular().getY(),
|
||||
material.getSpecular().getZ(),
|
||||
material.getSpecular().getW()
|
||||
)
|
||||
material_data['shininess'] = material.getShininess()
|
||||
node_data['material'] = material_data
|
||||
|
||||
except Exception as e:
|
||||
print(f"保存视觉属性时出错: {e}")
|
||||
|
||||
# 根据节点类型保存特定信息
|
||||
if node.hasTag("tree_item_type"):
|
||||
node_type = node.getTag("tree_item_type")
|
||||
node_data['node_type'] = node_type
|
||||
|
||||
# 保存特定类型节点的额外信息
|
||||
if node_type in ["LIGHT_NODE", "SPOT_LIGHT_NODE", "POINT_LIGHT_NODE"]:
|
||||
# 保存光源特定信息
|
||||
rp_light = node.getPythonTag("rp_light_object")
|
||||
if rp_light:
|
||||
node_data['light_data'] = {
|
||||
'energy': getattr(rp_light, 'energy', 5000),
|
||||
'radius': getattr(rp_light, 'radius', 1000),
|
||||
'fov': getattr(rp_light, 'fov', 70) if hasattr(rp_light, 'fov') else None,
|
||||
'inner_radius': getattr(rp_light, 'inner_radius', 0.4) if hasattr(rp_light,
|
||||
'inner_radius') else None,
|
||||
'casts_shadows': getattr(rp_light, 'casts_shadows', True) if hasattr(rp_light,
|
||||
'casts_shadows') else True,
|
||||
'shadow_map_resolution': getattr(rp_light, 'shadow_map_resolution', 256) if hasattr(
|
||||
rp_light, 'shadow_map_resolution') else 256
|
||||
}
|
||||
elif node_type in ["GUI_BUTTON", "GUI_LABEL", "GUI_ENTRY", "GUI_IMAGE",
|
||||
"GUI_3D_TEXT", "GUI_3D_IMAGE", "GUI_VIRTUAL_SCREEN"]:
|
||||
# 保存GUI元素特定信息
|
||||
node_data['gui_data'] = self._serializeGUIData(node)
|
||||
elif node_type == "IMPORTED_MODEL_NODE":
|
||||
# 保存模型特定信息
|
||||
node_data['model_data'] = self._serializeModelData(node)
|
||||
|
||||
return node_data
|
||||
|
||||
except Exception as e:
|
||||
print(f"序列化节点失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def _serializeGUIData(self, node):
|
||||
"""序列化GUI元素数据"""
|
||||
try:
|
||||
gui_data = {}
|
||||
|
||||
# 保存GUI相关的通用属性
|
||||
if node.hasTag("gui_type"):
|
||||
gui_data['gui_type'] = node.getTag("gui_type")
|
||||
|
||||
# 保存文本内容(如果有的话)
|
||||
if node.hasTag("text"):
|
||||
gui_data['text'] = node.getTag("text")
|
||||
|
||||
# 保存其他GUI相关标签
|
||||
gui_tags = ['font', 'font_size', 'text_color', 'bg_color', 'size']
|
||||
for tag in gui_tags:
|
||||
if node.hasTag(tag):
|
||||
gui_data[tag] = node.getTag(tag)
|
||||
|
||||
return gui_data
|
||||
except Exception as e:
|
||||
print(f"序列化GUI数据失败: {e}")
|
||||
return {}
|
||||
|
||||
def _serializeModelData(self, node):
|
||||
"""序列化模型数据,包括材质信息"""
|
||||
try:
|
||||
model_data = {}
|
||||
|
||||
# 保存模型相关的标签
|
||||
model_tags = ['model_path', 'file', 'element_type']
|
||||
for tag in model_tags:
|
||||
if node.hasTag(tag):
|
||||
model_data[tag] = node.getTag(tag)
|
||||
|
||||
# 保存材质信息
|
||||
try:
|
||||
# 获取模型的材质信息
|
||||
if hasattr(node, 'getState'):
|
||||
state = node.getState()
|
||||
if state:
|
||||
# 保存基础颜色信息
|
||||
from panda3d.core import ColorAttrib
|
||||
color_attrib = state.getColor()
|
||||
if color_attrib:
|
||||
model_data['base_color'] = (
|
||||
color_attrib.getColor().getX(),
|
||||
color_attrib.getColor().getY(),
|
||||
color_attrib.getColor().getZ(),
|
||||
color_attrib.getColor().getW()
|
||||
)
|
||||
|
||||
# 保存其他材质属性
|
||||
from panda3d.core import MaterialAttrib
|
||||
material_attrib = state.getAttrib(MaterialAttrib.getClassType())
|
||||
if material_attrib:
|
||||
material = material_attrib.getMaterial()
|
||||
if material:
|
||||
# 保存基础颜色
|
||||
base_color = material.getBaseColor()
|
||||
model_data['material_base_color'] = (
|
||||
base_color.getX(), base_color.getY(), base_color.getZ(), base_color.getW()
|
||||
)
|
||||
|
||||
# 保存环境光颜色
|
||||
ambient_color = material.getAmbient()
|
||||
model_data['material_ambient_color'] = (
|
||||
ambient_color.getX(), ambient_color.getY(), ambient_color.getZ(),
|
||||
ambient_color.getW()
|
||||
)
|
||||
|
||||
# 保存漫反射颜色
|
||||
diffuse_color = material.getDiffuse()
|
||||
model_data['material_diffuse_color'] = (
|
||||
diffuse_color.getX(), diffuse_color.getY(), diffuse_color.getZ(),
|
||||
diffuse_color.getW()
|
||||
)
|
||||
|
||||
# 保存高光颜色
|
||||
specular_color = material.getSpecular()
|
||||
model_data['material_specular_color'] = (
|
||||
specular_color.getX(), specular_color.getY(), specular_color.getZ(),
|
||||
specular_color.getW()
|
||||
)
|
||||
|
||||
# 保存粗糙度和金属度等参数
|
||||
model_data['material_roughness'] = material.getRoughness()
|
||||
model_data['material metallic'] = material.getMetallic()
|
||||
|
||||
except Exception as e:
|
||||
print(f"保存材质信息时出错: {e}")
|
||||
|
||||
return model_data
|
||||
except Exception as e:
|
||||
print(f"序列化模型数据失败: {e}")
|
||||
return {}
|
||||
|
||||
def recreateNodeFromData(self, node_data, parent_node):
|
||||
"""根据数据重建节点,并确保在场景树中显示"""
|
||||
try:
|
||||
if not node_data or not parent_node or parent_node.isEmpty():
|
||||
return None
|
||||
|
||||
print(f"正在重建节点 {node_data}")
|
||||
node_type = node_data.get('node_type', '')
|
||||
original_name = node_data.get('name', 'node')
|
||||
|
||||
# 生成唯一名称
|
||||
unique_name = self._generateUniqueName(original_name, parent_node)
|
||||
|
||||
# 根据节点类型调用相应的重建方法
|
||||
new_node = None
|
||||
if node_type in ["LIGHT_NODE", "SPOT_LIGHT_NODE", "POINT_LIGHT_NODE"]:
|
||||
new_node = self._recreateLightFromData(node_data, parent_node, unique_name)
|
||||
elif node_type == "CESIUM_TILESET_NODE":
|
||||
new_node = self._recreateTilesetFromData(node_data, parent_node, unique_name)
|
||||
elif node_type in ["GUI_BUTTON", "GUI_LABEL", "GUI_ENTRY", "GUI_IMAGE",
|
||||
"GUI_3DTEXT", "GUI_3DIMAGE", "GUI_VIRTUAL_SCREEN"]:
|
||||
new_node = self._recreateGUIFromData(node_data, parent_node, unique_name)
|
||||
elif node_type == "IMPORTED_MODEL_NODE":
|
||||
new_node = self._recreateModelFromData(node_data, parent_node, unique_name)
|
||||
else:
|
||||
# 创建普通节点
|
||||
new_node = self._createBasicNodeFromData(node_data, parent_node, unique_name)
|
||||
|
||||
# 如果成功创建节点,确保它在场景树中显示
|
||||
if new_node:
|
||||
# 尝试更新场景树以显示新节点
|
||||
try:
|
||||
if hasattr(self.world, 'interface_manager') and self.world.interface_manager:
|
||||
# 查找父节点在场景树中的对应项
|
||||
parent_item = self._findTreeItemForNode(parent_node)
|
||||
if parent_item:
|
||||
# 添加新节点到场景树
|
||||
tree_widget = self.world.interface_manager.treeWidget
|
||||
if tree_widget:
|
||||
tree_widget.add_node_to_tree_widget(new_node, parent_item, node_type or "NODE")
|
||||
except Exception as e:
|
||||
print(f"添加节点到场景树时出错: {e}")
|
||||
|
||||
return new_node
|
||||
|
||||
except Exception as e:
|
||||
print(f"重建节点失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def _findTreeItemForNode(self, node):
|
||||
"""根据节点查找对应的场景树项"""
|
||||
try:
|
||||
if hasattr(self.world, 'interface_manager') and self.world.interface_manager:
|
||||
tree_widget = self.world.interface_manager.treeWidget
|
||||
if tree_widget:
|
||||
# 遍历场景树查找匹配的节点项
|
||||
for i in range(tree_widget.topLevelItemCount()):
|
||||
item = tree_widget.topLevelItem(i)
|
||||
result = self._findTreeItemForNodeRecursive(item, node)
|
||||
if result:
|
||||
return result
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"查找场景树项时出错: {e}")
|
||||
return None
|
||||
|
||||
def _findTreeItemForNodeRecursive(self, item, target_node):
|
||||
"""递归查找场景树项"""
|
||||
try:
|
||||
# 检查当前项是否匹配
|
||||
item_node = getattr(item, 'node_path', None)
|
||||
if not item_node:
|
||||
item_node = getattr(item, 'node', None)
|
||||
|
||||
if item_node and item_node == target_node:
|
||||
return item
|
||||
|
||||
# 递归检查子项
|
||||
for i in range(item.childCount()):
|
||||
child_item = item.child(i)
|
||||
result = self._findTreeItemForNodeRecursive(child_item, target_node)
|
||||
if result:
|
||||
return result
|
||||
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"递归查找场景树项时出错: {e}")
|
||||
return None
|
||||
|
||||
def _recreateLightFromData(self, node_data, parent_node, name):
|
||||
"""根据数据重建光源"""
|
||||
try:
|
||||
light_type = node_data.get('tags', {}).get('light_type', 'spot_light')
|
||||
|
||||
# 创建光源
|
||||
if light_type == 'spot_light':
|
||||
light_node = self.createSpotLight(pos=node_data.get('pos', (0, 0, 0)))
|
||||
else: # point_light
|
||||
light_node = self.createPointLight(pos=node_data.get('pos', (0, 0, 0)))
|
||||
|
||||
if light_node:
|
||||
# 设置名称
|
||||
light_node.setName(name)
|
||||
|
||||
# 恢复其他属性
|
||||
light_data = node_data.get('light_data', {})
|
||||
rp_light = light_node.getPythonTag("rp_light_object")
|
||||
if rp_light and light_data:
|
||||
if 'energy' in light_data:
|
||||
rp_light.energy = light_data['energy']
|
||||
if 'radius' in light_data:
|
||||
rp_light.radius = light_data['radius']
|
||||
if 'fov' in light_data and hasattr(rp_light, 'fov'):
|
||||
rp_light.fov = light_data['fov']
|
||||
if 'inner_radius' in light_data and hasattr(rp_light, 'inner_radius'):
|
||||
rp_light.inner_radius = light_data['inner_radius']
|
||||
if 'casts_shadows' in light_data and hasattr(rp_light, 'casts_shadows'):
|
||||
rp_light.casts_shadows = light_data['casts_shadows']
|
||||
if 'shadow_map_resolution' in light_data and hasattr(rp_light, 'shadow_map_resolution'):
|
||||
rp_light.shadow_map_resolution = light_data['shadow_map_resolution']
|
||||
|
||||
# 恢复其他标签
|
||||
for tag_key, tag_value in node_data.get('tags', {}).items():
|
||||
if tag_key not in ['name', 'light_type']:
|
||||
light_node.setTag(tag_key, str(tag_value))
|
||||
|
||||
return light_node
|
||||
|
||||
except Exception as e:
|
||||
print(f"重建光源失败: {e}")
|
||||
return None
|
||||
|
||||
def _recreateTilesetFromData(self, node_data, parent_node, name):
|
||||
"""根据数据重建Tileset"""
|
||||
try:
|
||||
tileset_url = node_data.get('tileset_url', '')
|
||||
if not tileset_url:
|
||||
return None
|
||||
|
||||
# 使用现有方法加载tileset
|
||||
position = node_data.get('pos', (0, 0, 0))
|
||||
tileset_node = self.load_cesium_tileset(tileset_url, position)
|
||||
|
||||
if tileset_node:
|
||||
# 设置名称
|
||||
tileset_node.setName(name)
|
||||
|
||||
# 恢复其他标签
|
||||
for tag_key, tag_value in node_data.get('tags', {}).items():
|
||||
if tag_key not in ['name']:
|
||||
tileset_node.setTag(tag_key, str(tag_value))
|
||||
|
||||
return tileset_node
|
||||
|
||||
except Exception as e:
|
||||
print(f"重建Tileset失败: {e}")
|
||||
return None
|
||||
|
||||
def _recreateGUIFromData(self, node_data, parent_node, name):
|
||||
"""根据数据重建GUI元素"""
|
||||
try:
|
||||
gui_data = node_data.get('gui_data', {})
|
||||
#gui_type = gui_data.get('gui_type', '')
|
||||
gui_type = node_data.get("tags").get("gui_type", "")
|
||||
|
||||
print(f"正在重建GUI元素: {gui_type}")
|
||||
print(f"正在重建GUI元素: {node_data}")
|
||||
|
||||
# 根据GUI类型调用相应的创建方法
|
||||
new_gui_element = None
|
||||
|
||||
if gui_type == "button" and hasattr(self.world, 'createGUIButton'):
|
||||
pos = node_data.get('pos', (0, 0, 0))
|
||||
text = node_data.get('tags').get('gui_text', '')
|
||||
size = node_data.get('scale', 1)
|
||||
print(pos,text,size)
|
||||
new_gui_element = self.world.createGUIButton(pos,text,size)
|
||||
elif gui_type == "label" and hasattr(self.world, 'createGUILabel'):
|
||||
pos = node_data.get('pos', (0, 0, 0))
|
||||
text = node_data.get('tags').get('gui_text', '')
|
||||
size = node_data.get('scale', 1)
|
||||
new_gui_element = self.world.createGUILabel(pos,text,size)
|
||||
elif gui_type == "entry" and hasattr(self.world, 'createGUIEntry'):
|
||||
pos = node_data.get('pos', (0, 0, 0))
|
||||
text = node_data.get('tags').get('gui_text', '')
|
||||
size = node_data.get('scale', 1)
|
||||
new_gui_element = self.world.createGUIEntry(pos,text,size)
|
||||
elif gui_type == "2d_image" and hasattr(self.world, 'createGUI2DImage'):
|
||||
pos = node_data.get('pos', (0, 0, 0))
|
||||
image_path = node_data.get('tags').get('image_path', '')
|
||||
size = node_data.get('size', 1)
|
||||
new_gui_element = self.world.createGUI2DImage(pos, image_path, size)
|
||||
elif gui_type == "3d_text" and hasattr(self.world, 'createGUI3DText'):
|
||||
print("正在创建3D文本!!!")
|
||||
pos = node_data.get('pos', (0, 0, 0))
|
||||
text = node_data.get('tags', {}).get('gui_text', '')
|
||||
scale = node_data.get('scale', 1)
|
||||
if isinstance(scale, (list, tuple)):
|
||||
scale = scale[0] if len(scale) > 0 else 1
|
||||
print(f"正在创建3D文本: 位置={pos}, 文本={text}, 大小={scale}")
|
||||
new_gui_element = self.world.createGUI3DText(pos, text, scale)
|
||||
elif gui_type == "3d_image" and hasattr(self.world, 'createGUI3DImage'):
|
||||
pos = node_data.get('pos', (0, 0, 0))
|
||||
image_path = node_data.get('tags').get('image_path', '')
|
||||
scale = node_data.get('scale', (1, 1))
|
||||
if isinstance(scale, (int, float)):
|
||||
scale = (scale, scale)
|
||||
elif isinstance(scale, (list, tuple)) and len(scale) >= 2:
|
||||
scale = (scale[0], scale[1])
|
||||
else:
|
||||
scale = (1, 1)
|
||||
print(f"正在创建3D图片: 位置={pos}, 路径={image_path}, 大小={scale}")
|
||||
new_gui_element = self.world.createGUI3DImage(pos, image_path, scale)
|
||||
|
||||
if new_gui_element:
|
||||
# 设置名称和变换
|
||||
if hasattr(new_gui_element, 'setName'):
|
||||
new_gui_element.setName(name)
|
||||
|
||||
# 设置位置、旋转、缩放
|
||||
pos = node_data.get('pos', (0, 0, 0))
|
||||
hpr = node_data.get('hpr', (0, 0, 0))
|
||||
scale = node_data.get('scale', (1, 1, 1))
|
||||
|
||||
if hasattr(new_gui_element, 'setPos'):
|
||||
new_gui_element.setPos(*pos)
|
||||
if hasattr(new_gui_element, 'setHpr'):
|
||||
new_gui_element.setHpr(*hpr)
|
||||
if hasattr(new_gui_element, 'setScale'):
|
||||
new_gui_element.setScale(*scale)
|
||||
|
||||
# 恢复文本内容
|
||||
if 'text' in gui_data and hasattr(new_gui_element, 'setText'):
|
||||
new_gui_element.setText(gui_data['text'])
|
||||
|
||||
# 恢复其他标签
|
||||
for tag_key, tag_value in node_data.get('tags', {}).items():
|
||||
if hasattr(new_gui_element, 'setTag') and tag_key not in ['name']:
|
||||
new_gui_element.setTag(tag_key, str(tag_value))
|
||||
|
||||
print(f"GUI元素重建成功: {name}")
|
||||
|
||||
return new_gui_element
|
||||
|
||||
except Exception as e:
|
||||
print(f"重建GUI元素失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def _recreateModelFromData(self, node_data, parent_node, name):
|
||||
"""根据数据重建模型,保持材质"""
|
||||
try:
|
||||
model_data = node_data.get('model_data', {})
|
||||
model_path = model_data.get('model_path', model_data.get('file', ''))
|
||||
|
||||
if not model_path or not os.path.exists(model_path):
|
||||
# 如果原始模型文件不存在,创建一个基本节点
|
||||
return self._createBasicNodeFromData(node_data, parent_node, name)
|
||||
|
||||
# 导入模型,保持原有参数
|
||||
model = self.importModel(
|
||||
model_path,
|
||||
apply_unit_conversion=False, # 已经处理过的模型不需要再次转换
|
||||
normalize_scales=False, # 保持原有缩放
|
||||
auto_convert_to_glb=False # 已经处理过的模型不需要再次转换
|
||||
)
|
||||
|
||||
if model:
|
||||
# 设置名称
|
||||
model.setName(name)
|
||||
|
||||
# 设置变换
|
||||
pos = node_data.get('pos', (0, 0, 0))
|
||||
hpr = node_data.get('hpr', (0, 0, 0))
|
||||
scale = node_data.get('scale', (1, 1, 1))
|
||||
|
||||
model.setPos(*pos)
|
||||
model.setHpr(*hpr)
|
||||
model.setScale(*scale)
|
||||
|
||||
# 恢复材质信息
|
||||
try:
|
||||
self._restoreModelMaterial(model, model_data)
|
||||
except Exception as e:
|
||||
print(f"恢复模型材质时出错: {e}")
|
||||
|
||||
# 恢复标签
|
||||
for tag_key, tag_value in node_data.get('tags', {}).items():
|
||||
if tag_key not in ['name']:
|
||||
model.setTag(tag_key, str(tag_value))
|
||||
|
||||
# 添加到模型列表
|
||||
if model not in self.models:
|
||||
self.models.append(model)
|
||||
|
||||
return model
|
||||
|
||||
except Exception as e:
|
||||
print(f"重建模型失败: {e}")
|
||||
# 出错时创建基本节点
|
||||
return self._createBasicNodeFromData(node_data, parent_node, name)
|
||||
|
||||
def _restoreModelMaterial(self, model, model_data):
|
||||
"""恢复模型材质"""
|
||||
try:
|
||||
# 恢复基础颜色
|
||||
if 'base_color' in model_data:
|
||||
from panda3d.core import ColorAttrib
|
||||
base_color = model_data['base_color']
|
||||
color = (base_color[0], base_color[1], base_color[2], base_color[3])
|
||||
model.setColor(color)
|
||||
|
||||
# 恢复复杂材质属性
|
||||
if any(key.startswith('material_') for key in model_data.keys()):
|
||||
from panda3d.core import Material
|
||||
|
||||
# 创建新材质或获取现有材质
|
||||
material = Material()
|
||||
|
||||
# 恢复基础颜色
|
||||
if 'material_base_color' in model_data:
|
||||
base_color = model_data['material_base_color']
|
||||
material.setBaseColor((base_color[0], base_color[1], base_color[2], base_color[3]))
|
||||
|
||||
# 恢复环境光颜色
|
||||
if 'material_ambient_color' in model_data:
|
||||
ambient_color = model_data['material_ambient_color']
|
||||
material.setAmbient((ambient_color[0], ambient_color[1], ambient_color[2], ambient_color[3]))
|
||||
|
||||
# 恢复漫反射颜色
|
||||
if 'material_diffuse_color' in model_data:
|
||||
diffuse_color = model_data['material_diffuse_color']
|
||||
material.setDiffuse((diffuse_color[0], diffuse_color[1], diffuse_color[2], diffuse_color[3]))
|
||||
|
||||
# 恢复高光颜色
|
||||
if 'material_specular_color' in model_data:
|
||||
specular_color = model_data['material_specular_color']
|
||||
material.setSpecular((specular_color[0], specular_color[1], specular_color[2], specular_color[3]))
|
||||
|
||||
# 恢复粗糙度和金属度
|
||||
if 'material_roughness' in model_data:
|
||||
material.setRoughness(model_data['material_roughness'])
|
||||
|
||||
if 'material_metallic' in model_data:
|
||||
material.setMetallic(model_data['material_metallic'])
|
||||
|
||||
# 应用材质到模型
|
||||
model.setMaterial(material)
|
||||
|
||||
except Exception as e:
|
||||
print(f"恢复材质失败: {e}")
|
||||
|
||||
def _createBasicNodeFromData(self, node_data, parent_node, name):
|
||||
"""创建基本节点,保持视觉属性"""
|
||||
try:
|
||||
new_node = parent_node.attachNewNode(name)
|
||||
|
||||
# 设置变换
|
||||
pos = node_data.get('pos', (0, 0, 0))
|
||||
hpr = node_data.get('hpr', (0, 0, 0))
|
||||
scale = node_data.get('scale', (1, 1, 1))
|
||||
|
||||
new_node.setPos(*pos)
|
||||
new_node.setHpr(*hpr)
|
||||
new_node.setScale(*scale)
|
||||
|
||||
# 恢复视觉属性
|
||||
try:
|
||||
# 恢复颜色
|
||||
if 'color' in node_data:
|
||||
color_data = node_data['color']
|
||||
new_node.setColor(color_data[0], color_data[1], color_data[2], color_data[3])
|
||||
|
||||
# 恢复材质
|
||||
if 'material' in node_data:
|
||||
from panda3d.core import Material
|
||||
material_data = node_data['material']
|
||||
material = Material()
|
||||
|
||||
if 'base_color' in material_data:
|
||||
bc = material_data['base_color']
|
||||
material.setBaseColor((bc[0], bc[1], bc[2], bc[3]))
|
||||
|
||||
if 'ambient' in material_data:
|
||||
ac = material_data['ambient']
|
||||
material.setAmbient((ac[0], ac[1], ac[2], ac[3]))
|
||||
|
||||
if 'diffuse' in material_data:
|
||||
dc = material_data['diffuse']
|
||||
material.setDiffuse((dc[0], dc[1], dc[2], dc[3]))
|
||||
|
||||
if 'specular' in material_data:
|
||||
sc = material_data['specular']
|
||||
material.setSpecular((sc[0], sc[1], sc[2], sc[3]))
|
||||
|
||||
if 'shininess' in material_data:
|
||||
material.setShininess(material_data['shininess'])
|
||||
|
||||
new_node.setMaterial(material)
|
||||
|
||||
except Exception as e:
|
||||
print(f"恢复视觉属性时出错: {e}")
|
||||
|
||||
# 恢复标签
|
||||
for tag_key, tag_value in node_data.get('tags', {}).items():
|
||||
if tag_key not in ['name']:
|
||||
new_node.setTag(tag_key, str(tag_value))
|
||||
|
||||
return new_node
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建基本节点失败: {e}")
|
||||
return None
|
||||
|
||||
def _generateUniqueName(self, base_name, parent_node):
|
||||
"""生成唯一节点名称"""
|
||||
try:
|
||||
# 移除可能的数字后缀
|
||||
import re
|
||||
import time
|
||||
name_base = re.sub(r'_\d+$', '', base_name)
|
||||
|
||||
# 查找现有同名节点
|
||||
counter = 1
|
||||
unique_name = base_name
|
||||
while True:
|
||||
# 检查父节点下是否已存在同名子节点
|
||||
existing_node = parent_node.find(unique_name)
|
||||
if existing_node.isEmpty():
|
||||
break
|
||||
unique_name = f"{name_base}_{counter}"
|
||||
counter += 1
|
||||
if counter > 1000: # 防止无限循环
|
||||
unique_name = f"{name_base}_{int(time.time())}"
|
||||
break
|
||||
|
||||
return unique_name
|
||||
except:
|
||||
return f"{base_name}_{int(time.time())}"
|
||||
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@ from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QMenu, QAction
|
||||
QSpinBox, QFrame)
|
||||
from PyQt5.QtCore import Qt, QDir, QTimer, QSize, QPoint, QUrl, QRect
|
||||
from direct.showbase.ShowBaseGlobal import aspect2d
|
||||
from panda3d.core import OrthographicLens
|
||||
|
||||
from ui.widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget,CustomAssetsTreeWidget, CustomConsoleDockWidget
|
||||
from ui.icon_manager import get_icon_manager, get_icon
|
||||
@ -37,6 +38,10 @@ class MainWindow(QMainWindow):
|
||||
self.world = world
|
||||
self.world.main_window = self # 关键:让world对象能访问主窗口
|
||||
|
||||
#剪切板相关属性
|
||||
self.clipboard = []
|
||||
self.clipboard_mode = None
|
||||
|
||||
# 初始化图标管理器并打印调试信息
|
||||
self.icon_manager = get_icon_manager()
|
||||
print("🔧 图标管理器初始化完成")
|
||||
@ -808,6 +813,238 @@ class MainWindow(QMainWindow):
|
||||
# 统一连接信号到处理方法
|
||||
self.connectCreateMenuActions()
|
||||
|
||||
def setupViewMenuActions(self):
|
||||
"""设置视图菜单动作"""
|
||||
# 连接视图菜单事件
|
||||
self.viewPerspectiveAction.triggered.connect(self.onViewPerspective)
|
||||
self.viewTopAction.triggered.connect(self.onViewTop)
|
||||
self.viewFrontAction.triggered.connect(self.onViewFront)
|
||||
self.viewOrthographicAction = self.viewMenu.addAction('正交视图') # 添加正交视图动作
|
||||
self.viewOrthographicAction.triggered.connect(self.onViewOrthographic)
|
||||
self.viewGridAction.triggered.connect(self.onViewGrid) # 添加网格显示的信号连接
|
||||
|
||||
# 保存原始相机设置
|
||||
self._original_camera_fov = 80
|
||||
self._original_camera_pos = (0, -50, 20)
|
||||
self._original_camera_lookat = (0, 0, 0)
|
||||
|
||||
self._grid_visible = False
|
||||
def onViewPerspective(self):
|
||||
"""切换到透视视图"""
|
||||
try:
|
||||
lens = self.world.cam.node().getLens()
|
||||
lens.setFov(self._original_camera_fov)
|
||||
except Exception as e:
|
||||
print(f"切换到透视视图失败{e}")
|
||||
|
||||
def onViewOrthographic(self):
|
||||
"""切换到正交视图"""
|
||||
try:
|
||||
# 保存当前相机设置(如果是透视模式)
|
||||
lens = self.world.cam.node().getLens()
|
||||
if not hasattr(self, '_saved_perspective_settings'):
|
||||
self._saved_perspective_settings = {
|
||||
'fov': lens.getFov()[0],
|
||||
'pos': self.world.cam.getPos(),
|
||||
'hpr': self.world.cam.getHpr()
|
||||
}
|
||||
|
||||
# 获取窗口尺寸
|
||||
win_width, win_height = self.world.getWindowSize()
|
||||
aspect_ratio = win_width / win_height if win_height != 0 else 16 / 9
|
||||
|
||||
# 修改现有镜头为正交投影
|
||||
if not isinstance(lens, OrthographicLens):
|
||||
# 保存当前镜头类型
|
||||
self._original_lens = lens
|
||||
|
||||
# 创建正交镜头并替换现有镜头
|
||||
ortho_lens = OrthographicLens()
|
||||
ortho_lens.setFilmSize(20 * aspect_ratio, 20) # 设置正交镜头大小
|
||||
ortho_lens.setNearFar(-1000, 1000) # 设置较大的近远裁剪面
|
||||
|
||||
# 应用正交镜头
|
||||
self.world.cam.node().setLens(ortho_lens)
|
||||
else:
|
||||
# 如果已经是正交镜头,则调整其参数
|
||||
film_height = 20
|
||||
film_width = film_height * aspect_ratio
|
||||
lens.setFilmSize(film_width, film_height)
|
||||
|
||||
print("切换到正交视图")
|
||||
except Exception as e:
|
||||
print(f"切换正交视图失败: {e}")
|
||||
|
||||
def onViewTop(self):
|
||||
"""切换到俯视图(正交)"""
|
||||
try:
|
||||
# 保存当前设置
|
||||
self._saveCurrentCameraSettings()
|
||||
|
||||
# 设置正交投影
|
||||
self._setupOrthographicLens()
|
||||
|
||||
# 设置摄像机位置(从上方俯视)
|
||||
self.world.cam.setPos(0, 0, 30)
|
||||
self.world.cam.lookAt(0, 0, 0)
|
||||
self.world.cam.setHpr(0, -90, 0) # 朝下看
|
||||
|
||||
# 更新菜单项文本
|
||||
self._updateViewMenuText()
|
||||
|
||||
print("切换到俯视图")
|
||||
except Exception as e:
|
||||
print(f"切换俯视图失败: {e}")
|
||||
|
||||
def onViewFront(self):
|
||||
"""切换到前视图(正交)"""
|
||||
try:
|
||||
# 保存当前设置
|
||||
self._saveCurrentCameraSettings()
|
||||
|
||||
# 设置正交投影
|
||||
self._setupOrthographicLens()
|
||||
|
||||
# 设置摄像机位置(从前方向看)
|
||||
self.world.cam.setPos(0, -30, 0)
|
||||
self.world.cam.lookAt(0, 0, 0)
|
||||
self.world.cam.setHpr(0, 0, 0) # 正面朝向
|
||||
|
||||
# 更新菜单项文本
|
||||
self._updateViewMenuText()
|
||||
|
||||
print("切换到前视图")
|
||||
except Exception as e:
|
||||
print(f"切换前视图失败: {e}")
|
||||
|
||||
def onViewGrid(self):
|
||||
"""切换网格显示/隐藏"""
|
||||
try:
|
||||
# 切换网格显示状态
|
||||
self._grid_visible = not self._grid_visible
|
||||
|
||||
# 查找网格节点
|
||||
grid_node = self.world.render.find("**/grid")
|
||||
if grid_node.isEmpty():
|
||||
# 如果网格不存在则创建
|
||||
self._createGridView()
|
||||
grid_node = self.world.render.find("**/grid")
|
||||
|
||||
# 设置网格可见性
|
||||
if not grid_node.isEmpty():
|
||||
if self._grid_visible:
|
||||
grid_node.show()
|
||||
self.viewGridAction.setText("隐藏网格")
|
||||
print("网格已显示")
|
||||
else:
|
||||
grid_node.hide()
|
||||
self.viewGridAction.setText("显示网格")
|
||||
print("网格已隐藏")
|
||||
else:
|
||||
print("网格节点未找到")
|
||||
|
||||
except Exception as e:
|
||||
print(f"切换网格显示失败: {e}")
|
||||
def _createGridView(self):
|
||||
"""创建网格视图"""
|
||||
try:
|
||||
from panda3d.core import LineSegs,Vec3
|
||||
grid_node = self.world.render.attachNewNode("grid")
|
||||
lines = LineSegs()
|
||||
lines.setThickness(1.0)
|
||||
lines.setColor(0.3,0.3,0.3,1.0)
|
||||
grid_size = 20
|
||||
grid_step = 1
|
||||
|
||||
for i in range(-grid_size,grid_size+1,grid_step):
|
||||
lines.moveTo(Vec3(-grid_size,i,0))
|
||||
lines.drawTo(Vec3(grid_size,i,0))
|
||||
|
||||
grid_node.attachNewNode(lines.create())
|
||||
# 添加中心轴线(红色X轴,绿色Y轴)
|
||||
axis_lines = LineSegs()
|
||||
axis_lines.setThickness(2.0)
|
||||
# X轴(红色)
|
||||
axis_lines.setColor(1.0,0.0,0.0,1.0)
|
||||
axis_lines.moveTo(Vec3(0,0,0))
|
||||
axis_lines.drawTo(Vec3(grid_size,0,0))
|
||||
# Y轴(绿色)
|
||||
axis_lines.setColor(0.0, 1.0, 0.0, 1.0)
|
||||
axis_lines.moveTo(Vec3(0, 0, 0))
|
||||
axis_lines.drawTo(Vec3(0, grid_size, 0))
|
||||
|
||||
grid_node.attachNewNode(axis_lines.create())
|
||||
|
||||
print("网格已创建")
|
||||
except Exception as e:
|
||||
print(f"创建网格失败{e}")
|
||||
|
||||
def _saveCurrentCameraSettings(self):
|
||||
"""保存当前相机设置"""
|
||||
try:
|
||||
lens = self.world.cam.node().getLens()
|
||||
self._saved_camera_settings = {
|
||||
'lens_type': 'perspective' if not isinstance(lens, OrthographicLens) else 'orthographic',
|
||||
'fov': lens.getFov()[0] if hasattr(lens, 'getFov') else None,
|
||||
'film_size': (lens.getFilmSize()[0], lens.getFilmSize()[1]) if hasattr(lens, 'getFilmSize') else None,
|
||||
'pos': self.world.cam.getPos(),
|
||||
'hpr': self.world.cam.getHpr()
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"保存相机设置失败: {e}")
|
||||
|
||||
def _setupOrthographicLens(self):
|
||||
"""设置正交镜头"""
|
||||
try:
|
||||
win_width, win_height = self.world.getWindowSize()
|
||||
aspect_ratio = win_width / win_height if win_height != 0 else 16 / 9
|
||||
|
||||
from panda3d.core import OrthographicLens
|
||||
ortho_lens = OrthographicLens()
|
||||
ortho_lens.setFilmSize(20 * aspect_ratio, 20) # 设置正交镜头大小
|
||||
ortho_lens.setNearFar(-1000, 1000) # 设置较大的近远裁剪面
|
||||
|
||||
self.world.cam.node().setLens(ortho_lens)
|
||||
except Exception as e:
|
||||
print(f"设置正交镜头失败: {e}")
|
||||
|
||||
def _updateViewMenuText(self):
|
||||
"""更新视图菜单文本"""
|
||||
try:
|
||||
lens = self.world.cam.node().getLens()
|
||||
from panda3d.core import OrthographicLens
|
||||
|
||||
# 更新正交/透视视图动作文本
|
||||
if isinstance(lens, OrthographicLens):
|
||||
self.viewOrthographicAction.setText("切换到透视视图")
|
||||
self.viewOrthographicAction.triggered.disconnect()
|
||||
self.viewOrthographicAction.triggered.connect(self.onViewPerspective)
|
||||
else:
|
||||
self.viewOrthographicAction.setText("切换到正交视图")
|
||||
self.viewOrthographicAction.triggered.disconnect()
|
||||
self.viewOrthographicAction.triggered.connect(self.onViewOrthographic)
|
||||
except Exception as e:
|
||||
print(f"更新视图菜单文本失败: {e}")
|
||||
|
||||
# 如果需要在窗口大小改变时调整正交镜头,可以添加以下方法
|
||||
def _onWindowResized(self):
|
||||
"""窗口大小改变时的处理"""
|
||||
try:
|
||||
lens = self.world.cam.node().getLens()
|
||||
from panda3d.core import OrthographicLens
|
||||
|
||||
# 如果当前是正交镜头,需要根据新窗口大小调整
|
||||
if isinstance(lens, OrthographicLens):
|
||||
win_width, win_height = self.world.getWindowSize()
|
||||
if win_height != 0:
|
||||
aspect_ratio = win_width / win_height
|
||||
film_height = 20
|
||||
film_width = film_height * aspect_ratio
|
||||
lens.setFilmSize(film_width, film_height)
|
||||
except Exception as e:
|
||||
print(f"窗口大小调整失败: {e}")
|
||||
|
||||
|
||||
def connectCreateMenuActions(self):
|
||||
"""统一连接创建菜单的信号到处理方法"""
|
||||
# 连接到world对象的创建方法
|
||||
@ -1564,6 +1801,9 @@ class MainWindow(QMainWindow):
|
||||
self.buildAction.triggered.connect(lambda: buildPackage(self))
|
||||
self.exitAction.triggered.connect(QApplication.instance().quit)
|
||||
|
||||
#添加保存项目快捷键盘
|
||||
self.saveAction.setShortcut(QKeySequence.Save)
|
||||
|
||||
# 连接工具事件
|
||||
self.sunsetAction.triggered.connect(lambda: self.world.setCurrentTool("光照编辑"))
|
||||
self.pluginAction.triggered.connect(lambda: self.world.setCurrentTool("图形编辑"))
|
||||
@ -1593,6 +1833,21 @@ class MainWindow(QMainWindow):
|
||||
lambda: self.world.onTreeItemClicked(self.treeWidget.currentItem(), 0))
|
||||
print("已连接点击信号")
|
||||
|
||||
self.undoAction.triggered.connect(self.onUndo)
|
||||
self.redoAction.triggered.connect(self.onRedo)
|
||||
self.cutAction.triggered.connect(self.onCut)
|
||||
self.copyAction.triggered.connect(self.onCopy)
|
||||
self.pasteAction.triggered.connect(self.onPaste)
|
||||
|
||||
self.undoAction.setShortcut(QKeySequence.Undo)
|
||||
self.redoAction.setShortcut(QKeySequence.Redo)
|
||||
self.cutAction.setShortcut(QKeySequence.Cut)
|
||||
self.copyAction.setShortcut(QKeySequence.Copy)
|
||||
self.pasteAction.setShortcut(QKeySequence.Paste)
|
||||
|
||||
#连接视图菜单事件
|
||||
self.setupViewMenuActions()
|
||||
|
||||
# 连接工具切换信号
|
||||
#self.toolGroup.buttonClicked.connect(self.onToolChanged)
|
||||
|
||||
@ -1603,6 +1858,288 @@ class MainWindow(QMainWindow):
|
||||
# self.toggleHotReloadAction.triggered.connect(self.onToggleHotReload)
|
||||
# self.openScriptsManagerAction.triggered.connect(self.onOpenScriptsManager)
|
||||
|
||||
def onCopy(self):
|
||||
"""复制操作"""
|
||||
try:
|
||||
selected_item = self.treeWidget.currentItem()
|
||||
if not selected_item:
|
||||
QMessageBox.information(self, "提示", "请先选择要复制的节点")
|
||||
return
|
||||
|
||||
# 获取选中的节点
|
||||
selected_node = getattr(selected_item, 'node_path', None)
|
||||
if not selected_node:
|
||||
selected_node = getattr(selected_item, 'node', None)
|
||||
|
||||
if not selected_node and hasattr(self.world, 'selection'):
|
||||
selected_node = getattr(self.world.selection, 'selectedNode', None)
|
||||
|
||||
if not selected_node or selected_node.isEmpty():
|
||||
QMessageBox.warning(self, "错误", "无法获取选中节点")
|
||||
return
|
||||
|
||||
# 检查是否是根节点
|
||||
if selected_node.getName() == "render":
|
||||
QMessageBox.warning(self, "错误", "不能复制根节点")
|
||||
return
|
||||
|
||||
# 序列化节点数据
|
||||
node_data = self.world.scene_manager.serializeNodeForCopy(selected_node)
|
||||
if not node_data:
|
||||
QMessageBox.warning(self, "错误", "无法序列化选中节点")
|
||||
return
|
||||
|
||||
# 存储到剪切板
|
||||
self.clipboard = [node_data]
|
||||
self.clipboard_mode = "copy"
|
||||
|
||||
QMessageBox.information(self, "成功", "节点已复制到剪切板")
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"复制操作失败: {str(e)}")
|
||||
|
||||
def onCut(self):
|
||||
"""剪切操作"""
|
||||
try:
|
||||
selected_item = self.treeWidget.currentItem()
|
||||
if not selected_item:
|
||||
QMessageBox.information(self, "提示", "请先选择要剪切的节点")
|
||||
return
|
||||
|
||||
# 获取选中的节点
|
||||
selected_node = getattr(selected_item, 'node_path', None)
|
||||
if not selected_node:
|
||||
selected_node = getattr(selected_item, 'node', None)
|
||||
|
||||
if not selected_node and hasattr(self.world, 'selection'):
|
||||
selected_node = getattr(self.world.selection, 'selectedNode', None)
|
||||
|
||||
if not selected_node or selected_node.isEmpty():
|
||||
QMessageBox.warning(self, "错误", "无法获取选中节点")
|
||||
return
|
||||
|
||||
# 检查是否是根节点或特殊节点
|
||||
if selected_node.getName() in ["render", "camera", "ambientLight", "directionalLight"]:
|
||||
QMessageBox.warning(self, "错误", "不能剪切根节点或系统节点")
|
||||
return
|
||||
|
||||
# 序列化节点数据
|
||||
node_data = self.world.scene_manager.serializeNodeForCopy(selected_node)
|
||||
if not node_data:
|
||||
QMessageBox.warning(self, "错误", "无法序列化选中节点")
|
||||
return
|
||||
|
||||
# 存储到剪切板
|
||||
self.clipboard = [node_data]
|
||||
self.clipboard_mode = "cut"
|
||||
|
||||
# 删除原节点
|
||||
self.treeWidget.delete_items([selected_item])
|
||||
|
||||
QMessageBox.information(self, "成功", "节点已剪切到剪切板")
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"剪切操作失败: {str(e)}")
|
||||
|
||||
def onPaste(self):
|
||||
"""粘贴操作"""
|
||||
try:
|
||||
if not self.clipboard:
|
||||
QMessageBox.information(self, "提示", "剪切板为空")
|
||||
return
|
||||
|
||||
# 获取粘贴目标节点
|
||||
parent_item = self.treeWidget.currentItem()
|
||||
parent_node = None
|
||||
|
||||
# 如果选中了节点,将其作为父节点
|
||||
if parent_item:
|
||||
parent_node = getattr(parent_item, 'node_path', None)
|
||||
if not parent_node:
|
||||
parent_node = getattr(parent_item, 'node', None)
|
||||
|
||||
# 确保获取到有效的父节点
|
||||
if parent_node and not parent_node.isEmpty():
|
||||
print(f"将粘贴到选中的节点: {parent_node.getName()}")
|
||||
else:
|
||||
parent_node = None
|
||||
|
||||
# 如果没有选中有效节点,默认粘贴到render节点下
|
||||
if not parent_node:
|
||||
print("未选中有效节点,将粘贴到根节点下")
|
||||
# 查找render节点
|
||||
for i in range(self.treeWidget.topLevelItemCount()):
|
||||
item = self.treeWidget.topLevelItem(i)
|
||||
if item.text(0) == "render":
|
||||
parent_item = item
|
||||
break
|
||||
|
||||
# 如果找到了render节点项,获取对应的节点
|
||||
if parent_item:
|
||||
parent_node = getattr(parent_item, 'node_path', None)
|
||||
if not parent_node:
|
||||
parent_node = getattr(parent_item, 'node', None)
|
||||
|
||||
# 如果仍然没有找到父节点项,直接使用world.render
|
||||
if not parent_node:
|
||||
parent_node = self.world.render
|
||||
|
||||
# 检查父节点有效性
|
||||
if not parent_node or parent_node.isEmpty():
|
||||
QMessageBox.warning(self, "错误", "无法获取有效的父节点")
|
||||
return
|
||||
|
||||
# 检查目标节点是否为允许的父节点类型
|
||||
parent_name = parent_node.getName()
|
||||
if parent_name in ["camera", "ambientLight", "directionalLight"]:
|
||||
QMessageBox.warning(self, "错误", "不能粘贴到该类型节点下")
|
||||
return
|
||||
|
||||
# 粘贴节点
|
||||
pasted_nodes = []
|
||||
for node_data in self.clipboard:
|
||||
print(f"正在粘贴节点数据:{node_data.get('name','Unknown')}")
|
||||
new_node = self.world.scene_manager.recreateNodeFromData(node_data, parent_node)
|
||||
if new_node:
|
||||
pasted_nodes.append(new_node)
|
||||
print(f"成功粘贴节点: {new_node.getName()}")
|
||||
else:
|
||||
print(f"粘贴节点失败: {node_data.get('name', 'Unknown')}")
|
||||
|
||||
# 如果是剪切操作,清空剪切板
|
||||
if self.clipboard_mode == "cut":
|
||||
self.clipboard.clear()
|
||||
self.clipboard_mode = None
|
||||
|
||||
QMessageBox.information(self, "成功", f"已粘贴 {len(pasted_nodes)} 个节点")
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"粘贴操作失败: {str(e)}")
|
||||
|
||||
def _serializeNode(self, node):
|
||||
"""序列化节点数据"""
|
||||
try:
|
||||
if not node or node.isEmpty():
|
||||
return None
|
||||
|
||||
node_data = {
|
||||
'name': node.getName(),
|
||||
'type': type(node.node()).__name__,
|
||||
'pos': (node.getX(), node.getY(), node.getZ()),
|
||||
'hpr': (node.getH(), node.getP(), node.getR()),
|
||||
'scale': (node.getSx(), node.getSy(), node.getSz()),
|
||||
'tags': {},
|
||||
'children': []
|
||||
}
|
||||
|
||||
# 保存所有标签
|
||||
try:
|
||||
# 使用更安全的方式获取标签
|
||||
if hasattr(node, 'getTagKeys'):
|
||||
for tag_key in node.getTagKeys():
|
||||
node_data['tags'][tag_key] = node.getTag(tag_key)
|
||||
except Exception as e:
|
||||
print(f"获取标签时出错: {e}")
|
||||
|
||||
# 递归序列化子节点(跳过辅助节点)
|
||||
try:
|
||||
if hasattr(node, 'getChildren'):
|
||||
for child in node.getChildren():
|
||||
# 跳过辅助节点
|
||||
child_name = child.getName() if hasattr(child, 'getName') else ""
|
||||
if not child_name.startswith(('gizmo', 'selectionBox', 'grid')):
|
||||
child_data = self._serializeNode(child)
|
||||
if child_data:
|
||||
node_data['children'].append(child_data)
|
||||
except Exception as e:
|
||||
print(f"序列化子节点时出错: {e}")
|
||||
|
||||
return node_data
|
||||
|
||||
except Exception as e:
|
||||
print(f"序列化节点失败: {e}")
|
||||
return None
|
||||
|
||||
def _deserializeNode(self, node_data, parent_node):
|
||||
"""反序列化节点数据"""
|
||||
try:
|
||||
if not node_data or not parent_node or parent_node.isEmpty():
|
||||
return None
|
||||
|
||||
# 创建新节点
|
||||
node_name = node_data.get('name', 'node')
|
||||
new_node = parent_node.attachNewNode(node_name)
|
||||
|
||||
# 设置变换
|
||||
try:
|
||||
pos = node_data.get('pos', (0, 0, 0))
|
||||
hpr = node_data.get('hpr', (0, 0, 0))
|
||||
scale = node_data.get('scale', (1, 1, 1))
|
||||
|
||||
new_node.setPos(*pos)
|
||||
new_node.setHpr(*hpr)
|
||||
new_node.setScale(*scale)
|
||||
except Exception as e:
|
||||
print(f"设置变换时出错: {e}")
|
||||
|
||||
# 恢复标签
|
||||
try:
|
||||
for tag_key, tag_value in node_data.get('tags', {}).items():
|
||||
new_node.setTag(tag_key, str(tag_value)) # 确保标签值是字符串
|
||||
except Exception as e:
|
||||
print(f"恢复标签时出错: {e}")
|
||||
|
||||
# 递归创建子节点
|
||||
try:
|
||||
for child_data in node_data.get('children', []):
|
||||
self._deserializeNode(child_data, new_node)
|
||||
except Exception as e:
|
||||
print(f"创建子节点时出错: {e}")
|
||||
|
||||
return new_node
|
||||
|
||||
except Exception as e:
|
||||
print(f"反序列化节点失败: {e}")
|
||||
return None
|
||||
|
||||
def _deleteNode(self, node, tree_item):
|
||||
"""删除节点"""
|
||||
try:
|
||||
if not node or node.isEmpty():
|
||||
return
|
||||
|
||||
# 特殊处理选中节点
|
||||
if hasattr(self.world, 'selection') and self.world.selection.selectedNode == node:
|
||||
self.world.selection.clearSelection()
|
||||
|
||||
# 从场景中删除节点
|
||||
node.removeNode()
|
||||
|
||||
# 从树形控件中删除项目
|
||||
if tree_item:
|
||||
try:
|
||||
parent = tree_item.parent()
|
||||
if parent:
|
||||
parent.removeChild(tree_item)
|
||||
else:
|
||||
index = self.treeWidget.indexOfTopLevelItem(tree_item)
|
||||
if index >= 0:
|
||||
self.treeWidget.takeTopLevelItem(index)
|
||||
except Exception as e:
|
||||
print(f"从树形控件删除项目时出错: {e}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"删除节点失败: {e}")
|
||||
|
||||
# 添加撤销/重做功能的基础实现
|
||||
def onUndo(self):
|
||||
"""撤销操作"""
|
||||
QMessageBox.information(self, "提示", "撤销功能将在后续版本中实现")
|
||||
|
||||
def onRedo(self):
|
||||
"""重做操作"""
|
||||
QMessageBox.information(self, "提示", "重做功能将在后续版本中实现")
|
||||
|
||||
|
||||
def onCreateCesiumView(self):
|
||||
if hasattr(self.world,'gui_manager') and self.world.gui_manager:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user