forked from Rowland/EG
保存系统持续完善
This commit is contained in:
parent
0ec780792e
commit
a8082bd656
@ -1,6 +1,7 @@
|
||||
# 修改后的 InfoPanelManager.py
|
||||
from xml.sax.handler import property_encoding
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from direct.gui.DirectGui import DirectFrame, DirectLabel
|
||||
from direct.showbase.ShowBaseGlobal import aspect2d
|
||||
from panda3d.core import TextNode, Vec4, NodePath
|
||||
@ -182,12 +183,60 @@ class InfoPanelManager(DirectObject):
|
||||
panel_node.setTag("gui_type", "info_panel")
|
||||
panel_node.setTag("panel_id", panel_id)
|
||||
panel_node.setTag("supports_3d_position_editing", "1") # 支持3D位置编辑
|
||||
panel_node.setTag("is_gui_element",'1')
|
||||
panel_node.setTag("tree_item_type","INFO_PANEL")
|
||||
panel_node.setTag("supports_3d_position_editing","1")
|
||||
|
||||
# 如果有背景图片,保存背景图片路径
|
||||
if bg_image:
|
||||
panel_node.setTag("bg_image_path", bg_image)
|
||||
|
||||
|
||||
if not visible:
|
||||
panel_node.hide()
|
||||
|
||||
# 将面板添加到场景树
|
||||
#self._addPanelToSceneTree(panel_node, panel_id)
|
||||
|
||||
return panel_node
|
||||
|
||||
def _addPanelToSceneTree(self, panel_node, panel_id):
|
||||
"""
|
||||
将信息面板添加到场景树中
|
||||
"""
|
||||
try:
|
||||
# 获取树形控件
|
||||
if hasattr(self.world, 'interface_manager') and hasattr(self.world.interface_manager, 'treeWidget'):
|
||||
tree_widget = self.world.interface_manager.treeWidget
|
||||
if tree_widget:
|
||||
# 查找根节点项
|
||||
root_item = None
|
||||
for i in range(tree_widget.topLevelItemCount()):
|
||||
item = tree_widget.topLevelItem(i)
|
||||
if item.text(0) == "render" or item.data(0, Qt.UserRole) == self.world.render:
|
||||
root_item = item
|
||||
break
|
||||
|
||||
if root_item:
|
||||
# 使用现有的 add_node_to_tree_widget 方法添加节点
|
||||
qt_item = tree_widget.add_node_to_tree_widget(panel_node, root_item, "INFO_PANEL")
|
||||
if qt_item:
|
||||
print(f"✅ 信息面板 {panel_id} 已添加到场景树")
|
||||
# 选中创建的节点
|
||||
tree_widget.setCurrentItem(qt_item)
|
||||
# 更新选择和属性面板
|
||||
tree_widget.update_selection_and_properties(panel_node, qt_item)
|
||||
else:
|
||||
print(f"⚠️ 信息面板 {panel_id} 添加到场景树失败")
|
||||
else:
|
||||
print("⚠️ 未找到场景树根节点,无法添加信息面板")
|
||||
else:
|
||||
print("⚠️ 无法访问场景树控件,信息面板未添加到场景树")
|
||||
except Exception as e:
|
||||
print(f"❌ 添加信息面板到场景树时出错: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def setPanelBackgroundImage(self, panel_id, image_path):
|
||||
"""
|
||||
为指定面板设置背景图片
|
||||
@ -758,11 +807,21 @@ class InfoPanelManager(DirectObject):
|
||||
# 设置GUI类型标记和支持3D编辑的标记
|
||||
panel_node.setTag("gui_type", "info_panel_3d")
|
||||
panel_node.setTag("panel_id", panel_id)
|
||||
panel_node.setTag("is_gui_element", "1") # 添加此标记确保节点被识别为GUI元素
|
||||
panel_node.setTag("is_scene_element", "1") # 添加此标记确保节点被识别为场景元素
|
||||
panel_node.setTag("supports_3d_position_editing", "1") # 支持3D位置编辑
|
||||
panel_node.setTag("tree_item_type", "INFO_PANEL_3D") # 添加树节点类型标记
|
||||
|
||||
# 如果有背景图片,保存背景图片路径
|
||||
if bg_image:
|
||||
panel_node.setTag("bg_image_path", bg_image)
|
||||
|
||||
if not visible:
|
||||
panel_node.hide()
|
||||
|
||||
# 将面板添加到场景树
|
||||
#self._addPanelToSceneTree(panel_node, panel_id)
|
||||
|
||||
return panel_node
|
||||
|
||||
def update3DPanelContent(self, panel_id, title=None, content=None):
|
||||
|
||||
@ -5,6 +5,7 @@ class ToolManager:
|
||||
"""初始化工具管理器"""
|
||||
self.world = world
|
||||
self.currentTool = "选择" # 默认工具为选择工具
|
||||
print(f"当前工具: {self.currentTool}")
|
||||
|
||||
def setCurrentTool(self, tool):
|
||||
"""设置当前工具"""
|
||||
|
||||
@ -320,7 +320,7 @@ class CoreWorld(Panda3DWorld):
|
||||
# 创建地板节点
|
||||
self.ground = self.render.attachNewNode(cm.generate())
|
||||
self.ground.setP(-90)
|
||||
self.ground.setZ(-0.1)
|
||||
self.ground.setZ(-1.0)
|
||||
self.ground.setColor(0.8, 0.8, 0.8, 1)
|
||||
# self.ground.setTag("is_scene_element", "1")
|
||||
|
||||
|
||||
@ -107,6 +107,14 @@ class SceneManager:
|
||||
filepath = util.normalize_model_path(filepath)
|
||||
original_filepath = filepath
|
||||
|
||||
# 在加载前设置忽略未知属性
|
||||
from panda3d.core import ConfigVariableBool
|
||||
ConfigVariableBool("model-cache-ignore-unknown-properties").setValue(True)
|
||||
|
||||
# 清除可能存在的模型缓存
|
||||
from panda3d.core import ModelPool
|
||||
ModelPool.releaseAllModels()
|
||||
|
||||
# 检查是否需要转换为GLB以获得更好的动画支持
|
||||
if auto_convert_to_glb and self._shouldConvertToGLB(filepath):
|
||||
print(f"🔄 检测到需要转换的格式,尝试转换为GLB...")
|
||||
@ -133,12 +141,26 @@ class SceneManager:
|
||||
print("加载模型失败")
|
||||
return None
|
||||
|
||||
# 验证并修复模型变换矩阵(在任何操作之前)
|
||||
#
|
||||
#self._validateAndFixAllTransforms(model)
|
||||
|
||||
# 设置模型名称
|
||||
model_name = os.path.basename(filepath)
|
||||
# 确保名称有效
|
||||
if not model_name:
|
||||
model_name = "imported_model"
|
||||
model.setName(model_name)
|
||||
|
||||
# 使用安全方法将模型添加到场景
|
||||
self._safeReparentTo(model, self.world.render)
|
||||
|
||||
# 设置模型名称
|
||||
model_name = os.path.basename(filepath)
|
||||
model.setName(model_name)
|
||||
|
||||
# 将模型添加到场景
|
||||
model.reparentTo(self.world.render)
|
||||
#model.reparentTo(self.world.render)
|
||||
# 保存原始路径和转换后的路径
|
||||
model.setTag("model_path", filepath)
|
||||
model.setTag("original_path", original_filepath)
|
||||
@ -214,6 +236,381 @@ class SceneManager:
|
||||
print(f"导入模型失败: {str(e)}")
|
||||
return None
|
||||
|
||||
def _validateAndFixAllTransforms(self, node_path, depth=0):
|
||||
"""验证并修复所有节点的变换矩阵"""
|
||||
indent = " " * depth
|
||||
try:
|
||||
if node_path.isEmpty():
|
||||
return
|
||||
|
||||
# 获取节点名称
|
||||
node_name = node_path.getName()
|
||||
print(f"{indent}检查节点变换: {node_name}")
|
||||
|
||||
# 修复变换矩阵问题
|
||||
self._fixNodeTransform(node_path)
|
||||
|
||||
# 递归检查子节点
|
||||
for i in range(node_path.getNumChildren()):
|
||||
try:
|
||||
child = node_path.getChild(i)
|
||||
self._validateAndFixAllTransforms(child, depth + 1)
|
||||
except Exception as e:
|
||||
print(f"{indent}⚠️ 处理子节点 {i} 时出错: {e}")
|
||||
continue
|
||||
|
||||
except Exception as e:
|
||||
print(f"{indent}❌ 检查节点变换时出错: {node_name} - {e}")
|
||||
|
||||
def _fixNodeTransform(self, node_path):
|
||||
"""修复单个节点的变换问题"""
|
||||
try:
|
||||
if node_path.isEmpty():
|
||||
return
|
||||
|
||||
node_name = node_path.getName()
|
||||
|
||||
# 方法1: 尝试获取和验证当前变换
|
||||
try:
|
||||
transform = node_path.getTransform()
|
||||
if not transform.hasMat():
|
||||
print(f"⚠️ 节点 {node_name} 没有有效变换矩阵")
|
||||
node_path.clearTransform()
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"⚠️ 获取节点 {node_name} 变换时出错: {e}")
|
||||
node_path.clearTransform()
|
||||
return
|
||||
|
||||
# 方法2: 检查矩阵是否奇异
|
||||
try:
|
||||
matrix = node_path.getMat()
|
||||
if matrix.isSingular():
|
||||
print(f"⚠️ 节点 {node_name} 有奇异矩阵")
|
||||
node_path.clearTransform()
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"⚠️ 检查节点 {node_name} 矩阵时出错: {e}")
|
||||
node_path.clearTransform()
|
||||
return
|
||||
|
||||
# 方法3: 检查缩放值
|
||||
try:
|
||||
scale = node_path.getScale()
|
||||
# 检查是否包含无效值
|
||||
if (scale.getX() != scale.getX() or # NaN检查
|
||||
scale.getY() != scale.getY() or
|
||||
scale.getZ() != scale.getZ() or
|
||||
abs(scale.getX()) > 1e10 or # 无穷大检查
|
||||
abs(scale.getY()) > 1e10 or
|
||||
abs(scale.getZ()) > 1e10):
|
||||
print(f"⚠️ 节点 {node_name} 有无效缩放值: {scale}")
|
||||
node_path.setScale(1.0)
|
||||
return
|
||||
|
||||
# 检查零缩放
|
||||
if (abs(scale.getX()) < 1e-10 or
|
||||
abs(scale.getY()) < 1e-10 or
|
||||
abs(scale.getZ()) < 1e-10):
|
||||
print(f"⚠️ 节点 {node_name} 有零或近零缩放: {scale}")
|
||||
node_path.setScale(1.0)
|
||||
return
|
||||
except Exception as e:
|
||||
print(f"⚠️ 检查节点 {node_name} 缩放时出错: {e}")
|
||||
try:
|
||||
node_path.setScale(1.0)
|
||||
except:
|
||||
node_path.clearTransform()
|
||||
return
|
||||
|
||||
# 方法4: 检查位置值
|
||||
try:
|
||||
pos = node_path.getPos()
|
||||
# 检查是否包含无效值
|
||||
if (pos.getX() != pos.getX() or # NaN检查
|
||||
pos.getY() != pos.getY() or
|
||||
pos.getZ() != pos.getZ() or
|
||||
abs(pos.getX()) > 1e10 or # 无穷大检查
|
||||
abs(pos.getY()) > 1e10 or
|
||||
abs(pos.getZ()) > 1e10):
|
||||
print(f"⚠️ 节点 {node_name} 有无效位置值: {pos}")
|
||||
node_path.setPos(0, 0, 0)
|
||||
except Exception as e:
|
||||
print(f"⚠️ 检查节点 {node_name} 位置时出错: {e}")
|
||||
try:
|
||||
node_path.setPos(0, 0, 0)
|
||||
except:
|
||||
pass
|
||||
|
||||
# 方法5: 检查旋转值
|
||||
try:
|
||||
hpr = node_path.getHpr()
|
||||
# 检查是否包含无效值
|
||||
if (hpr.getX() != hpr.getX() or # NaN检查
|
||||
hpr.getY() != hpr.getY() or
|
||||
hpr.getZ() != hpr.getZ()):
|
||||
print(f"⚠️ 节点 {node_name} 有无效旋转值: {hpr}")
|
||||
node_path.setHpr(0, 0, 0)
|
||||
except Exception as e:
|
||||
print(f"⚠️ 检查节点 {node_name} 旋转时出错: {e}")
|
||||
try:
|
||||
node_path.setHpr(0, 0, 0)
|
||||
except:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 修复节点 {node_path.getName()} 变换时出错: {e}")
|
||||
try:
|
||||
node_path.clearTransform()
|
||||
except:
|
||||
pass
|
||||
|
||||
def _safeReparentTo(self, child_node, parent_node):
|
||||
"""安全地重新设置父节点,避免矩阵问题"""
|
||||
try:
|
||||
if child_node.isEmpty() or parent_node.isEmpty():
|
||||
print("⚠️ 空节点无法挂载")
|
||||
return
|
||||
|
||||
# 在重新设置父节点前验证和修复变换
|
||||
self._fixNodeTransform(child_node)
|
||||
|
||||
# 使用 wrtReparentTo 避免变换问题
|
||||
child_node.wrtReparentTo(parent_node)
|
||||
print(f"✅ 节点 {child_node.getName()} 已安全挂载到 {parent_node.getName()}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 安全挂载节点失败: {e}")
|
||||
# 备用方案:先清除变换再挂载
|
||||
try:
|
||||
child_node.clearTransform()
|
||||
child_node.reparentTo(parent_node)
|
||||
print(f"✅ 使用备用方案挂载节点: {child_node.getName()}")
|
||||
except Exception as e2:
|
||||
print(f"❌ 备用方案也失败: {e2}")
|
||||
# 最后的备用方案:创建新节点并复制内容
|
||||
try:
|
||||
new_node = parent_node.attachNewNode(child_node.getName())
|
||||
child_node.copyTo(new_node)
|
||||
child_node.removeNode()
|
||||
print(f"✅ 使用最终备用方案挂载节点: {new_node.getName()}")
|
||||
except Exception as e3:
|
||||
print(f"❌ 所有方案都失败: {e3}")
|
||||
|
||||
def _applyMaterialsToModel(self, model):
|
||||
"""递归应用材质到模型的所有GeomNode"""
|
||||
|
||||
def apply_material(node_path, depth=0):
|
||||
indent = " " * depth
|
||||
try:
|
||||
print(f"{indent}处理节点: {node_path.getName()}")
|
||||
print(f"{indent}节点类型: {node_path.node().__class__.__name__}")
|
||||
|
||||
if isinstance(node_path.node(), GeomNode):
|
||||
print(f"{indent}发现GeomNode,处理材质")
|
||||
geom_node = node_path.node()
|
||||
|
||||
# 检查所有几何体的状态
|
||||
has_color = False
|
||||
color = None
|
||||
|
||||
# 首先检查节点自身的状态
|
||||
node_state = node_path.getState()
|
||||
if node_state.hasAttrib(MaterialAttrib.getClassType()):
|
||||
mat_attrib = node_state.getAttrib(MaterialAttrib.getClassType())
|
||||
node_material = mat_attrib.getMaterial()
|
||||
if node_material:
|
||||
if node_material.hasBaseColor():
|
||||
color = node_material.getBaseColor()
|
||||
has_color = True
|
||||
print(f"{indent}从节点材质获取基础颜色: {color}")
|
||||
elif node_material.hasDiffuse():
|
||||
color = node_material.getDiffuse()
|
||||
has_color = True
|
||||
print(f"{indent}从节点材质获取漫反射颜色: {color}")
|
||||
|
||||
# 检查几何体材质
|
||||
if not has_color:
|
||||
for i in range(geom_node.getNumGeoms()):
|
||||
try:
|
||||
geom = geom_node.getGeom(i)
|
||||
state = geom_node.getGeomState(i)
|
||||
|
||||
# 检查材质属性
|
||||
if state.hasAttrib(MaterialAttrib.getClassType()):
|
||||
mat_attrib = state.getAttrib(MaterialAttrib.getClassType())
|
||||
orig_material = mat_attrib.getMaterial()
|
||||
if orig_material:
|
||||
if orig_material.hasBaseColor():
|
||||
color = orig_material.getBaseColor()
|
||||
has_color = True
|
||||
print(f"{indent}从几何体材质获取基础颜色: {color}")
|
||||
break
|
||||
elif orig_material.hasDiffuse():
|
||||
color = orig_material.getDiffuse()
|
||||
has_color = True
|
||||
print(f"{indent}从几何体材质获取漫反射颜色: {color}")
|
||||
break
|
||||
|
||||
# 检查颜色属性
|
||||
if not has_color and state.hasAttrib(ColorAttrib.getClassType()):
|
||||
color_attrib = state.getAttrib(ColorAttrib.getClassType())
|
||||
if not color_attrib.isOff():
|
||||
color = color_attrib.getColor()
|
||||
has_color = True
|
||||
print(f"{indent}从颜色属性获取: {color}")
|
||||
break
|
||||
except Exception as geom_error:
|
||||
print(f"{indent}处理几何体 {i} 时出错: {geom_error}")
|
||||
continue
|
||||
|
||||
# 创建新材质
|
||||
material = Material()
|
||||
if has_color and color:
|
||||
print(f"{indent}应用找到的颜色: {color}")
|
||||
try:
|
||||
# 确保颜色值有效
|
||||
if (color.getX() == color.getX() and color.getY() == color.getY() and
|
||||
color.getZ() == color.getZ() and color.getW() == color.getW()):
|
||||
material.setBaseColor(color)
|
||||
material.setDiffuse(color)
|
||||
node_path.setColor(color)
|
||||
else:
|
||||
print(f"{indent}⚠️ 颜色值无效,使用默认颜色")
|
||||
material.setBaseColor((0.8, 0.8, 0.8, 1.0))
|
||||
material.setDiffuse((0.8, 0.8, 0.8, 1.0))
|
||||
except Exception as color_error:
|
||||
print(f"{indent}设置颜色时出错: {color_error}")
|
||||
material.setBaseColor((0.8, 0.8, 0.8, 1.0))
|
||||
material.setDiffuse((0.8, 0.8, 0.8, 1.0))
|
||||
else:
|
||||
print(f"{indent}使用默认颜色")
|
||||
material.setBaseColor((0.8, 0.8, 0.8, 1.0))
|
||||
material.setDiffuse((0.8, 0.8, 0.8, 1.0))
|
||||
|
||||
# 设置其他材质属性
|
||||
material.setAmbient((0.2, 0.2, 0.2, 1.0))
|
||||
material.setSpecular((0.5, 0.5, 0.5, 1.0))
|
||||
material.setShininess(32.0)
|
||||
|
||||
# 应用材质
|
||||
try:
|
||||
node_path.setMaterial(material, 1) # 1表示强制应用
|
||||
print(f"{indent}材质应用成功")
|
||||
except Exception as mat_error:
|
||||
print(f"{indent}⚠️ 应用材质时出错: {mat_error}")
|
||||
|
||||
print(f"{indent}几何体数量: {geom_node.getNumGeoms()}")
|
||||
|
||||
except Exception as node_error:
|
||||
print(f"{indent}处理节点 {node_path.getName()} 时出错: {node_error}")
|
||||
|
||||
# 递归处理子节点
|
||||
child_count = node_path.getNumChildren()
|
||||
print(f"{indent}子节点数量: {child_count}")
|
||||
for i in range(child_count):
|
||||
try:
|
||||
child = node_path.getChild(i)
|
||||
apply_material(child, depth + 1)
|
||||
except Exception as child_error:
|
||||
print(f"{indent}处理子节点 {i} 时出错: {child_error}")
|
||||
continue
|
||||
|
||||
# 应用材质
|
||||
print("\n开始递归应用材质...")
|
||||
try:
|
||||
apply_material(model)
|
||||
except Exception as e:
|
||||
print(f"应用材质时出错: {e}")
|
||||
print("=== 材质设置完成 ===\n")
|
||||
|
||||
def setupCollision(self, model):
|
||||
"""为模型设置碰撞检测(增强版本)"""
|
||||
try:
|
||||
if model.isEmpty():
|
||||
print("⚠️ 空模型无法设置碰撞检测")
|
||||
return None
|
||||
|
||||
# 验证模型变换
|
||||
self._fixNodeTransform(model)
|
||||
|
||||
# 创建碰撞节点
|
||||
cNode = CollisionNode(f'modelCollision_{model.getName()}')
|
||||
|
||||
# 设置碰撞掩码
|
||||
cNode.setIntoCollideMask(BitMask32.bit(2)) # 用于鼠标选择
|
||||
|
||||
# 如果启用了模型间碰撞检测,添加额外的掩码
|
||||
if (hasattr(self.world, 'collision_manager') and
|
||||
self.world.collision_manager.model_collision_enabled):
|
||||
# 同时设置模型间碰撞掩码
|
||||
current_mask = cNode.getIntoCollideMask()
|
||||
model_collision_mask = BitMask32.bit(6) # MODEL_COLLISION
|
||||
cNode.setIntoCollideMask(current_mask | model_collision_mask)
|
||||
print(f"为 {model.getName()} 启用模型间碰撞检测")
|
||||
|
||||
# 获取模型的边界
|
||||
bounds = model.getBounds()
|
||||
if bounds.isEmpty():
|
||||
print(f"⚠️ 模型 {model.getName()} 边界为空,使用默认碰撞体")
|
||||
# 使用默认的小球体
|
||||
cSphere = CollisionSphere(0, 0, 0, 1.0)
|
||||
else:
|
||||
try:
|
||||
center = bounds.getCenter()
|
||||
radius = bounds.getRadius()
|
||||
|
||||
# 确保半径不为零
|
||||
if radius <= 0 or radius != radius: # 检查NaN
|
||||
radius = 1.0
|
||||
print(f"⚠️ 模型 {model.getName()} 半径无效,使用默认半径 1.0")
|
||||
|
||||
# 确保中心点有效
|
||||
if (center.getX() != center.getX() or
|
||||
center.getY() != center.getY() or
|
||||
center.getZ() != center.getZ()):
|
||||
center = Point3(0, 0, 0)
|
||||
print(f"⚠️ 模型 {model.getName()} 中心点无效,使用默认中心点")
|
||||
|
||||
cSphere = CollisionSphere(center, radius)
|
||||
except Exception as e:
|
||||
print(f"⚠️ 创建碰撞体时出错: {e}")
|
||||
cSphere = CollisionSphere(0, 0, 0, 1.0)
|
||||
|
||||
cNode.addSolid(cSphere)
|
||||
|
||||
# 将碰撞节点附加到模型上
|
||||
try:
|
||||
cNodePath = model.attachNewNode(cNode)
|
||||
except Exception as e:
|
||||
print(f"⚠️ 附加碰撞节点时出错: {e}")
|
||||
# 创建一个新的节点来附加
|
||||
cNodePath = self.world.render.attachNewNode(cNode)
|
||||
cNodePath.reparentTo(model)
|
||||
|
||||
# 根据调试设置决定是否显示碰撞体
|
||||
if hasattr(self.world, 'debug_collision') and self.world.debug_collision:
|
||||
cNodePath.show()
|
||||
else:
|
||||
cNodePath.hide()
|
||||
|
||||
# 为模型添加碰撞相关标签
|
||||
model.setTag("has_collision", "true")
|
||||
try:
|
||||
model.setTag("collision_radius", str(bounds.getRadius() if not bounds.isEmpty() else 1.0))
|
||||
except:
|
||||
model.setTag("collision_radius", "1.0")
|
||||
|
||||
print(f"✅ 为模型 {model.getName()} 设置碰撞检测完成")
|
||||
|
||||
return cNodePath
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 为模型 {model.getName()} 设置碰撞检测失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def _applyModelScale(self, model, scale_factor):
|
||||
"""应用模型特定缩放
|
||||
|
||||
@ -728,6 +1125,11 @@ class SceneManager:
|
||||
gui_type = "2d_video_screen"
|
||||
else:
|
||||
gui_type = "video_screen"
|
||||
elif "info_panel" in name_lower:
|
||||
if "3d" in name_lower:
|
||||
gui_type = "info_panel_3d"
|
||||
else:
|
||||
gui_type = "info_panel"
|
||||
else:
|
||||
# 如果无法识别类型,跳过该元素
|
||||
print(f"跳过无法识别类型的GUI元素: {gui_node.getName()}")
|
||||
@ -789,11 +1191,19 @@ class SceneManager:
|
||||
elif gui_type == "virtual_screen":
|
||||
if hasattr(gui_node, 'hasTag') and gui_node.hasTag("gui_text"):
|
||||
gui_info["text"] = gui_node.getTag("gui_text")
|
||||
elif gui_type == "info_panel":
|
||||
if hasattr(gui_node, 'hasTag') and gui_node.hasTag("panel_data"):
|
||||
gui_info["panel_data"] = gui_node.getTag("panel_data")
|
||||
elif gui_type in ["info_panel", "info_panel_3d"]:
|
||||
# 收集信息面板的特定信息
|
||||
if hasattr(gui_node, 'hasTag') and gui_node.hasTag("panel_id"):
|
||||
gui_info["panel_id"] = gui_node.getTag("panel_id")
|
||||
|
||||
# 收集背景图片信息
|
||||
if hasattr(gui_node, 'hasTag') and gui_node.hasTag("bg_image_path"):
|
||||
gui_info["bg_image_path"] = gui_node.getTag("bg_image_path")
|
||||
|
||||
# 如果是信息面板,收集面板数据
|
||||
if hasattr(gui_node, 'hasTag') and gui_node.hasTag("info_panel_data"):
|
||||
gui_info["panel_data"] = gui_node.getTag("info_panel_data")
|
||||
|
||||
# 修改 _collectGUIElementInfo 方法中的脚本收集部分
|
||||
if hasattr(self.world, 'script_manager') and self.world.script_manager:
|
||||
script_manager = self.world.script_manager
|
||||
# 获取挂载在此节点上的所有脚本
|
||||
@ -1451,10 +1861,36 @@ class SceneManager:
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def _shouldSkipNodeInTree(self, nodePath):
|
||||
"""判断节点是否应该在场景树中跳过显示"""
|
||||
# 跳过render节点的递归
|
||||
if nodePath.getName() == "render":
|
||||
return True
|
||||
|
||||
# 跳过光源节点
|
||||
if nodePath.getName() in ["alight", "dlight"]:
|
||||
return True
|
||||
|
||||
# 跳过相机节点
|
||||
if nodePath.getName() in ["camera", "cam"]:
|
||||
return True
|
||||
|
||||
# 跳过3D文本和3D图像节点
|
||||
if (hasattr(nodePath.node(), "hasTag") and
|
||||
nodePath.node().hasTag("gui_type") and
|
||||
nodePath.node().getTag("gui_type") in ["3d_text", "3d_image"]):
|
||||
return True
|
||||
|
||||
# 跳过辅助节点
|
||||
if nodePath.getName().startswith(("gizmo", "selectionBox")):
|
||||
return True
|
||||
|
||||
return False
|
||||
def _recreateGUIElementsFromData(self, gui_data):
|
||||
"""根据保存的GUI数据重新创建GUI元素"""
|
||||
try:
|
||||
gui_manager = getattr(self.world, 'gui_manager', None)
|
||||
info_panel_manager = getattr(self.world,'info_panel_manager',None)
|
||||
if not gui_manager:
|
||||
print("GUI管理器未找到,无法重建GUI元素")
|
||||
return
|
||||
@ -1474,6 +1910,9 @@ class SceneManager:
|
||||
text = gui_info.get("text", "")
|
||||
image_path = gui_info.get("image_path", "")
|
||||
video_path = gui_info.get("video_path","")
|
||||
bg_image_path = gui_info.get("bg_image_path", "") # 背景图片路径
|
||||
panel_id = gui_info.get("panel_id", name) # 信息面板ID
|
||||
panel_data = gui_info.get("panel_data", None) # 面板数据
|
||||
|
||||
# 检查是否已经处理过同名元素
|
||||
if name in processed_names:
|
||||
@ -1487,6 +1926,7 @@ class SceneManager:
|
||||
print(f" 缩放: {scale}")
|
||||
print(f" 文本: {text}")
|
||||
print(f" 图像路径: {image_path}")
|
||||
print(f" 背景图片路径: {bg_image_path}")
|
||||
print(f"视频路径:{video_path}")
|
||||
|
||||
# 根据类型创建相应的GUI元素
|
||||
@ -1563,6 +2003,54 @@ class SceneManager:
|
||||
size=scale,
|
||||
video_path=video_path
|
||||
)
|
||||
elif gui_type in ["info_panel", "info_panel_3d"]:
|
||||
# 重建信息面板
|
||||
if info_panel_manager:
|
||||
try:
|
||||
if panel_data:
|
||||
# 从序列化数据重建面板
|
||||
import json
|
||||
panel_data_obj = json.loads(panel_data)
|
||||
new_element = info_panel_manager.recreatePanelFromData(panel_data_obj)
|
||||
else:
|
||||
# 创建新的面板
|
||||
if gui_type == "info_panel_3d":
|
||||
# 3D信息面板
|
||||
new_element = info_panel_manager.create3DInfoPanel(
|
||||
panel_id=panel_id,
|
||||
position=tuple(position) if len(position) >= 3 else (0, 0, 0),
|
||||
size=tuple(scale) if len(scale) >= 2 else (1.0, 0.6),
|
||||
visible=not tags.get("hidden", False)
|
||||
)
|
||||
else:
|
||||
# 2D信息面板
|
||||
pos_2d = (position[0], position[2]) if len(position) >= 3 else (0, 0)
|
||||
new_element = info_panel_manager.createInfoPanel(
|
||||
panel_id=panel_id,
|
||||
position=pos_2d,
|
||||
size=tuple(scale) if len(scale) >= 2 else (1.0, 0.6),
|
||||
visible=not tags.get("hidden", False)
|
||||
)
|
||||
|
||||
# 设置背景图片
|
||||
if bg_image_path and hasattr(info_panel_manager, 'setPanelBackgroundImage'):
|
||||
info_panel_manager.setPanelBackgroundImage(panel_id, bg_image_path)
|
||||
|
||||
# 更新面板内容
|
||||
if text:
|
||||
title, content = text.split('\n', 1) if '\n' in text else ("信息面板", text)
|
||||
if gui_type == "info_panel_3d":
|
||||
info_panel_manager.update3DPanelContent(panel_id, title=title,
|
||||
content=content)
|
||||
else:
|
||||
info_panel_manager.updatePanelContent(panel_id, title=title,
|
||||
content=content)
|
||||
except Exception as e:
|
||||
print(f"重建信息面板失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
else:
|
||||
print("信息面板管理器未找到,无法重建信息面板")
|
||||
|
||||
|
||||
# 如果创建成功,设置属性
|
||||
|
||||
@ -10,16 +10,23 @@ import os
|
||||
import sys
|
||||
|
||||
from PyQt5.QtGui import QKeySequence, QIcon, QPalette, QColor
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineView
|
||||
from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QMenu, QAction,
|
||||
QDockWidget, QTreeWidget, QListWidget, QWidget, QVBoxLayout, QTreeWidgetItem,
|
||||
QLabel, QLineEdit, QFormLayout, QDoubleSpinBox, QScrollArea,
|
||||
QFileSystemModel, QButtonGroup, QToolButton, QPushButton, QHBoxLayout,
|
||||
QComboBox, QGroupBox, QInputDialog, QFileDialog, QMessageBox, QDesktopWidget, QDialog,
|
||||
QSpinBox, QFrame)
|
||||
from PyQt5.QtCore import Qt, QDir, QTimer, QSize, QPoint
|
||||
from PyQt5.QtCore import Qt, QDir, QTimer, QSize, QPoint, QUrl, QRect
|
||||
from direct.showbase.ShowBaseGlobal import aspect2d
|
||||
|
||||
from ui.widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget,CustomAssetsTreeWidget, CustomConsoleDockWidget
|
||||
try:
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineView
|
||||
WEB_ENGINE_AVAILABLE = True
|
||||
except ImportError:
|
||||
QWebEngineView = None
|
||||
WEB_ENGINE_AVAILABLE = False
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
"""主窗口类"""
|
||||
@ -439,11 +446,12 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# 移动工具
|
||||
self.moveTool = QToolButton()
|
||||
self.moveTool.setText("移动")
|
||||
icon_path = self.get_icon_path("move_tool.png")
|
||||
if icon_path and os.path.exists(icon_path):
|
||||
self.moveTool.setIcon(QIcon(icon_path))
|
||||
else:
|
||||
self.moveTool.setText('移动')
|
||||
# if icon_path and os.path.exists(icon_path):
|
||||
# self.moveTool.setIcon(QIcon(icon_path))
|
||||
# else:
|
||||
# self.moveTool.setText('移动')
|
||||
self.moveTool.setIconSize(QSize(16, 16))
|
||||
self.moveTool.setCheckable(True)
|
||||
self.moveTool.setToolTip("移动工具 (W)")
|
||||
@ -453,11 +461,12 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# 旋转工具
|
||||
self.rotateTool = QToolButton()
|
||||
self.rotateTool.setText("旋转")
|
||||
icon_path = self.get_icon_path("rotate_tool.png")
|
||||
if icon_path and os.path.exists(icon_path):
|
||||
self.rotateTool.setIcon(QIcon(icon_path))
|
||||
else:
|
||||
self.rotateTool.setText('旋转')
|
||||
# if icon_path and os.path.exists(icon_path):
|
||||
# self.rotateTool.setIcon(QIcon(icon_path))
|
||||
# else:
|
||||
# self.rotateTool.setText('旋转')
|
||||
self.rotateTool.setIconSize(QSize(16, 16))
|
||||
self.rotateTool.setCheckable(True)
|
||||
self.rotateTool.setToolTip("旋转工具 (E)")
|
||||
@ -467,11 +476,12 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# 缩放工具
|
||||
self.scaleTool = QToolButton()
|
||||
self.scaleTool.setText("缩放")
|
||||
icon_path = self.get_icon_path("scale_tool.png")
|
||||
if icon_path and os.path.exists(icon_path):
|
||||
self.scaleTool.setIcon(QIcon(icon_path))
|
||||
else:
|
||||
self.scaleTool.setText('缩放')
|
||||
# if icon_path and os.path.exists(icon_path):
|
||||
# self.scaleTool.setIcon(QIcon(icon_path))
|
||||
# else:
|
||||
# self.scaleTool.setText('缩放')
|
||||
self.scaleTool.setIconSize(QSize(16, 16))
|
||||
self.scaleTool.setCheckable(True)
|
||||
self.scaleTool.setToolTip("缩放工具 (R)")
|
||||
@ -724,6 +734,9 @@ class MainWindow(QMainWindow):
|
||||
self.infoPanelMenu.addSeparator()
|
||||
self.create3DSamplePanelAction = self.infoPanelMenu.addAction('创建3D实例面板')
|
||||
self.create3DSamplePanelAction.triggered.connect(self.onCreate3DSampleInfoPanel)
|
||||
# 添加网页浏览器菜单项
|
||||
self.webBrowserAction = self.infoPanelMenu.addAction("信息面板")
|
||||
self.webBrowserAction.triggered.connect(self.openWebBrowser)
|
||||
#
|
||||
# self.create3DSystemStatusPanelAction = self.infoPanelMenu.addAction('创建3D系统状态面板')
|
||||
# self.create3DSystemStatusPanelAction.triggered.connect(self.onCreate3DSystemStatusPanel)
|
||||
@ -1126,44 +1139,44 @@ class MainWindow(QMainWindow):
|
||||
self.bottomDock.setWidget(self.fileView)
|
||||
self.addDockWidget(Qt.BottomDockWidgetArea, self.bottomDock)
|
||||
|
||||
# 创建底部停靠控制台
|
||||
self.consoleDock = QDockWidget("控制台", self)
|
||||
self.consoleDock.setStyleSheet("""
|
||||
QDockWidget {
|
||||
background-color: #252538;
|
||||
color: #e0e0ff;
|
||||
border: 1px solid #3a3a4a;
|
||||
}
|
||||
QDockWidget::title {
|
||||
background-color: #2d2d44;
|
||||
padding: 0px 0px; /* 增加内边距,提供更多的垂直空间 */
|
||||
border-bottom: 0px solid #3a3a4a;
|
||||
}
|
||||
QDockWidget::close-button, QDockWidget::float-button {
|
||||
background-color: #8b5cf6;
|
||||
border: none;
|
||||
icon-size: 8px; /* 调整图标大小 */
|
||||
border-radius: 4px; /* 增加圆角 */
|
||||
}
|
||||
QDockWidget::close-button:hover, QDockWidget::float-button:hover {
|
||||
background-color: #7c3aed; /* 悬停时显示较亮的背景 */
|
||||
}
|
||||
QDockWidget::close-button:pressed, QDockWidget::float-button:pressed {
|
||||
background-color: #8b5cf6; /* 按下时显示紫色高亮 */
|
||||
}
|
||||
""")
|
||||
self.consoleView = CustomConsoleDockWidget(self.world)
|
||||
# 为控制台添加样式
|
||||
self.consoleView.setStyleSheet("""
|
||||
QTextEdit {
|
||||
background-color: #1e1e2e;
|
||||
color: #e0e0ff;
|
||||
border: 1px solid #3a3a4a;
|
||||
font-family: 'Consolas', 'Monaco', monospace;
|
||||
}
|
||||
""")
|
||||
self.consoleDock.setWidget(self.consoleView)
|
||||
self.addDockWidget(Qt.BottomDockWidgetArea, self.consoleDock)
|
||||
# # 创建底部停靠控制台
|
||||
# self.consoleDock = QDockWidget("控制台", self)
|
||||
# self.consoleDock.setStyleSheet("""
|
||||
# QDockWidget {
|
||||
# background-color: #252538;
|
||||
# color: #e0e0ff;
|
||||
# border: 1px solid #3a3a4a;
|
||||
# }
|
||||
# QDockWidget::title {
|
||||
# background-color: #2d2d44;
|
||||
# padding: 0px 0px; /* 增加内边距,提供更多的垂直空间 */
|
||||
# border-bottom: 0px solid #3a3a4a;
|
||||
# }
|
||||
# QDockWidget::close-button, QDockWidget::float-button {
|
||||
# background-color: #8b5cf6;
|
||||
# border: none;
|
||||
# icon-size: 8px; /* 调整图标大小 */
|
||||
# border-radius: 4px; /* 增加圆角 */
|
||||
# }
|
||||
# QDockWidget::close-button:hover, QDockWidget::float-button:hover {
|
||||
# background-color: #7c3aed; /* 悬停时显示较亮的背景 */
|
||||
# }
|
||||
# QDockWidget::close-button:pressed, QDockWidget::float-button:pressed {
|
||||
# background-color: #8b5cf6; /* 按下时显示紫色高亮 */
|
||||
# }
|
||||
# """)
|
||||
# self.consoleView = CustomConsoleDockWidget(self.world)
|
||||
# # 为控制台添加样式
|
||||
# self.consoleView.setStyleSheet("""
|
||||
# QTextEdit {
|
||||
# background-color: #1e1e2e;
|
||||
# color: #e0e0ff;
|
||||
# border: 1px solid #3a3a4a;
|
||||
# font-family: 'Consolas', 'Monaco', monospace;
|
||||
# }
|
||||
# """)
|
||||
# self.consoleDock.setWidget(self.consoleView)
|
||||
# self.addDockWidget(Qt.BottomDockWidgetArea, self.consoleDock)
|
||||
|
||||
# 将右侧停靠窗口设为标签形式
|
||||
# self.tabifyDockWidget(self.rightDock, self.scriptDock)
|
||||
@ -1173,7 +1186,7 @@ class MainWindow(QMainWindow):
|
||||
self.bottomDock.raise_()
|
||||
self.rightDock.raise_()
|
||||
self.scriptDock.raise_()
|
||||
self.consoleDock.raise_()
|
||||
# self.consoleDock.raise_()
|
||||
self.leftDock.raise_()
|
||||
# =========================================================================
|
||||
# ↓↓↓ 新增代码:为停靠窗口的标签栏(QTabBar)设置统一样式 ↓↓↓
|
||||
@ -1523,7 +1536,7 @@ class MainWindow(QMainWindow):
|
||||
print("已连接点击信号")
|
||||
|
||||
# 连接工具切换信号
|
||||
self.toolGroup.buttonClicked.connect(self.onToolChanged)
|
||||
#self.toolGroup.buttonClicked.connect(self.onToolChanged)
|
||||
|
||||
# 连接脚本菜单事件
|
||||
# self.createScriptAction.triggered.connect(self.onCreateScriptDialog)
|
||||
@ -1766,14 +1779,100 @@ class MainWindow(QMainWindow):
|
||||
def onToolChanged(self, button):
|
||||
"""工具切换事件处理"""
|
||||
if button.isChecked():
|
||||
tool_name = button.text()
|
||||
self.world.setCurrentTool(tool_name)
|
||||
print(f"工具栏: 选择了 {tool_name} 工具")
|
||||
tool_name = button.text().strip() # 添加strip()去除空格
|
||||
if tool_name: # 确保工具名称不为空
|
||||
self.world.setCurrentTool(tool_name)
|
||||
print(f"工具栏: 选择了 {tool_name} 工具")
|
||||
else:
|
||||
print("工具栏: 选择了空工具名称")
|
||||
else:
|
||||
self.world.setCurrentTool(None)
|
||||
print("工具栏: 取消选择工具")
|
||||
|
||||
# 在 MainWindow 类中添加以下方法
|
||||
def openWebBrowser(self):
|
||||
if not WEB_ENGINE_AVAILABLE:
|
||||
return None
|
||||
try:
|
||||
from PyQt5.QtWebEngineWidgets import QWebEngineView
|
||||
from PyQt5.QtWidgets import QDockWidget
|
||||
from PyQt5.QtCore import QUrl
|
||||
import os
|
||||
|
||||
main_window = self.world.main_window
|
||||
|
||||
# 尝试获取主窗口引用
|
||||
if main_window is None:
|
||||
print("🔍 尝试获取主窗口引用...")
|
||||
|
||||
# 检查各种可能的主窗口引用
|
||||
if hasattr(self.world, 'interface_manager'):
|
||||
print(f" - interface_manager 存在: {self.world.interface_manager}")
|
||||
if hasattr(self.world.interface_manager, 'main_window'):
|
||||
main_window = self.world.interface_manager.main_window
|
||||
print(f" - interface_manager.main_window: {main_window}")
|
||||
|
||||
if main_window is None and hasattr(self.world, 'main_window'):
|
||||
main_window = self.world.main_window
|
||||
print(f" - world.main_window: {main_window}")
|
||||
|
||||
# 如果仍然没有主窗口,尝试从树形控件获取
|
||||
if main_window is None and self.world.treeWidget:
|
||||
try:
|
||||
main_window = self.world.treeWidget.window()
|
||||
print(f" - 从 treeWidget 获取窗口: {main_window}")
|
||||
except:
|
||||
pass
|
||||
|
||||
if main_window is None:
|
||||
print("✗ 无法获取主窗口引用")
|
||||
return None
|
||||
else:
|
||||
print(f"✅ 使用传入的主窗口引用: {main_window}")
|
||||
|
||||
# 检查主窗口是否有效
|
||||
if not hasattr(main_window, 'addDockWidget'):
|
||||
print(f"✗ 主窗口引用无效,缺少 addDockWidget 方法")
|
||||
return None
|
||||
|
||||
# 检查是否已经存在浏览器视图
|
||||
for element in self.world.gui_elements:
|
||||
if hasattr(element, 'objectName') and element.objectName() == "WebBrowserView":
|
||||
print("⚠ 浏览器视图已经存在")
|
||||
# 将其前置显示
|
||||
element.show()
|
||||
element.raise_()
|
||||
return element
|
||||
|
||||
# 创建停靠窗口
|
||||
print(f"🔧 创建浏览器停靠窗口,父窗口: {main_window}")
|
||||
browser_dock = QDockWidget("信息面板", main_window)
|
||||
browser_dock.setObjectName("WebBrowserView")
|
||||
|
||||
# 创建 Web 视图
|
||||
self.web_view = QWebEngineView()
|
||||
|
||||
# 加载百度网页
|
||||
#print("🌐 加载百度网页: https://www.baidu.com")
|
||||
self.web_view.load(QUrl("https://www.bootstrapmb.com/item/15762/preview"))
|
||||
|
||||
# 设置内容
|
||||
browser_dock.setWidget(self.web_view)
|
||||
|
||||
# 添加到主窗口
|
||||
print("📍 将浏览器视图添加到主窗口")
|
||||
main_window.addDockWidget(Qt.RightDockWidgetArea, browser_dock)
|
||||
|
||||
# 添加到GUI元素列表以便管理
|
||||
self.gui_elements.append(browser_dock)
|
||||
|
||||
print("✓ 网页浏览器视图已创建并集成到项目中")
|
||||
return browser_dock
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ 创建浏览器视图失败: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def onCreateSampleInfoPanel(self):
|
||||
"""创建示例天气信息面板(模拟数据)"""
|
||||
@ -1793,13 +1892,14 @@ class MainWindow(QMainWindow):
|
||||
# 创建示例面板
|
||||
weather_panel = info_manager.createInfoPanel(
|
||||
panel_id=unique_id, # 使用唯一ID
|
||||
position=(0, 0),
|
||||
size=(1, 1),
|
||||
position=(1.32, 0.68),
|
||||
size=(1, 0.6),
|
||||
bg_color=(0.15, 0.25, 0.35, 0), # 蓝色背景
|
||||
border_color=(0.3, 0.5, 0.7, 0), # 蓝色边框
|
||||
title_color=(0.7, 0.9, 1.0, 1.0), # 浅蓝色标题
|
||||
content_color=(0.95, 0.95, 0.95, 1.0),
|
||||
font=font
|
||||
font=font,
|
||||
bg_image="/home/tiger/图片/内部信息框2@2x.png"
|
||||
)
|
||||
|
||||
# 更新面板标题
|
||||
@ -1925,7 +2025,7 @@ class MainWindow(QMainWindow):
|
||||
weather_panel = info_manager.create3DInfoPanel(
|
||||
panel_id=unique_id,
|
||||
position=(2, 0, 2), # 调整Z坐标避免与其他对象重叠
|
||||
size=(1, 1),
|
||||
size=(5, 5),
|
||||
bg_color=(0.15, 0.25, 0.35, 0.85), # 设置合适的透明度值
|
||||
border_color=(0.3, 0.5, 0.7, 1.0),
|
||||
title_color=(0.7, 0.9, 1.0, 1.0),
|
||||
|
||||
@ -2342,6 +2342,10 @@ class CustomTreeWidget(QTreeWidget):
|
||||
for child_node in model.getChildren():
|
||||
if child_node.hasTag("is_scene_element"):
|
||||
print(f"找到带标签的根节点:{child_node.getName()}")
|
||||
if (child_node.hasTag("gui_type")and
|
||||
child_node.getTag("gui_type") in ["3d_text","3d_image","video_screen"]):
|
||||
print(f"跳过3dGUI节点{child_node.getName()}")
|
||||
continue
|
||||
|
||||
# 为这个带标签的节点创建一个树项
|
||||
child_item = QTreeWidgetItem(root_item)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user