1
0
forked from Rowland/EG

保存系统持续完善

This commit is contained in:
Hector 2025-09-16 16:09:54 +08:00
parent 0ec780792e
commit a8082bd656
6 changed files with 719 additions and 67 deletions

View File

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

View File

@ -5,6 +5,7 @@ class ToolManager:
"""初始化工具管理器"""
self.world = world
self.currentTool = "选择" # 默认工具为选择工具
print(f"当前工具: {self.currentTool}")
def setCurrentTool(self, tool):
"""设置当前工具"""

View File

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

View File

@ -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("信息面板管理器未找到,无法重建信息面板")
# 如果创建成功,设置属性

View File

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

View File

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