diff --git a/core/InfoPanelManager.py b/core/InfoPanelManager.py index 9363d567..232e2139 100644 --- a/core/InfoPanelManager.py +++ b/core/InfoPanelManager.py @@ -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): diff --git a/core/tool_manager.py b/core/tool_manager.py index 4f5f4cdf..a0906215 100644 --- a/core/tool_manager.py +++ b/core/tool_manager.py @@ -5,6 +5,7 @@ class ToolManager: """初始化工具管理器""" self.world = world self.currentTool = "选择" # 默认工具为选择工具 + print(f"当前工具: {self.currentTool}") def setCurrentTool(self, tool): """设置当前工具""" diff --git a/core/world.py b/core/world.py index 0538877f..36481f66 100644 --- a/core/world.py +++ b/core/world.py @@ -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") diff --git a/scene/scene_manager.py b/scene/scene_manager.py index 7fcf50d1..d2d5d31c 100644 --- a/scene/scene_manager.py +++ b/scene/scene_manager.py @@ -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("信息面板管理器未找到,无法重建信息面板") # 如果创建成功,设置属性 diff --git a/ui/main_window.py b/ui/main_window.py index 92af669d..3f856329 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -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), diff --git a/ui/widgets.py b/ui/widgets.py index a2347cfb..d8cbe44f 100644 --- a/ui/widgets.py +++ b/ui/widgets.py @@ -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)