From f9bd83c876d71e09ff9388e5163482e808aa9b8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=A8=AA?= <2938139566@qq.com> Date: Wed, 17 Sep 2025 17:34:23 +0800 Subject: [PATCH] =?UTF-8?q?1.=E4=BC=98=E5=8C=96=E7=A2=B0=E6=92=9E=E9=9D=A2?= =?UTF-8?q?=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/collision_manager.py | 161 ++++- ui/property_panel.py | 1271 +++++++++++++++++++++---------------- 2 files changed, 853 insertions(+), 579 deletions(-) diff --git a/core/collision_manager.py b/core/collision_manager.py index 853ad45b..f39269fb 100644 --- a/core/collision_manager.py +++ b/core/collision_manager.py @@ -309,51 +309,81 @@ class CollisionManager: CollisionPlane, CollisionPolygon, Plane, Vec3 ) - bounds = model.getBounds() - if bounds.isEmpty(): + # 获取考虑变换后的实际尺寸和中心 + transformed_info = self._getTransformedModelInfo(model) + if not transformed_info: # 默认小球体 return CollisionSphere(Point3(0, 0, 0), 1.0) - center = bounds.getCenter() - radius = bounds.getRadius() + center = transformed_info['center'] + radius = transformed_info['radius'] + actual_size = transformed_info['size'] + scale_factor = transformed_info['scale_factor'] # 自动选择最适合的形状 if shape_type == 'auto': - shape_type = self._determineOptimalShape(model, bounds) + shape_type = self._determineOptimalShape(model, transformed_info) if shape_type == 'sphere': - return CollisionSphere(center, kwargs.get('radius', radius)) + # 优化球形碰撞体 + sphere_radius = kwargs.get('radius', radius) + + # 支持位置偏移 + pos_offset = kwargs.get('position_offset', Vec3(0, 0, 0)) + sphere_center = Point3(center.x + pos_offset.x, center.y + pos_offset.y, center.z + pos_offset.z) + + return CollisionSphere(sphere_center, sphere_radius) elif shape_type == 'box': - # 创建自定义尺寸的包围盒 - width = kwargs.get('width', bounds.getMax().x - bounds.getMin().x) - length = kwargs.get('length', bounds.getMax().y - bounds.getMin().y) - height = kwargs.get('height', bounds.getMax().z - bounds.getMin().z) + # 优化盒型碰撞体 - 更精确的尺寸和位置控制(考虑缩放) + # 获取自定义尺寸,如果没有提供则使用变换后的实际尺寸 + width = kwargs.get('width', actual_size.x) + length = kwargs.get('length', actual_size.y) + height = kwargs.get('height', actual_size.z) - # 计算盒子的最小和最大点 + # 支持位置偏移 + pos_offset = kwargs.get('position_offset', Vec3(0, 0, 0)) + box_center = Point3(center.x + pos_offset.x, center.y + pos_offset.y, center.z + pos_offset.z) + + # 计算盒子的最小和最大点(基于偏移后的中心) half_width = width / 2 half_length = length / 2 half_height = height / 2 - min_point = Point3(center.x - half_width, center.y - half_length, center.z - half_height) - max_point = Point3(center.x + half_width, center.y + half_length, center.z + half_height) + min_point = Point3(box_center.x - half_width, box_center.y - half_length, box_center.z - half_height) + max_point = Point3(box_center.x + half_width, box_center.y + half_length, box_center.z + half_height) return CollisionBox(min_point, max_point) elif shape_type == 'capsule': - # 创建自定义参数的胶囊体 - custom_height = kwargs.get('height', (bounds.getMax().z - bounds.getMin().z)) - custom_radius = kwargs.get('radius', min(bounds.getRadius() * 0.5, custom_height * 0.3)) + # 优化胶囊体碰撞 - 更合理的比例和位置控制(考虑缩放) + # 使用变换后的实际高度,或自定义高度 + custom_height = kwargs.get('height', actual_size.z) - # 计算胶囊体的两个端点 - point_a = Point3(center.x, center.y, center.z - custom_height/2 + custom_radius) - point_b = Point3(center.x, center.y, center.z + custom_height/2 - custom_radius) + # 更合理的半径计算:基于变换后模型宽度的平均值 + default_radius = min(actual_size.x, actual_size.y) / 2.5 # 稍微小于模型的宽度 + custom_radius = kwargs.get('radius', min(default_radius, custom_height * 0.4)) + + # 支持位置偏移 + pos_offset = kwargs.get('position_offset', Vec3(0, 0, 0)) + capsule_center = Point3(center.x + pos_offset.x, center.y + pos_offset.y, center.z + pos_offset.z) + + # 计算胶囊体的两个端点(确保半径不会超出高度) + effective_height = max(custom_height, custom_radius * 2.1) # 确保高度至少是半径的2倍多一点 + point_a = Point3(capsule_center.x, capsule_center.y, capsule_center.z - effective_height/2 + custom_radius) + point_b = Point3(capsule_center.x, capsule_center.y, capsule_center.z + effective_height/2 - custom_radius) return CollisionCapsule(point_a, point_b, custom_radius) elif shape_type == 'plane': - # 创建平面(适合地面、墙面) + # 优化平面碰撞 - 支持位置偏移和更灵活的法向量 normal = kwargs.get('normal', Vec3(0, 0, 1)) - point = kwargs.get('point', center) - plane = Plane(normal, point) + + # 支持位置偏移 + pos_offset = kwargs.get('position_offset', Vec3(0, 0, 0)) + plane_point = kwargs.get('point', Point3(center.x + pos_offset.x, center.y + pos_offset.y, center.z + pos_offset.z)) + + # 标准化法向量 + normal.normalize() + plane = Plane(normal, plane_point) return CollisionPlane(plane) elif shape_type == 'polygon': @@ -378,10 +408,10 @@ class CollisionManager: print("⚠️ 多边形至少需要3个顶点,回退到球体") return CollisionSphere(center, radius) - def _determineOptimalShape(self, model, bounds): + def _determineOptimalShape(self, model, transformed_info): """根据模型特征自动确定最适合的碰撞体形状""" - # 获取模型尺寸比例 - size = bounds.getMax() - bounds.getMin() + # 获取变换后的模型尺寸比例 + size = transformed_info['size'] max_dim = max(size.x, size.y, size.z) min_dim = min(size.x, size.y, size.z) @@ -410,6 +440,87 @@ class CollisionManager: else: # 其他用包围盒 return 'box' + def _getTransformedModelInfo(self, model): + """获取考虑变换后的模型信息 + + Args: + model: 模型节点 + + Returns: + dict: 包含变换后的尺寸、中心、半径等信息 + """ + try: + # 获取原始包围盒 + bounds = model.getBounds() + if bounds.isEmpty(): + return None + + # 获取模型的变换矩阵 + transform = model.getTransform() + scale = model.getScale() + + # 计算缩放因子(取三轴缩放的平均值) + scale_factor = (abs(scale.x) + abs(scale.y) + abs(scale.z)) / 3.0 + + # 获取原始尺寸 + original_size = bounds.getMax() - bounds.getMin() + + # 应用缩放到尺寸 + actual_size = Vec3( + original_size.x * abs(scale.x), + original_size.y * abs(scale.y), + original_size.z * abs(scale.z) + ) + + # 获取变换后的中心点(在世界坐标系中) + original_center = bounds.getCenter() + if hasattr(model, 'getPos'): + # 模型在世界坐标系中的位置 + world_center = model.getPos(model.getParent() if model.getParent() else model) + center = Point3(world_center.x, world_center.y, world_center.z) + else: + # 如果无法获取世界位置,使用原始中心 + center = original_center + + # 计算变换后的半径(考虑缩放) + original_radius = bounds.getRadius() + transformed_radius = original_radius * scale_factor + + # 调试信息 + print(f"🔍 模型 {model.getName()} 变换信息:") + print(f" 原始尺寸: {original_size}") + print(f" 缩放因子: {scale}") + print(f" 变换后尺寸: {actual_size}") + print(f" 变换后半径: {transformed_radius:.2f}") + + return { + 'center': center, + 'radius': transformed_radius, + 'size': actual_size, + 'scale_factor': scale_factor, + 'original_size': original_size, + 'scale': scale, + 'transform': transform + } + + except Exception as e: + print(f"⚠️ 获取模型变换信息失败: {e}") + # 回退到原始包围盒 + bounds = model.getBounds() + if bounds.isEmpty(): + return None + + original_size = bounds.getMax() - bounds.getMin() + return { + 'center': bounds.getCenter(), + 'radius': bounds.getRadius(), + 'size': original_size, + 'scale_factor': 1.0, + 'original_size': original_size, + 'scale': Vec3(1, 1, 1), + 'transform': None + } + def createMouseRay(self, screen_x, screen_y, mask_types=['SELECTABLE']): """创建鼠标射线""" # 组合掩码 diff --git a/ui/property_panel.py b/ui/property_panel.py index 063f1a25..251f1a6c 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -27,7 +27,7 @@ class PropertyPanelManager: """初始化属性面板管理器""" self.world = world self._propertyLayout = None - self._actor_cache={} + self._actor_cache = {} self._spherical_video_controls = {} # 初始化地形编辑参数 @@ -95,11 +95,11 @@ class PropertyPanelManager: # 当节点被拖拽后,需要根据新父节点的状态来更新可见性 self._syncEffectiveVisibility(node) self._syncSceneVisibility() + def _syncSceneVisibility(self): scene_root = self.world.render self._syncEffectiveVisibility(scene_root) - def updatePropertyPanel(self, item): """更新属性面板显示""" if not self._propertyLayout or not self._propertyLayout.parent(): @@ -154,7 +154,7 @@ class PropertyPanelManager: except TypeError: pass self.active_check.stateChanged.connect( - lambda state,m = model:self._setUserVisible(m,state == Qt.Checked) + lambda state, m=model: self._setUserVisible(m, state == Qt.Checked) ) # nameLabel = QLabel("名称:") # nameEdit = QLineEdit(itemText) @@ -163,12 +163,12 @@ class PropertyPanelManager: # 获取节点对象 model = item.data(0, Qt.UserRole) - if self._isTerrainNode(model,item): - self._showTerrainProperties(model,item) - elif model and hasattr(model,'getTag') and model.getTag("element_type") == "cesium_tileset": - self._showCesiumTilesetProperties(model,item) + if self._isTerrainNode(model, item): + self._showTerrainProperties(model, item) + elif model and hasattr(model, 'getTag') and model.getTag("element_type") == "cesium_tileset": + self._showCesiumTilesetProperties(model, item) elif model and hasattr(model, 'getTag') and model.getTag("gui_type"): - self.updateGUIPropertyPanel(model, item) + self.updateGUIPropertyPanel(model, item) elif model and hasattr(model, 'getTag') and model.getTag("light_type"): self.updateLightPropertyPanel(model) elif model: @@ -183,23 +183,23 @@ class PropertyPanelManager: if propertyWidget: propertyWidget.update() - def _isTerrainNode(self,node,item): + def _isTerrainNode(self, node, item): """检查是否是地形节点""" - item_data = item.data(0,Qt.UserRole+1) + item_data = item.data(0, Qt.UserRole + 1) if item_data == "terrain": return True - if hasattr(self.world,'terrain_manager') and self.world.terrain_manager.terrains: + if hasattr(self.world, 'terrain_manager') and self.world.terrain_manager.terrains: for terrain_info in self.world.terrain_manager.terrains: if terrain_info['node'] == node: return True return False - def _showTerrainProperties(self,terrain_node,item): + def _showTerrainProperties(self, terrain_node, item): """显示地形属性面板""" try: terrain_info = None - if hasattr(self.world,'terrain_manager'): + if hasattr(self.world, 'terrain_manager'): for info in self.world.terrain_manager.terrains: if info['node'] == terrain_node: terrain_info = info @@ -212,28 +212,28 @@ class PropertyPanelManager: info_group = QGroupBox("地形信息") info_layout = QGridLayout() - info_layout.addWidget(QLabel("名称:"),0,0) - name_label = QLabel(terrain_info.get('name','未知')) - info_layout.addWidget(name_label,0,1) + info_layout.addWidget(QLabel("名称:"), 0, 0) + name_label = QLabel(terrain_info.get('name', '未知')) + info_layout.addWidget(name_label, 0, 1) - info_layout.addWidget(QLabel("类型:"),1,0) - type_label = QLabel("高度图地形"if terrain_info.get('heightmap') else "平面地形") - info_layout.addWidget(type_label,1,1) + info_layout.addWidget(QLabel("类型:"), 1, 0) + type_label = QLabel("高度图地形" if terrain_info.get('heightmap') else "平面地形") + info_layout.addWidget(type_label, 1, 1) if terrain_info.get('heightmap'): - info_layout.addWidget(QLabel("高度图:"),2,0) + info_layout.addWidget(QLabel("高度图:"), 2, 0) heightmap_label = QLabel(os.path.basename(terrain_info['heightmap'])) heightmap_label.setWordWrap(True) - info_layout.addWidget(heightmap_label,2,1) + info_layout.addWidget(heightmap_label, 2, 1) info_group.setLayout(info_layout) self._propertyLayout.addWidget(info_group) - #变换属性 + # 变换属性 self._updateTerrainTransformPanel(terrain_node) - #地形编辑控制面板 + # 地形编辑控制面板 self._createTerrainEditPanel(terrain_info) - #材质属性 - self._updateTerrainMaterialPanel(terrain_node,terrain_info) + # 材质属性 + self._updateTerrainMaterialPanel(terrain_node, terrain_info) # #删除按钮 # delete_btn = QPushButton("删除地形") @@ -257,7 +257,7 @@ class PropertyPanelManager: import traceback traceback.print_exc() - def _updateTerrainTransformPanel(self,terrain_node): + def _updateTerrainTransformPanel(self, terrain_node): """更新地形变化属性面板""" try: transform_group = QGroupBox("变换 Transform") @@ -266,7 +266,7 @@ class PropertyPanelManager: pos = terrain_node.getPos() scale = terrain_node.getScale() - transform_layout.addWidget(QLabel("位置"),0,0) + transform_layout.addWidget(QLabel("位置"), 0, 0) x_label = QLabel("X") y_label = QLabel("Y") @@ -275,16 +275,16 @@ class PropertyPanelManager: y_label.setAlignment(Qt.AlignCenter) z_label.setAlignment(Qt.AlignCenter) - transform_layout.addWidget(x_label,0,1) - transform_layout.addWidget(y_label,0,2) - transform_layout.addWidget(z_label,0,3) + transform_layout.addWidget(x_label, 0, 1) + transform_layout.addWidget(y_label, 0, 2) + transform_layout.addWidget(z_label, 0, 3) self.pos_x = QDoubleSpinBox() self.pos_y = QDoubleSpinBox() self.pos_z = QDoubleSpinBox() - for pos_widget in [self.pos_x,self.pos_y,self.pos_z]: - pos_widget.setRange(-1000000.0,1000000.0) + for pos_widget in [self.pos_x, self.pos_y, self.pos_z]: + pos_widget.setRange(-1000000.0, 1000000.0) self.pos_x.setValue(pos.getX()) self.pos_y.setValue(pos.getY()) self.pos_z.setValue(pos.getZ()) @@ -292,37 +292,40 @@ class PropertyPanelManager: def updateXPosition(value): terrain_node.setX(value) self.refreshModelValues(terrain_node) + self.pos_x.valueChanged.connect(updateXPosition) def updateYPosition(value): terrain_node.setY(value) self.refreshModelValues(terrain_node) + self.pos_y.valueChanged.connect(updateYPosition) def updateZPosition(value): terrain_node.setZ(value) self.refreshModelValues(terrain_node) + self.pos_z.valueChanged.connect(updateZPosition) - transform_layout.addWidget(self.pos_x,1,1) - transform_layout.addWidget(self.pos_y,1,2) - transform_layout.addWidget(self.pos_z,1,3) + transform_layout.addWidget(self.pos_x, 1, 1) + transform_layout.addWidget(self.pos_y, 1, 2) + transform_layout.addWidget(self.pos_z, 1, 3) - transform_layout.addWidget(QLabel("旋转"),2,0) + transform_layout.addWidget(QLabel("旋转"), 2, 0) self.rot_x = QDoubleSpinBox() self.rot_y = QDoubleSpinBox() self.rot_z = QDoubleSpinBox() - for rot_widget in [self.rot_x,self.rot_y,self.rot_z]: - rot_widget.setRange(-360,360) + for rot_widget in [self.rot_x, self.rot_y, self.rot_z]: + rot_widget.setRange(-360, 360) self.rot_x.setValue(terrain_node.getH()) self.rot_y.setValue(terrain_node.getP()) self.rot_z.setValue(terrain_node.getR()) - self.rot_x.valueChanged.connect(lambda v:terrain_node.setH(v)) - self.rot_y.valueChanged.connect(lambda v:terrain_node.setP(v)) - self.rot_z.valueChanged.connect(lambda v:terrain_node.setR(v)) + self.rot_x.valueChanged.connect(lambda v: terrain_node.setH(v)) + self.rot_y.valueChanged.connect(lambda v: terrain_node.setP(v)) + self.rot_z.valueChanged.connect(lambda v: terrain_node.setR(v)) h_label = QLabel("H") p_label = QLabel("P") @@ -331,28 +334,28 @@ class PropertyPanelManager: p_label.setAlignment(Qt.AlignCenter) r_label.setAlignment(Qt.AlignCenter) - transform_layout.addWidget(h_label,2,1) - transform_layout.addWidget(p_label,2,2) - transform_layout.addWidget(r_label,2,3) - transform_layout.addWidget(self.rot_x,3,1) - transform_layout.addWidget(self.rot_y,3,2) - transform_layout.addWidget(self.rot_z,3,3) + transform_layout.addWidget(h_label, 2, 1) + transform_layout.addWidget(p_label, 2, 2) + transform_layout.addWidget(r_label, 2, 3) + transform_layout.addWidget(self.rot_x, 3, 1) + transform_layout.addWidget(self.rot_y, 3, 2) + transform_layout.addWidget(self.rot_z, 3, 3) - transform_layout.addWidget(QLabel("缩放"),4,0) + transform_layout.addWidget(QLabel("缩放"), 4, 0) self.scale_x = QDoubleSpinBox() self.scale_y = QDoubleSpinBox() self.scale_z = QDoubleSpinBox() - for scale_widget in [self.scale_x,self.scale_y,self.scale_z]: - scale_widget.setRange(-1000,1000) + for scale_widget in [self.scale_x, self.scale_y, self.scale_z]: + scale_widget.setRange(-1000, 1000) scale_widget.setSingleStep(0.1) self.scale_x.setValue(scale.getX()) self.scale_y.setValue(scale.getY()) self.scale_z.setValue(scale.getZ()) - self.scale_x.valueChanged.connect(lambda v:self._updateXScale(terrain_node,v)) - self.scale_y.valueChanged.connect(lambda v:self._updateYScale(terrain_node,v)) - self.scale_z.valueChanged.connect(lambda v:self._updateZScale(terrain_node,v)) + self.scale_x.valueChanged.connect(lambda v: self._updateXScale(terrain_node, v)) + self.scale_y.valueChanged.connect(lambda v: self._updateYScale(terrain_node, v)) + self.scale_z.valueChanged.connect(lambda v: self._updateZScale(terrain_node, v)) x_label2 = QLabel("X") y_label2 = QLabel("Y") @@ -361,12 +364,12 @@ class PropertyPanelManager: y_label2.setAlignment(Qt.AlignCenter) z_label2.setAlignment(Qt.AlignCenter) - transform_layout.addWidget(x_label2,4,1) - transform_layout.addWidget(y_label2,4,2) - transform_layout.addWidget(z_label2,4,3) - transform_layout.addWidget(self.scale_x,5,1) - transform_layout.addWidget(self.scale_y,5,2) - transform_layout.addWidget(self.scale_z,5,3) + transform_layout.addWidget(x_label2, 4, 1) + transform_layout.addWidget(y_label2, 4, 2) + transform_layout.addWidget(z_label2, 4, 3) + transform_layout.addWidget(self.scale_x, 5, 1) + transform_layout.addWidget(self.scale_y, 5, 2) + transform_layout.addWidget(self.scale_z, 5, 3) transform_group.setLayout(transform_layout) self._propertyLayout.addWidget(transform_group) @@ -424,14 +427,14 @@ class PropertyPanelManager: except Exception as e: print(f"创建地形编辑面板时出错: {e}") - def _onTerrainRadiusChanged(self,value): + def _onTerrainRadiusChanged(self, value): """地形编辑半径改变""" - if hasattr(self.world,'terrain_edit_radius'): + if hasattr(self.world, 'terrain_edit_radius'): self.world.terrain_edit_radius = float(value) - def _onTerrainStrengthChanged(self,value): + def _onTerrainStrengthChanged(self, value): """地形编辑强度改变""" - if hasattr(self.world,'terrain_edit_strength'): + if hasattr(self.world, 'terrain_edit_strength'): self.world.terrain_edit_strength = float(value) def _onTerrainOperationChanged(self, text): @@ -446,46 +449,46 @@ class PropertyPanelManager: self.world.terrain_edit_operation = operation_map.get(text, "add") print(f"地形编辑操作已设置为: {self.world.terrain_edit_operation}") - def _updateTerrainMaterialPanel(self,terrain_node,terrain_info): + def _updateTerrainMaterialPanel(self, terrain_node, terrain_info): """更新地形材质属性面板""" try: material_group = QGroupBox("材质属性") material_layout = QGridLayout() - #颜色设置 - material_layout.addWidget(QLabel("颜色:"),0,0) + # 颜色设置 + material_layout.addWidget(QLabel("颜色:"), 0, 0) color_button = QPushButton("选择颜色") - color_button.clicked.connect(lambda:self._selectTerrainColor(terrain_info)) - #纹理设置 - material_layout.addWidget(QLabel("纹理:"),1,0) + color_button.clicked.connect(lambda: self._selectTerrainColor(terrain_info)) + # 纹理设置 + material_layout.addWidget(QLabel("纹理:"), 1, 0) texture_button = QPushButton("选择纹理") - texture_button.clicked.connect(lambda :self._selectTerrainTexture(terrain_info)) - material_layout.addWidget(texture_button,1,1) + texture_button.clicked.connect(lambda: self._selectTerrainTexture(terrain_info)) + material_layout.addWidget(texture_button, 1, 1) material_group.setLayout(material_layout) self._propertyLayout.addWidget(material_group) except Exception as e: print(f"更新材质面板时出错{e}") - def _selectTerrainColor(self,terrain_info): + def _selectTerrainColor(self, terrain_info): try: from PyQt5.QtWidgets import QColorDialog from PyQt5.QtGui import QColor - current_color = QColor(255,255,255) + current_color = QColor(255, 255, 255) - color = QColorDialog.getColor(current_color,None,"选择地形颜色") + color = QColorDialog.getColor(current_color, None, "选择地形颜色") if color.isValid(): - r,g,b = color.red()/255.0,color.green()/255.0,color.blue()/255.0 - if hasattr(self.world,'terrain_manager'): - self.world.terrain_manager.setTerrainColor(terrain_info,(r,g,b)) + r, g, b = color.red() / 255.0, color.green() / 255.0, color.blue() / 255.0 + if hasattr(self.world, 'terrain_manager'): + self.world.terrain_manager.setTerrainColor(terrain_info, (r, g, b)) except Exception as e: print(f"选择地形颜色时出错: {e}") - def _selectTerrainTexture(self,terrain_info): + def _selectTerrainTexture(self, terrain_info): """选择地形纹理""" try: from PyQt5.QtWidgets import QFileDialog - file_path,_ = QFileDialog.getOpenFileName( + file_path, _ = QFileDialog.getOpenFileName( None, "选择地形纹理", "", @@ -493,8 +496,8 @@ class PropertyPanelManager: ) if file_path and os.path.exists(file_path): - if hasattr(self.world,'terrain_manager'): - success = self.world.terrain_manager.setTerrainTexture(terrain_info,file_path) + if hasattr(self.world, 'terrain_manager'): + success = self.world.terrain_manager.setTerrainTexture(terrain_info, file_path) if success: print(f"地形纹理已应用{file_path}") else: @@ -563,8 +566,9 @@ class PropertyPanelManager: for name in other_controls: if hasattr(self, name): setattr(self, name, None) - def _setUserVisible(self,node,visible): - node.setPythonTag("user_visible",visible) + + def _setUserVisible(self, node, visible): + node.setPythonTag("user_visible", visible) self._syncEffectiveVisibility(node) def _syncEffectiveVisibility(self, start_node): @@ -830,8 +834,9 @@ class PropertyPanelManager: except: pass setattr(self, name, None) - def _refreshWorldPos(self,model): - if not hasattr(self,'worldXSpin'): + + def _refreshWorldPos(self, model): + if not hasattr(self, 'worldXSpin'): return world = model.getPos(self.world.render) self._worldXSpin.setValue(world.x) @@ -1053,7 +1058,7 @@ class PropertyPanelManager: def _updateModelPropertyPanel(self, model): """更新模型属性面板""" - if hasattr(model,'getTag') and model.getTag("is_gui_element") == "1": + if hasattr(model, 'getTag') and model.getTag("is_gui_element") == "1": self.updateGUIPropertyPanel(model) return @@ -1090,16 +1095,19 @@ class PropertyPanelManager: def updateXPosition(value): model.setX(value) self.refreshModelValues(model) + self.pos_x.valueChanged.connect(updateXPosition) def updateYPosition(value): model.setY(value) self.refreshModelValues(model) + self.pos_y.valueChanged.connect(updateYPosition) def updateZPosition(value): model.setZ(value) self.refreshModelValues(model) + self.pos_z.valueChanged.connect(updateZPosition) # 创建并设置 X, Y, Z 标签居中 @@ -1283,7 +1291,7 @@ class PropertyPanelManager: model.setScale(current_scale.getX(), current_scale.getY(), value) self.refreshModelValues(model) - def updateGUIPropertyPanel(self, gui_element,item): + def updateGUIPropertyPanel(self, gui_element, item): """更新GUI元素属性面板""" self.clearPropertyPanel() @@ -1293,7 +1301,7 @@ class PropertyPanelManager: user_visible = gui_element.getPythonTag("user_visible") if user_visible is None: user_visible = True - gui_element.setPythonTag("user_visible",True) + gui_element.setPythonTag("user_visible", True) self.name_group = QGroupBox("物体名称") name_layout = QHBoxLayout() @@ -1371,7 +1379,7 @@ class PropertyPanelManager: # 变换属性组(合并位置和变换) if hasattr(gui_element, 'getPos'): # 根据GUI类型设置组名—— - if gui_type in ["button", "label", "entry","2d_image","2d_video_screen"]: + if gui_type in ["button", "label", "entry", "2d_image", "2d_video_screen"]: transform_group = QGroupBox("变换 Rect Transform") else: transform_group = QGroupBox("变换 Transform") @@ -1381,7 +1389,7 @@ class PropertyPanelManager: pos = gui_element.getPos() # 根据GUI类型决定位置编辑方式 - if gui_type in ["button", "label", "entry","2d_image","2d_video_screen"]: + if gui_type in ["button", "label", "entry", "2d_image", "2d_video_screen"]: # 2D GUI组件使用屏幕坐标 logical_x = pos.getX() / 0.1 # 反向转换为逻辑坐标 logical_z = pos.getZ() / 0.1 @@ -1421,35 +1429,38 @@ class PropertyPanelManager: transform_layout.addWidget(actualXLabel, 3, 1) transform_layout.addWidget(actualZLabel, 3, 2) - if gui_type in ["2d_image","2d_video_screen"]: + if gui_type in ["2d_image", "2d_video_screen"]: scale = gui_element.getScale() - width = scale.getX() if hasattr(scale, 'getX') else scale[0] if isinstance(scale,(tuple, list)) else scale - height = scale.getZ() if hasattr(scale, 'getZ') else scale[1] if isinstance(scale,(tuple, list)) and len(scale) > 1 else scale + width = scale.getX() if hasattr(scale, 'getX') else scale[0] if isinstance(scale, + (tuple, list)) else scale + height = scale.getZ() if hasattr(scale, 'getZ') else scale[1] if isinstance(scale, + (tuple, list)) and len( + scale) > 1 else scale - transform_layout.addWidget(QLabel("宽度"),4,0) + transform_layout.addWidget(QLabel("宽度"), 4, 0) self.scale_x = QDoubleSpinBox() - self.scale_x.setRange(0.1,100) + self.scale_x.setRange(0.1, 100) self.scale_x.setSingleStep(0.01) self.scale_x.setValue(width) - self.scale_x.valueChanged.connect(lambda v:self.editGUIScale(gui_element,"x",v)) - transform_layout.addWidget(self.scale_x,4,1) + self.scale_x.valueChanged.connect(lambda v: self.editGUIScale(gui_element, "x", v)) + transform_layout.addWidget(self.scale_x, 4, 1) - transform_layout.addWidget(QLabel("高度"),4,2) + transform_layout.addWidget(QLabel("高度"), 4, 2) self.scale_z = QDoubleSpinBox() - self.scale_z.setRange(0.1,10) + self.scale_z.setRange(0.1, 10) self.scale_z.setSingleStep(0.01) self.scale_z.setValue(height) - self.scale_z.valueChanged.connect(lambda v:self.editGUIScale(gui_element,"z",v)) - transform_layout.addWidget(self.scale_z,4,3) + self.scale_z.valueChanged.connect(lambda v: self.editGUIScale(gui_element, "z", v)) + transform_layout.addWidget(self.scale_z, 4, 3) else: - transform_layout.addWidget(QLabel("缩放"),4,0) + transform_layout.addWidget(QLabel("缩放"), 4, 0) scaleSpinBox = QDoubleSpinBox() - scaleSpinBox.setRange(0.01,100) + scaleSpinBox.setRange(0.01, 100) scaleSpinBox.setSingleStep(0.01) - scaleSpinBox.setValue(gui_element.getScale().getX()*2) - scaleSpinBox.valueChanged.connect(lambda v:self._update2DImageScale(gui_element,v)) - transform_layout.addWidget(scaleSpinBox,4,1) + scaleSpinBox.setValue(gui_element.getScale().getX() * 2) + scaleSpinBox.valueChanged.connect(lambda v: self._update2DImageScale(gui_element, v)) + transform_layout.addWidget(scaleSpinBox, 4, 1) else: # 3D GUI组件使用世界坐标 @@ -1532,12 +1543,11 @@ class PropertyPanelManager: self.scale_z.valueChanged.connect(lambda v: self.world.gui_manager.editGUIScale(gui_element, "z", v)) transform_layout.addWidget(self.scale_z, 3, 3) - transform_group.setLayout(transform_layout) self._propertyLayout.addWidget(transform_group) # 为2D图像和视频屏幕添加Sort属性 - if gui_type in ["2d_image","2d_video_screen","info_panel"]: + if gui_type in ["2d_image", "2d_video_screen", "info_panel"]: sort_group = QGroupBox("显示顺序") sort_layout = QGridLayout() @@ -1604,7 +1614,8 @@ class PropertyPanelManager: # 显示当前贴图路径(简化显示) current_texture_path = gui_element.getTag("texture_path") or "未设置" if current_texture_path != "未设置": - display_path = current_texture_path if len(current_texture_path) <= 10 else current_texture_path[:7] + "..." + display_path = current_texture_path if len(current_texture_path) <= 10 else current_texture_path[ + :7] + "..." else: display_path = current_texture_path texture_label = QLabel(display_path) @@ -1631,7 +1642,7 @@ class PropertyPanelManager: if success: # 保存路径到 Tag gui_element.setTag("texture_path", file_path) - gui_element.setTag("gui_image_path",file_path) + gui_element.setTag("gui_image_path", file_path) # 更新显示 texture_label.setText(file_path) # 可选:刷新场景树或其他 UI @@ -1642,7 +1653,7 @@ class PropertyPanelManager: image_group.setLayout(image_layout) self._propertyLayout.addWidget(image_group) - if gui_type in [ "2d_image"]: + if gui_type in ["2d_image"]: image_group = QGroupBox("2D图片设置") image_layout = QGridLayout() @@ -1653,7 +1664,8 @@ class PropertyPanelManager: # 显示当前贴图路径(简化显示) current_texture_path = gui_element.getTag("texture_path") or gui_element.getTag("image_path") or "未设置" if current_texture_path != "未设置": - display_path = current_texture_path if len(current_texture_path) <= 10 else current_texture_path[:7] + "..." + display_path = current_texture_path if len(current_texture_path) <= 10 else current_texture_path[ + :7] + "..." else: display_path = current_texture_path texture_label = QLabel(display_path) @@ -1693,12 +1705,12 @@ class PropertyPanelManager: # 添加弹性空间 if gui_type == "video_screen": - self._addVideoScreenProperties(gui_element,item) + self._addVideoScreenProperties(gui_element, item) elif gui_type == "2d_video_screen": - self._add2DVideoScreenProperties(gui_element,item) + self._add2DVideoScreenProperties(gui_element, item) elif gui_type == "spherical_video": self._addSphericalVideoProperties(gui_element) - elif gui_type in ['info_panel','info_panel_3d']: + elif gui_type in ['info_panel', 'info_panel_3d']: self._addInfoPanelProperties(gui_element) self._propertyLayout.addStretch() @@ -2811,6 +2823,7 @@ class PropertyPanelManager: import traceback traceback.print_exc() return False + def _selectInfoPanelBackgroundColor(self, info_panel, r_spin, g_spin, b_spin, a_spin): """选择信息面板背景颜色""" try: @@ -2981,7 +2994,7 @@ class PropertyPanelManager: content_size=size ) - #print(f"✓ 信息面板内容属性已更新: 颜色RGBA({r:.2f}, {g:.2f}, {b:.2f}, {a:.2f}), 大小{size:.3f}") + # print(f"✓ 信息面板内容属性已更新: 颜色RGBA({r:.2f}, {g:.2f}, {b:.2f}, {a:.2f}), 大小{size:.3f}") return True else: print("❌ 无法找到 info_panel_manager") @@ -3295,7 +3308,7 @@ class PropertyPanelManager: import traceback traceback.print_exc() - def _addVideoScreenProperties(self, video_screen,item): + def _addVideoScreenProperties(self, video_screen, item): """添加视频屏幕属性面板""" try: from PyQt5.QtWidgets import (QGroupBox, QGridLayout, QPushButton, QLabel, @@ -3362,7 +3375,7 @@ class PropertyPanelManager: # 加载新视频按钮 load_btn = QPushButton("📁 加载新视频...") - load_btn.clicked.connect(lambda: self._loadNewVideo(video_screen,item)) + load_btn.clicked.connect(lambda: self._loadNewVideo(video_screen, item)) self._propertyLayout.addWidget(load_btn) # 添加URL输入区域 @@ -3376,7 +3389,8 @@ class PropertyPanelManager: load_url_btn = QPushButton("加载URL") self._current_load_url_btn_3d = load_url_btn - load_url_btn.clicked.connect(lambda: self._loadVideoFromURLWithOpenCV_3D(video_screen, self.url_input.text().strip())) + load_url_btn.clicked.connect( + lambda: self._loadVideoFromURLWithOpenCV_3D(video_screen, self.url_input.text().strip())) url_layout.addWidget(load_url_btn) url_group.setLayout(url_layout) @@ -3385,7 +3399,7 @@ class PropertyPanelManager: except Exception as e: print(f"添加视频屏幕属性失败: {e}") - def _add2DVideoScreenProperties(self, video_screen,item): + def _add2DVideoScreenProperties(self, video_screen, item): """为2D视频屏幕添加属性控制面板""" try: from PyQt5.QtWidgets import (QGroupBox, QGridLayout, QPushButton, QLabel, @@ -3456,7 +3470,7 @@ class PropertyPanelManager: # 加载新视频按钮 load_btn = QPushButton("📁 加载新视频...") - load_btn.clicked.connect(lambda: self._loadNew2DVideo(video_screen,item)) + load_btn.clicked.connect(lambda: self._loadNew2DVideo(video_screen, item)) self._propertyLayout.addWidget(load_btn) # 添加URL输入区域 @@ -3470,7 +3484,8 @@ class PropertyPanelManager: load_url_btn = QPushButton("加载URL") self._current_load_url_btn_2d = load_url_btn - load_url_btn.clicked.connect(lambda: self._loadVideoFromURLWithOpenCV(video_screen, self.url_input.text().strip())) + load_url_btn.clicked.connect( + lambda: self._loadVideoFromURLWithOpenCV(video_screen, self.url_input.text().strip())) url_layout.addWidget(load_url_btn) url_group.setLayout(url_layout) @@ -3529,7 +3544,7 @@ class PropertyPanelManager: QMessageBox.critical(None, "视频流错误", error_msg) print(f"❌ 无法打开视频流: {url}") - #恢复按钮状态 + # 恢复按钮状态 if load_url_btn: load_url_btn.setText("加载URL") load_url_btn.setEnabled(True) @@ -3754,7 +3769,8 @@ class PropertyPanelManager: except Exception as e: print(f"❌ 停止视频失败: {e}") return False - def _loadNew2DVideo(self, video_screen,item): + + def _loadNew2DVideo(self, video_screen, item): """为2D视频屏幕加载新视频文件""" try: file_path, _ = QFileDialog.getOpenFileName( @@ -3769,7 +3785,7 @@ class PropertyPanelManager: print(f"成功加载新视频: {file_path}") # 刷新属性面板以显示新视频信息 self._stop2DVideo(video_screen) - self.updateGUIPropertyPanel(video_screen,item) + self.updateGUIPropertyPanel(video_screen, item) return True except Exception as e: print(f"加载新视频失败: {e}") @@ -3787,7 +3803,7 @@ class PropertyPanelManager: is_first_load = not video_screen.hasTag("video_path") or not video_screen.getTag("video_path") needs_second_call = False - if is_first_load and not hasattr(self,'_first_load_processed'): + if is_first_load and not hasattr(self, '_first_load_processed'): needs_second_call = True self._first_load_processed = True @@ -3941,7 +3957,8 @@ class PropertyPanelManager: print("检测到首次加载,再次调用_loadVideoFromURLWithOpenCV_3D以确保正确显示") from PyQt5.QtCore import QTimer QTimer.singleShot(100, lambda: self._loadVideoFromURLWithOpenCV_3D(video_screen, url)) - QTimer.singleShot(200,lambda :setattr(self,'_first_load_processed',False)if hasattr(self,'_first_load_processed')else None) + QTimer.singleShot(200, lambda: setattr(self, '_first_load_processed', False) if hasattr(self, + '_first_load_processed') else None) # 恢复按钮状态 update_button_text("加载URL") @@ -3979,7 +3996,7 @@ class PropertyPanelManager: return False - def _verifyVideoDisplay(self,video_screen,texture): + def _verifyVideoDisplay(self, video_screen, texture): try: applied_texture = video_screen.getTexture() if applied_texture and applied_texture.getName() == texture.getName(): @@ -3992,9 +4009,9 @@ class PropertyPanelManager: print(f"验证视频显示时出错:{e}") return False - def _retryLoadVideoFromURLWithOpenCV_3D(self,video_screen,url): + def _retryLoadVideoFromURLWithOpenCV_3D(self, video_screen, url): print("重新加载3D视频流") - return self._loadVideoFromURLWithOpenCV_3D(video_screen,url) + return self._loadVideoFromURLWithOpenCV_3D(video_screen, url) def _stop3DVideo(self, video_screen): """停止3D视频(内部方法)""" @@ -4031,8 +4048,7 @@ class PropertyPanelManager: print(f"❌ 停止3D视频失败: {e}") return False - - def _loadNewVideo(self,video_screen,item): + def _loadNewVideo(self, video_screen, item): try: file_path, _ = QFileDialog.getOpenFileName( None, @@ -4041,9 +4057,9 @@ class PropertyPanelManager: "视频文件(*.mp4 *.avi *.mov *.mkv *.webm *.ogg)" ) if file_path: - success = self.world.gui_manager.loadVideoFile(video_screen,file_path) + success = self.world.gui_manager.loadVideoFile(video_screen, file_path) if success: - self.updateGUIPropertyPanel(video_screen,item) + self.updateGUIPropertyPanel(video_screen, item) return True except Exception as e: print(f"加载新视频失败{e}") @@ -4090,7 +4106,7 @@ class PropertyPanelManager: if value == 0: value = 0.01 - if gui_type in ["3d_text", "3d_image","2d_image","video_screen","2d_video_screen"]: + if gui_type in ["3d_text", "3d_image", "2d_image", "video_screen", "2d_video_screen"]: # 3D元素处理 if axis == "x": new_scale = (value, current_scale.getY(), current_scale.getZ()) @@ -4126,17 +4142,17 @@ class PropertyPanelManager: traceback.print_exc() return False - def _update2DImageWidth(self,gui_element,width): + def _update2DImageWidth(self, gui_element, width): try: current_scale = gui_element.getScale() - width_scaled = width/2 + width_scaled = width / 2 height_scaled = current_scale.getZ() - gui_element.setScale(width_scaled,current_scale.getY(),height_scaled) + gui_element.setScale(width_scaled, current_scale.getY(), height_scaled) - if hasattr(gui_element,'_height_spinbox'): + if hasattr(gui_element, '_height_spinbox'): gui_element._height_spinbox.blockSignals(True) - gui_element._height_spinbox.setValue(height_scaled*2) + gui_element._height_spinbox.setValue(height_scaled * 2) gui_element._height_spinbox.blockSignals(False) print(f"✓ 更新2D图片宽度: {width}") @@ -4164,10 +4180,10 @@ class PropertyPanelManager: except Exception as e: print(f"✗ 更新2D图片高度失败: {e}") - def _update2DImageScale(self,gui_element,scale): + def _update2DImageScale(self, gui_element, scale): try: - scaled_value = scale/2 - gui_element.setScale(scaled_value,0,scaled_value) + scaled_value = scale / 2 + gui_element.setScale(scaled_value, 0, scaled_value) print(f"✓ 更新2D图片缩放: {scale}") except Exception as e: print(f"✗ 更新2D图片缩放失败: {e}") @@ -4252,7 +4268,7 @@ class PropertyPanelManager: # gui_element.setColor(*color) # print(f"✓ 更新3D文本字体颜色: {gui_type}") - from panda3d.core import Material,Vec4 + from panda3d.core import Material, Vec4 if not gui_element.hasMaterial(): material = Material(f"text-material-{gui_element.getName()}") material.setBaseColor(Vec4(color[0], color[1], color[2], color[3])) @@ -4355,7 +4371,6 @@ class PropertyPanelManager: traceback.print_exc() return False - def _updateScriptPropertyPanel(self, game_object): """更新脚本属性面板""" # 获取对象上的脚本 @@ -4373,7 +4388,7 @@ class PropertyPanelManager: enabled = script_component.enabled # 脚本名称和状态 - scriptLabel = QLabel(f"脚本 {i+1}:") + scriptLabel = QLabel(f"脚本 {i + 1}:") scriptInfo = QLabel(f"{script_name}") scriptInfo.setStyleSheet("color: green; font-weight: bold;" if enabled else "color: gray;") self._propertyLayout.addRow(scriptLabel, scriptInfo) @@ -4574,11 +4589,11 @@ class PropertyPanelManager: light_group.setLayout(light_layout) self._propertyLayout.addWidget(light_group) - def _updateLightPosition(self,light_object,node_path,axis,value): + def _updateLightPosition(self, light_object, node_path, axis, value): current_pos = light_object.pos - if axis=='x': - new_pos = Vec3(value,current_pos.getY(),current_pos.getZ()) + if axis == 'x': + new_pos = Vec3(value, current_pos.getY(), current_pos.getZ()) elif axis == 'y': new_pos = Vec3(current_pos.getX(), value, current_pos.getZ()) else: # z @@ -4589,73 +4604,73 @@ class PropertyPanelManager: # 同步更新场景节点位置(用于显示) node_path.setPos(new_pos) - def _updateLightRotation(self,light_object,node_path,axis,value): + def _updateLightRotation(self, light_object, node_path, axis, value): """更新光源旋转""" from panda3d.core import Vec3 current_hpr = node_path.getHpr() - if axis=='h': - new_hpr = Vec3(value,current_hpr.getY(),current_hpr.getZ()) - elif axis=='p': - new_hpr = Vec3(current_hpr.getX(),value,current_hpr.getZ()) + if axis == 'h': + new_hpr = Vec3(value, current_hpr.getY(), current_hpr.getZ()) + elif axis == 'p': + new_hpr = Vec3(current_hpr.getX(), value, current_hpr.getZ()) else: - new_hpr = Vec3(current_hpr.getX(),current_hpr.getY(),value) + new_hpr = Vec3(current_hpr.getX(), current_hpr.getY(), value) node_path.setHpr(new_hpr) - if hasattr(light_object,'direction'): + if hasattr(light_object, 'direction'): direction_mat = node_path.getMat() - new_direction = direction_mat.xformVec(Vec3(0,1,0)) + new_direction = direction_mat.xformVec(Vec3(0, 1, 0)) light_object.direction = new_direction print(f"光源旋转已更新:{axis}={value}") - def _updateLightEnergy(self,light_object,value): + def _updateLightEnergy(self, light_object, value): """更新光源强度""" light_object.energy = value - def _updateLightRadius(self,light_object,value): + def _updateLightRadius(self, light_object, value): """更新光源半径""" light_object.radius = value - def _updateLightFOV(self,light_Object,value): + def _updateLightFOV(self, light_Object, value): """更新聚光灯视野角度""" - if hasattr(light_Object,'fov'): + if hasattr(light_Object, 'fov'): light_Object.fov = value - def _updateLightTemperature(self,light_object,value): + def _updateLightTemperature(self, light_object, value): """更新光源色温""" light_object.set_color_from_temperature(value) - #保存色温值以便下次显示 - light_object._temperature=value + # 保存色温值以便下次显示 + light_object._temperature = value - def _updateLightInnerRadius(self,light_object,value): + def _updateLightInnerRadius(self, light_object, value): """更新点光源内半径""" - if hasattr(light_object,'inner_radius'): - light_object.inner_radius=value + if hasattr(light_object, 'inner_radius'): + light_object.inner_radius = value - def _updateLightShaowResolution(self,light_object,value): + def _updateLightShaowResolution(self, light_object, value): """更新阴影分辨率""" light_object.shadow_map_resolution = value - def _updateLightNearPlane(self,light_object,value): + def _updateLightNearPlane(self, light_object, value): """更新近平面距离""" light_object.near_plane = value - def _updateLightCastsShadows(self,light_object,casts_shadows): + def _updateLightCastsShadows(self, light_object, casts_shadows): """更新光源是否投射阴影""" light_object.casts_shadows = casts_shadows - def _updateLightScale(self,node_path,axis,value): + def _updateLightScale(self, node_path, axis, value): """更新光源节点缩放""" current_scale = node_path.getScale() - if axis=='x': - new_scale = Vec3(value,current_scale.getY(),current_scale.getZ()) - elif axis=='y': - new_scale = Vec3(current_scale.getX(),value,current_scale.getZ()) + if axis == 'x': + new_scale = Vec3(value, current_scale.getY(), current_scale.getZ()) + elif axis == 'y': + new_scale = Vec3(current_scale.getX(), value, current_scale.getZ()) else: - new_scale = Vec3(current_scale.getX(),current_scale.getY(),value) + new_scale = Vec3(current_scale.getX(), current_scale.getY(), value) node_path.setScale(new_scale) @@ -4683,8 +4698,7 @@ class PropertyPanelManager: return unique_names - - def _updateModelMaterialPanel(self,model): + def _updateModelMaterialPanel(self, model): """模型材质属性""" if model.is_empty(): print("警告: 无法在空的 NodePath 上查找材质") @@ -4950,8 +4964,6 @@ class PropertyPanelManager: # gloss_button.clicked.connect(lambda checked, mat=material: self._selectGlossTexture(mat)) # self._propertyLayout.addRow("光泽贴图:", gloss_button) - - # 在纹理按钮后添加当前贴图信息显示 current_row = self._displayCurrentTextures(material, material_layout, current_row) @@ -4983,7 +4995,7 @@ class PropertyPanelManager: # elif component == 'a': # Alpha分量处理 # self._updateMaterialTransparency(material, value) # return - #new_color = Vec4(current_color.x, current_color.y, current_color.z, value) + # new_color = Vec4(current_color.x, current_color.y, current_color.z, value) else: print(f"未知的颜色分量: {component}") return @@ -5026,18 +5038,18 @@ class PropertyPanelManager: except Exception as e: print(f"更新材质基础颜色失败: {e}") - def _updateMaterialTransparency(self,material,alpha_value): + def _updateMaterialTransparency(self, material, alpha_value): try: from panda3d.core import Vec4 - if hasattr(material,'emission'): - material.emission = Vec4(3,0,0,0) + if hasattr(material, 'emission'): + material.emission = Vec4(3, 0, 0, 0) print("设置透明着色器模型") - if hasattr(material,'shading_model_param0'): + if hasattr(material, 'shading_model_param0'): material.shading_model_param0 = alpha_value print(f"设置透明度参数{alpha_value}") - if hasattr(material,'base_color'): + if hasattr(material, 'base_color'): current_color = material.base_color - material.base_color = Vec4(current_color.x,current_color.y,current_color.z,alpha_value) + material.base_color = Vec4(current_color.x, current_color.y, current_color.z, alpha_value) print(f"更新基础颜色透明度{alpha_value}") print(f"材质透明度已更新:{alpha_value}") except Exception as e: @@ -5101,7 +5113,6 @@ class PropertyPanelManager: print(f"检查材质状态时出错: {e}") return "未知材质类型(可尝试编辑)" - def _getTextureModeString(self, mode): """获取纹理模式的字符串表示""" from panda3d.core import TextureStage @@ -5164,7 +5175,7 @@ class PropertyPanelManager: try: base_color = material.get_base_color() if base_color is not None: - #print(f"✓ 通过get_base_color()获取: {base_color}") + # print(f"✓ 通过get_base_color()获取: {base_color}") return base_color except: pass @@ -5213,164 +5224,162 @@ class PropertyPanelManager: print(f"✗ 获取材质基础颜色失败: {e}") return None - def _selectDiffuseTexture(self,material_title): + def _selectDiffuseTexture(self, material_title): """漫反射贴图""" - from PyQt5.QtWidgets import QFileDialog + from PyQt5.QtWidgets import QFileDialog import os - file_dialog = QFileDialog(None,"选择漫反射贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") + file_dialog = QFileDialog(None, "选择漫反射贴图", "", "图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") if file_dialog.exec_(): filename = file_dialog.selectedFiles()[0] if filename: # 使用跨平台路径标准化 normalized_path = util.normalize_model_path(filename) - self._applyDiffuseTexture(material_title,normalized_path) + self._applyDiffuseTexture(material_title, normalized_path) print(f"已选择漫反射贴图:{filename} -> 标准化路径:{normalized_path}") - def _selectNormalTexture(self,material): + def _selectNormalTexture(self, material): """选择法线贴图""" - from PyQt5.QtWidgets import QFileDialog + from PyQt5.QtWidgets import QFileDialog - file_dialog = QFileDialog(None,"选择法线贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") + file_dialog = QFileDialog(None, "选择法线贴图", "", "图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") if file_dialog.exec_(): filename = file_dialog.selectedFiles()[0] if filename: # 使用跨平台路径标准化 normalized_path = util.normalize_model_path(filename) - self._applyNormalTexture(material,normalized_path) + self._applyNormalTexture(material, normalized_path) print(f"已选择法线贴图:{filename} -> 标准化路径:{normalized_path}") - def _selectRoughnessTexture(self,material): + def _selectRoughnessTexture(self, material): """选择粗糙度贴图""" from PyQt5.QtWidgets import QFileDialog - file_dialog = QFileDialog(None,"选择粗糙度贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") + file_dialog = QFileDialog(None, "选择粗糙度贴图", "", "图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") if file_dialog.exec_(): filename = file_dialog.selectedFiles()[0] if filename: # 使用跨平台路径标准化 normalized_path = util.normalize_model_path(filename) - self._applyRoughnessTexture_FINAL(material,normalized_path) + self._applyRoughnessTexture_FINAL(material, normalized_path) print(f"已选择粗糙度贴图: {filename} -> 标准化路径:{normalized_path}") - def _selectMetallicTexture(self,material): + def _selectMetallicTexture(self, material): """选择金属性贴图""" from PyQt5.QtWidgets import QFileDialog - file_dialog = QFileDialog(None,"选择金属性贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") + file_dialog = QFileDialog(None, "选择金属性贴图", "", "图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") if file_dialog.exec_(): filename = file_dialog.selectedFiles()[0] if filename: # 使用跨平台路径标准化 normalized_path = util.normalize_model_path(filename) - self._applyMetallicTexture_NEW(material,normalized_path) + self._applyMetallicTexture_NEW(material, normalized_path) print(f"已选择金属性贴图: {filename} -> 标准化路径:{normalized_path}") - #IOR贴图 - def _selectIORTexture(self,material): + # IOR贴图 + def _selectIORTexture(self, material): """选择IOR贴图""" from PyQt5.QtWidgets import QFileDialog - file_dialong = QFileDialog(None,"选择IOR贴图","","图像(*.png *.jpg *.jpeg *.tga *.bmp)") + file_dialong = QFileDialog(None, "选择IOR贴图", "", "图像(*.png *.jpg *.jpeg *.tga *.bmp)") if file_dialong.exec_(): filename = file_dialong.selectedFiles()[0] if filename: # 使用跨平台路径标准化 normalized_path = util.normalize_model_path(filename) - self._applyIORTexture(material,normalized_path) + self._applyIORTexture(material, normalized_path) print(f"已选择IOR贴图:{filename} -> 标准化路径:{normalized_path}") - def _selectParallaxTexture(self,material): + def _selectParallaxTexture(self, material): """选择视差贴图""" from PyQt5.QtWidgets import QFileDialog - file_dialog = QFileDialog(None,"选择视差贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") + file_dialog = QFileDialog(None, "选择视差贴图", "", "图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") if file_dialog.exec_(): filename = file_dialog.selectedFiles()[0] if filename: # 使用跨平台路径标准化 normalized_path = util.normalize_model_path(filename) - self._applyParallaxTexture(material,normalized_path) + self._applyParallaxTexture(material, normalized_path) print(f"已选择视差贴图:{filename} -> 标准化路径:{normalized_path}") - def _selectEmissionTexture(self,material): + def _selectEmissionTexture(self, material): """选择自发光贴图""" from PyQt5.QtWidgets import QFileDialog - file_dialog = QFileDialog(None,"选择自发光贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") + file_dialog = QFileDialog(None, "选择自发光贴图", "", "图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") if file_dialog.exec_(): filename = file_dialog.selectedFiles()[0] if filename: # 使用跨平台路径标准化 normalized_path = util.normalize_model_path(filename) - self._applyEmissionTexture(material,normalized_path) + self._applyEmissionTexture(material, normalized_path) print(f"已选择自发光贴图:{filename} -> 标准化路径:{normalized_path}") - def _selectAOTexture(self,material): + def _selectAOTexture(self, material): """选择环境光遮蔽贴图""" from PyQt5.QtWidgets import QFileDialog - file_dialog = QFileDialog(None,"选择AO贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") + file_dialog = QFileDialog(None, "选择AO贴图", "", "图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") if file_dialog.exec_(): filename = file_dialog.selectedFiles()[0] if filename: # 使用跨平台路径标准化 normalized_path = util.normalize_model_path(filename) - self._applyAOTexture(material,normalized_path) + self._applyAOTexture(material, normalized_path) print(f"已选择AO贴图:{filename} -> 标准化路径:{normalized_path}") - def _selectAlphaTexture(self,material): + def _selectAlphaTexture(self, material): """选择透明度贴图""" from PyQt5.QtWidgets import QFileDialog - file_dialog = QFileDialog(None,"选择透明度贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") + file_dialog = QFileDialog(None, "选择透明度贴图", "", "图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") if file_dialog.exec_(): filename = file_dialog.selectedFiles()[0] if filename: # 使用跨平台路径标准化 normalized_path = util.normalize_model_path(filename) - self._applyAlphaTexture(material,normalized_path) + self._applyAlphaTexture(material, normalized_path) print(f"已选择透明度贴图:{filename} -> 标准化路径:{normalized_path}") - def _selectDetailTexture(self,material): + def _selectDetailTexture(self, material): """选择细节贴图""" from PyQt5.QtWidgets import QFileDialog - file_dialog = QFileDialog(None,"选择细节贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") + file_dialog = QFileDialog(None, "选择细节贴图", "", "图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") if file_dialog.exec_(): filename = file_dialog.selectedFiles()[0] if filename: # 使用跨平台路径标准化 normalized_path = util.normalize_model_path(filename) - self._applyDetailTexture(material,normalized_path) + self._applyDetailTexture(material, normalized_path) print(f"已选择细节贴图:{filename} -> 标准化路径:{normalized_path}") - def _selectGlossTexture(self,material): + def _selectGlossTexture(self, material): """选择光泽贴图""" from PyQt5.QtWidgets import QFileDialog - file_dialog = QFileDialog(None,"选择光泽贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") + file_dialog = QFileDialog(None, "选择光泽贴图", "", "图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") if file_dialog.exec_(): filename = file_dialog.selectedFiles()[0] if filename: # 使用跨平台路径标准化 normalized_path = util.normalize_model_path(filename) - self._applyGlossTexture(material,normalized_path) + self._applyGlossTexture(material, normalized_path) print(f"已选择光泽贴图:{filename} -> 标准化路径:{normalized_path}") - - # def _applyDiffuseTexture(self, texture_path): # from panda3d.core import TextureStage # try: @@ -5401,17 +5410,17 @@ class PropertyPanelManager: # print("贴图已直接贴到节点:", node.getName()) # except Exception as e: # print("贴图失败:", e) - def _applyDiffuseTexture(self,material_title,texture_path): + def _applyDiffuseTexture(self, material_title, texture_path): """应用漫反射贴图""" try: from RenderPipelineFile.rpcore.loader import RPLoader from panda3d.core import TextureStage - #加载纹理 + # 加载纹理 texture = RPLoader.load_texture(texture_path) if texture: - #获取材质所属的节点 - material,node = self._findMaterialAndNodeByTitle(material_title) + # 获取材质所属的节点 + material, node = self._findMaterialAndNodeByTitle(material_title) if node and material: print(f"正在为节点 {node.getName()} 应用漫反射贴图") @@ -5434,7 +5443,7 @@ class PropertyPanelManager: node, effect_file, { - "normal_mapping": True, # 启用法线映射支持 + "normal_mapping": True, # 启用法线映射支持 "render_gbuffer": True, "alpha_testing": needs_alpha, # 根据是否需要透明度决定 "parallax_mapping": False, @@ -5472,7 +5481,8 @@ class PropertyPanelManager: for i, stage in enumerate(all_stages): tex = node.getTexture(stage) mode_name = self._getTextureModeString(stage.getMode()) - print(f"阶段 {i}: {stage.getName()}, Sort: {stage.getSort()}, 模式: {mode_name}, 纹理: {tex.getName() if tex else 'None'}") + print( + f"阶段 {i}: {stage.getName()}, Sort: {stage.getSort()}, 模式: {mode_name}, 纹理: {tex.getName() if tex else 'None'}") print("==========================================") print(f"漫反射贴图已成功应用:{texture_path}") @@ -5541,7 +5551,7 @@ class PropertyPanelManager: node, effect_file, { - "normal_mapping": True, # 强制启用法线映射 + "normal_mapping": True, # 强制启用法线映射 "render_gbuffer": True, "alpha_testing": needs_alpha, # 根据是否需要透明度决定 "parallax_mapping": False, @@ -5602,7 +5612,6 @@ class PropertyPanelManager: import traceback traceback.print_exc() - def _applyRoughnessTexture_FINAL(self, material, texture_path): """应用粗糙度贴图 - 先编译后绑定策略""" try: @@ -5636,14 +5645,12 @@ class PropertyPanelManager: if not has_normal: print("⚠️ 检测到材质没有法线贴图,先添加默认法线贴图...") - #self._applyDefaultNormalTexture(node) - self._applyNormalTexture(material,"RenderPipelineFile/Default_NRM_2K.png") + # self._applyDefaultNormalTexture(node) + self._applyNormalTexture(material, "RenderPipelineFile/Default_NRM_2K.png") print("✅ 默认法线贴图已添加") else: print("✅ 检测到材质已有法线贴图") - - # 5. 检查是否有金属性贴图和透明漫反射贴图,选择合适的PBR效果 print("🔧 步骤2:检查金属性贴图和透明度设置...") has_metallic = self._hasMetallicTexture(node) @@ -5672,19 +5679,19 @@ class PropertyPanelManager: 100 ) print(f"✅ {effect_file} 效果已应用") - #print("✅ 着色器预编译完成") + # print("✅ 着色器预编译完成") # 5. 等待编译完成 - #time.sleep(0.2) # 200ms等待 - #print("⏱️ 等待着色器编译...") + # time.sleep(0.2) # 200ms等待 + # print("⏱️ 等待着色器编译...") # 6. 现在绑定纹理到已编译的着色器 - #print("🔧 步骤2:绑定纹理到编译完成的着色器...") + # print("🔧 步骤2:绑定纹理到编译完成的着色器...") # roughness_stage = TextureStage("roughness_map") # roughness_stage.setSort(3) # p3d_Texture3 # roughness_stage.setMode(TextureStage.MModulate) # node.setTexture(roughness_stage, texture) - #print("✅ 纹理已绑定到预编译着色器") + # print("✅ 纹理已绑定到预编译着色器") print("🧹 清理现有粗糙度贴图...") existing_stages = node.findAllTextureStages() @@ -5701,7 +5708,6 @@ class PropertyPanelManager: roughness_stage.setMode(TextureStage.MModulate) node.setTexture(roughness_stage, texture) - # 7. 验证效果 applied_texture = node.getTexture(roughness_stage) if applied_texture: @@ -5812,7 +5818,7 @@ class PropertyPanelManager: "effects/pbr_with_metallic.yaml", { "normal_mapping": False, # 关闭法线贴图避免干扰 - "render_gbuffer": True, # 必须启用gbuffer渲染 + "render_gbuffer": True, # 必须启用gbuffer渲染 "alpha_testing": False, "parallax_mapping": False, "render_shadow": True, @@ -5896,7 +5902,7 @@ class PropertyPanelManager: print(f"⚠️ 创建白色纹理失败: {e}") return False - def _applyMetallicTexture(self,material,texture_path): + def _applyMetallicTexture(self, material, texture_path): """应用金属性贴图 - Blender风格效果""" try: from RenderPipelineFile.rpcore.loader import RPLoader @@ -5976,7 +5982,7 @@ class PropertyPanelManager: "effects/metallic_only.yaml", { "normal_mapping": False, # 关闭法线贴图避免干扰 - "render_gbuffer": True, # 必须启用gbuffer渲染 + "render_gbuffer": True, # 必须启用gbuffer渲染 "alpha_testing": False, "parallax_mapping": False, "render_shadow": True, @@ -6078,7 +6084,7 @@ class PropertyPanelManager: import traceback traceback.print_exc() - def _applyIORTexture(self,material,texture_path): + def _applyIORTexture(self, material, texture_path): """应用IOR贴图到特定材质""" try: from RenderPipelineFile.rpcore.loader import RPLoader @@ -6109,7 +6115,7 @@ class PropertyPanelManager: ior_stage.setSort(2) # 对应p3d_Texture2 ior_stage.setMode(TextureStage.MModulate) - node.setTexture(ior_stage,texture) + node.setTexture(ior_stage, texture) print("IOR贴图已应用到p3d_Texture2槽") # 不再需要手动刷新渲染状态,避免闪烁 @@ -6121,7 +6127,7 @@ class PropertyPanelManager: import traceback traceback.print_exc() - def _applyParallaxTexture(self,material,texture_path): + def _applyParallaxTexture(self, material, texture_path): """应用视差贴图""" try: from RenderPipelineFile.rpcore.loader import RPLoader @@ -6151,7 +6157,7 @@ class PropertyPanelManager: parallax_stage.setSort(4) # 对应p3d_Texture4 parallax_stage.setMode(TextureStage.MHeight) # 高度贴图模式 - node.setTexture(parallax_stage,texture) + node.setTexture(parallax_stage, texture) print("视差贴图已应用到p3d_Texture4槽") print(f"视差贴图已成功应用:{texture_path}") @@ -6162,16 +6168,16 @@ class PropertyPanelManager: import traceback traceback.print_exc() - def _ensureNormalMappingEnabled(self,model): + def _ensureNormalMappingEnabled(self, model): """确保模型启用了法线映射功能""" try: self.world.render_pipeline.set_effect( model, "effects/default.yaml", { - "normal_mapping":True, - "render_gbuffer":True, - "alpha_testing":True + "normal_mapping": True, + "render_gbuffer": True, + "alpha_testing": True }, 30 ) @@ -6360,7 +6366,7 @@ class PropertyPanelManager: { "normal_mapping": True, "render_gbuffer": False, # 透明物体不渲染到GBuffer - "render_forward": True, # 使用前向渲染 + "render_forward": True, # 使用前向渲染 "alpha_testing": True, "parallax_mapping": False, "render_shadow": True, @@ -6439,8 +6445,6 @@ class PropertyPanelManager: except: pass - - def _applyEmissionTexture(self, material, texture_path): """应用自发光贴图""" try: @@ -6758,17 +6762,17 @@ class PropertyPanelManager: """查找使用指定材质的具体几何节点""" from panda3d.core import MaterialAttrib, GeomNode - #print(f"查找材质: {target_material.get_name() if hasattr(target_material, 'get_name') else 'unnamed'}") + # print(f"查找材质: {target_material.get_name() if hasattr(target_material, 'get_name') else 'unnamed'}") # 首先尝试查找GeomNode geom_nodes = model.find_all_matches("**/+GeomNode") - #print(f"找到 {len(geom_nodes)} 个几何节点") + # print(f"找到 {len(geom_nodes)} 个几何节点") # 如果没有找到GeomNode,尝试查找所有子节点 if len(geom_nodes) == 0: - #print("未找到GeomNode,尝试查找所有子节点...") + # print("未找到GeomNode,尝试查找所有子节点...") all_nodes = model.find_all_matches("**") - #print(f"找到 {len(all_nodes)} 个子节点") + # print(f"找到 {len(all_nodes)} 个子节点") for node_np in all_nodes: node = node_np.node() @@ -6779,14 +6783,14 @@ class PropertyPanelManager: for geom_np in geom_nodes: geom_node = geom_np.node() geom_count = geom_node.get_num_geoms() - #rint(f"检查几何节点 {geom_node.get_name()}: {geom_count} 个几何体") + # rint(f"检查几何节点 {geom_node.get_name()}: {geom_count} 个几何体") for i in range(geom_count): state = geom_node.get_geom_state(i) if state.has_attrib(MaterialAttrib): material = state.get_attrib(MaterialAttrib).get_material() if material == target_material: - #print(f"找到匹配的几何节点: {geom_np.get_name()}") + # print(f"找到匹配的几何节点: {geom_np.get_name()}") return geom_np print("未找到匹配的几何节点") @@ -6813,14 +6817,14 @@ class PropertyPanelManager: # 使用现有的精确查找方法 geom_node = self._findSpecificGeomNodeWithMaterial(current_model, target_material) if geom_node: - #print(f"✓ 找到特定几何节点: {geom_node.getName()}") + # print(f"✓ 找到特定几何节点: {geom_node.getName()}") # 存储映射以供后续使用 if not hasattr(self, '_material_geom_mapping'): self._material_geom_mapping = {} self._material_geom_mapping[material_id] = geom_node return geom_node else: - #print("⚠️ 未找到特定几何节点,使用模型节点(可能影响所有材质)") + # print("⚠️ 未找到特定几何节点,使用模型节点(可能影响所有材质)") return current_model print("❌ 未找到当前选中的模型") @@ -6830,23 +6834,23 @@ class PropertyPanelManager: """查找使用指定材质的具体几何节点""" from panda3d.core import MaterialAttrib - #print(f"查找材质: {target_material.get_name() if hasattr(target_material, 'get_name') else 'unnamed'}") + # print(f"查找材质: {target_material.get_name() if hasattr(target_material, 'get_name') else 'unnamed'}") # 遍历模型下的所有几何节点 geom_nodes = model.find_all_matches("**/+GeomNode") - #print(f"找到 {len(geom_nodes)} 个几何节点") + # print(f"找到 {len(geom_nodes)} 个几何节点") for geom_np in geom_nodes: geom_node = geom_np.node() geom_count = geom_node.get_num_geoms() - #print(f"几何节点 {geom_node.get_name()}: {geom_count} 个几何体") + # print(f"几何节点 {geom_node.get_name()}: {geom_count} 个几何体") for i in range(geom_count): state = geom_node.get_geom_state(i) if state.has_attrib(MaterialAttrib): material = state.get_attrib(MaterialAttrib).get_material() if material == target_material: - #print(f"找到匹配的几何节点: {geom_np.get_name()}") + # print(f"找到匹配的几何节点: {geom_np.get_name()}") return geom_np else: print(f"几何体 {i} 没有材质属性") @@ -6913,7 +6917,7 @@ class PropertyPanelManager: shading_title = QLabel("着色模型") shading_title.setStyleSheet("font-weight:bold;") - material_layout.addWidget(shading_title,current_row, 0, 1, 4) + material_layout.addWidget(shading_title, current_row, 0, 1, 4) current_row += 1 material_layout.addWidget(QLabel("着色模型:"), current_row, 0) @@ -6989,7 +6993,7 @@ class PropertyPanelManager: self._updateMaterialAlphaForTransparency(material, default_opacity) # 应用透明渲染效果 - #self._applyTransparentRenderingEffect() + # self._applyTransparentRenderingEffect() print(f"透明着色模型设置完成") print(f" - emission.x = {model_index} (透明着色模型)") @@ -7004,13 +7008,14 @@ class PropertyPanelManager: if model_index in [1, 3]: # 自发光或透明模式 self._refreshMaterialUI() - print(f"着色模型已更新为: {model_index} ({'自发光' if model_index == 1 else '透明' if model_index == 3 else '默认'})") + print( + f"着色模型已更新为: {model_index} ({'自发光' if model_index == 1 else '透明' if model_index == 3 else '默认'})") def _addTransparencyPanel(self, material, material_layout, current_row): """添加透明度控制面板""" transparency_title = QLabel("透明度属性") transparency_title.setStyleSheet("color: #00BFFF; font-weight:bold;") - material_layout.addWidget(transparency_title,current_row, 0, 1, 4) + material_layout.addWidget(transparency_title, current_row, 0, 1, 4) current_row += 1 # 不透明度滑块(避免混淆,使用不透明度) @@ -7074,11 +7079,11 @@ class PropertyPanelManager: alpha = opacity_slider # 反转 color = self._getOrCreateMaterialBaseColor(material) or Vec4(1, 1, 1, 1) - material.base_color=Vec4(color.x, color.y, color.z, alpha) - material.base_color=Vec4(color.x, color.y, color.z, alpha) + material.base_color = Vec4(color.x, color.y, color.z, alpha) + material.base_color = Vec4(color.x, color.y, color.z, alpha) em = material.emission or Vec4(0, 0, 0, 0) - material.set_emission(Vec4(3.0,alpha,em.z,em.w)) + material.set_emission(Vec4(3.0, alpha, em.z, em.w)) self.world.render_pipeline.set_effect( model, @@ -7089,7 +7094,6 @@ class PropertyPanelManager: self.world.render_pipeline.prepare_scene(model) print(f"[透明] 不透明度={opacity_slider:.2f} 已同步") - def _applyTransparentRenderingEffect(self): from panda3d.core import TransparencyAttrib """为当前选中的模型应用透明渲染效果(简化版本)""" @@ -7104,7 +7108,7 @@ class PropertyPanelManager: model, "effects/default.yaml", { - "render_gbuffer":True, + "render_gbuffer": True, "alpha_testing": False, "normal_mapping": True, "render_shadow": True, @@ -7113,7 +7117,6 @@ class PropertyPanelManager: sort=100 ) - # 让RenderPipeline自动处理透明材质 # 当emission.x=3时,RenderPipeline会自动设置正确的渲染参数 self.world.render_pipeline.prepare_scene(model) @@ -7241,9 +7244,9 @@ class PropertyPanelManager: if hasattr(material, 'base_color') and material.base_color is not None: try: base_color_match = ( - abs(material.base_color.x - preset_values["base_color"][0]) < tolerance and - abs(material.base_color.y - preset_values["base_color"][1]) < tolerance and - abs(material.base_color.z - preset_values["base_color"][2]) < tolerance + abs(material.base_color.x - preset_values["base_color"][0]) < tolerance and + abs(material.base_color.y - preset_values["base_color"][1]) < tolerance and + abs(material.base_color.z - preset_values["base_color"][2]) < tolerance ) except (AttributeError, TypeError): base_color_match = False @@ -7281,7 +7284,8 @@ class PropertyPanelManager: presets = { "塑料": {"base_color": Vec4(0.8, 0.8, 0.8, 1.0), "roughness": 0.7, "metallic": 0.0, "ior": 1.4}, "金属": {"base_color": Vec4(0.7, 0.7, 0.7, 1.0), "roughness": 0.1, "metallic": 1.0, "ior": 1.5}, - "玻璃": {"base_color": Vec4(0.9, 0.9, 1.0, 0.2), "roughness": 0.0, "metallic": 0.0, "ior": 1.5,"shading_model":3,"transparency":0.2}, + "玻璃": {"base_color": Vec4(0.9, 0.9, 1.0, 0.2), "roughness": 0.0, "metallic": 0.0, "ior": 1.5, + "shading_model": 3, "transparency": 0.2}, "橡胶": {"base_color": Vec4(0.2, 0.2, 0.2, 1.0), "roughness": 0.9, "metallic": 0.0, "ior": 1.3}, "木材": {"base_color": Vec4(0.6, 0.4, 0.2, 1.0), "roughness": 0.8, "metallic": 0.0, "ior": 1.3}, "陶瓷": {"base_color": Vec4(0.9, 0.9, 0.85, 1.0), "roughness": 0.1, "metallic": 0.0, "ior": 1.6}, @@ -7310,16 +7314,16 @@ class PropertyPanelManager: material.set_refractive_index(preset["ior"]) if "shading_model" in preset: - emission = Vec4(float (preset["shading_model"]),0,0,0) + emission = Vec4(float(preset["shading_model"]), 0, 0, 0) if "transparency" in preset: emission.y = preset["transparency"] material.set_emission(emission) - #关键:为透明材质应用正确的渲染效果 - if preset["shading_model"]==3: + # 关键:为透明材质应用正确的渲染效果 + if preset["shading_model"] == 3: self._apply_transparent_effect() - #material._applied_preset = preset_name + # material._applied_preset = preset_name self._refreshMaterialUI() print(f"已应用材质预设: {preset_name}") @@ -7416,8 +7420,8 @@ class PropertyPanelManager: if not has_normal: print("⚠️ 检测到材质没有法线贴图,先添加默认法线贴图...") - #self._applyDefaultNormalTexture(node) - self._applyNormalTexture(material,"RenderPipelineFile/Default_NRM_2K.png") + # self._applyDefaultNormalTexture(node) + self._applyNormalTexture(material, "RenderPipelineFile/Default_NRM_2K.png") print("✅ 默认法线贴图已添加") else: print("✅ 检测到材质已有法线贴图") @@ -7466,7 +7470,7 @@ class PropertyPanelManager: node, "effects/pbr_with_metallic.yaml", { - #"normal_mapping": has_normal, + # "normal_mapping": has_normal, "normal_mapping": True, "render_gbuffer": True, "alpha_testing": False, @@ -7486,7 +7490,7 @@ class PropertyPanelManager: # 多次重新绑定,模拟手动"应用两次"的效果 for i in range(3): - print(f" 第{i+1}次绑定...") + print(f" 第{i + 1}次绑定...") # 等待更长时间确保着色器编译完成 time.sleep(0.05) # 50ms延迟 @@ -7501,9 +7505,9 @@ class PropertyPanelManager: # 验证绑定 applied_texture = node.getTexture(metallic_stage) if applied_texture: - print(f" ✅ 第{i+1}次绑定成功") + print(f" ✅ 第{i + 1}次绑定成功") else: - print(f" ❌ 第{i+1}次绑定失败") + print(f" ❌ 第{i + 1}次绑定失败") # 最终验证 final_texture = node.getTexture(metallic_stage) @@ -7649,7 +7653,6 @@ class PropertyPanelManager: sun_group.setLayout(sun_layout) self._propertyLayout.addWidget(sun_group) - def _onSunAzimuthSliderChanged(self, value): """滑块值改变时的回调""" try: @@ -7710,10 +7713,10 @@ class PropertyPanelManager: """设置太阳预设位置""" try: presets = { - "sunrise": (90, 45), # 东方,低角度 - "noon": (180, 90), # 南方,天顶 - "sunset": (270, 45), # 西方,低角度 - "midnight": (0, 0) # 北方,地平线 + "sunrise": (90, 45), # 东方,低角度 + "noon": (180, 90), # 南方,天顶 + "sunset": (270, 45), # 西方,低角度 + "midnight": (0, 0) # 北方,地平线 } if preset_name in presets: @@ -7986,8 +7989,8 @@ class PropertyPanelManager: import traceback traceback.print_exc() - def _buildSkeletalUI(self,origin_model,actor,layout): - from PyQt5.QtWidgets import QLabel,QComboBox,QHBoxLayout,QWidget,QPushButton,QDoubleSpinBox + def _buildSkeletalUI(self, origin_model, actor, layout): + from PyQt5.QtWidgets import QLabel, QComboBox, QHBoxLayout, QWidget, QPushButton, QDoubleSpinBox actor.hide() origin_model.show() @@ -8031,24 +8034,24 @@ class PropertyPanelManager: btn_box = QWidget() btn_lay = QHBoxLayout(btn_box) - for txt,slot in (("播放",self._playAnimation), - ("暂停",self._pauseAnimation), - ("停止",self._stopAnimation), - ("循环",self._loopAnimation)): + for txt, slot in (("播放", self._playAnimation), + ("暂停", self._pauseAnimation), + ("停止", self._stopAnimation), + ("循环", self._loopAnimation)): btn = QPushButton(txt) - btn.clicked.connect(lambda _,f=slot:f(origin_model)) + btn.clicked.connect(lambda _, f=slot: f(origin_model)) btn_lay.addWidget(btn) layout.addWidget(QLabel("控制:"), current_row, 0) layout.addWidget(btn_box, current_row, 1, 1, 3) current_row += 1 self.speed_spinbox = QDoubleSpinBox() - self.speed_spinbox.setRange(0.1,5.0) + self.speed_spinbox.setRange(0.1, 5.0) self.speed_spinbox.setSingleStep(0.1) saved = origin_model.getPythonTag("anim_speed") self.speed_spinbox.setValue(saved if saved is not None else 1.0) - #self.speed_spinbox.setValue(1.0) - self.speed_spinbox.valueChanged.connect(lambda v:self._setAnimationSpeed(origin_model,v)) + # self.speed_spinbox.setValue(1.0) + self.speed_spinbox.valueChanged.connect(lambda v: self._setAnimationSpeed(origin_model, v)) layout.addWidget(QLabel("播放速度:"), current_row, 0) layout.addWidget(self.speed_spinbox, current_row, 1) @@ -8161,12 +8164,12 @@ class PropertyPanelManager: print(f"[动画分析] 分析失败: {e}") return "分析失败" - def _getActor(self,origin_model): + def _getActor(self, origin_model): if origin_model in self._actor_cache: return self._actor_cache[origin_model] filepath = origin_model.getTag("model_path") if not filepath: - return None + return None print(f"[Actor加载] 尝试加载: {filepath}") @@ -8179,7 +8182,7 @@ class PropertyPanelManager: import gltf print(f"[GLTF加载] 尝试加载: {filepath}") # test_actor=Actor(NodePath(gltf._loader.GltfLoader.load_file(filepath,None))) - test_actor=Actor(NodePath(gltf.load_model(filepath,None))) + test_actor = Actor(NodePath(gltf.load_model(filepath, None))) anims = test_actor.getAnimNames() test_actor.reparentTo(self.world.render) self._actor_cache[origin_model] = test_actor @@ -8407,7 +8410,7 @@ bpy.ops.export_scene.gltf(filepath="{egg_path.replace('.egg', '.gltf')}", export # 显示成功消息 QMessageBox.information(None, "转换成功", - f"FBX动画转换成功!\n请重新选择模型查看动画。") + f"FBX动画转换成功!\n请重新选择模型查看动画。") print(f"[手动转换] 转换完成: {converted_path}") @@ -8423,10 +8426,10 @@ bpy.ops.export_scene.gltf(filepath="{egg_path.replace('.egg', '.gltf')}", export - 打开 Blender - 导入 FBX 文件 - 导出为 glTF (.gltf) 格式,确保选择"包含动画" - + 2. 使用命令行工具: - gltf2bam your_file.gltf your_file.bam - + 3. 检查原始 FBX 文件: - 确保 FBX 文件确实包含动画数据 - 尝试在其他软件中验证动画 @@ -8458,7 +8461,7 @@ bpy.ops.export_scene.gltf(filepath="{egg_path.replace('.egg', '.gltf')}", export try: # 首先尝试看看是否有直接的 FBX 支持 result = subprocess.run(['gltf2bam', '--help'], - capture_output=True, text=True, timeout=10) + capture_output=True, text=True, timeout=10) print(f"[系统转换] gltf2bam 可用") except: print(f"[系统转换] gltf2bam 不可用") @@ -8483,7 +8486,7 @@ bpy.ops.object.delete(use_global=False) try: bpy.ops.import_scene.fbx(filepath="{fbx_path}") print("FBX导入成功") - + # 导出为 glTF bpy.ops.export_scene.gltf( filepath="{gltf_path}", @@ -8492,7 +8495,7 @@ try: export_frame_range=True ) print("glTF导出成功") - + except Exception as e: print(f"转换失败: {{e}}") sys.exit(1) @@ -8522,7 +8525,7 @@ except Exception as e: # 转换 glTF 为 BAM result2 = subprocess.run(['gltf2bam', gltf_path, bam_path], - capture_output=True, text=True, timeout=60) + capture_output=True, text=True, timeout=60) if result2.returncode == 0 and os.path.exists(bam_path): print(f"[系统转换] 成功转换为: {bam_path}") @@ -8544,10 +8547,8 @@ except Exception as e: print(f"[系统转换] 系统转换失败: {e}") return None - - - def _playAnimation(self,origin_model): - actor=self._getActor(origin_model) + def _playAnimation(self, origin_model): + actor = self._getActor(origin_model) if not actor: return actor.setPos(origin_model.getPos()) @@ -8556,7 +8557,7 @@ except Exception as e: origin_model.hide() actor.show() - if hasattr(self,'animation_combo'): + if hasattr(self, 'animation_combo'): # 获取原始动画名称(存储在 userData 中) current_index = self.animation_combo.currentIndex() if current_index >= 0: @@ -8570,8 +8571,7 @@ except Exception as e: actor.play(display_name) print(f"『动画播放』:{display_name}") - - def _pauseAnimation(self,origin_model): + def _pauseAnimation(self, origin_model): actor = self._getActor(origin_model) if not actor: return @@ -8584,7 +8584,7 @@ except Exception as e: actor.stop() print("『动画』暂停") - def _stopAnimation(self,origin_model): + def _stopAnimation(self, origin_model): actor = self._getActor(origin_model) if not actor: return @@ -8603,8 +8603,7 @@ except Exception as e: origin_model.show() print("『动画』停止切换至原始模型") - - def _loopAnimation(self,origin_model): + def _loopAnimation(self, origin_model): actor = self._getActor(origin_model) if not actor: return @@ -8644,21 +8643,21 @@ except Exception as e: if anim_name: actor.setPlayRate(speed, anim_name) - origin_model.setPythonTag("anim_speed",speed) + origin_model.setPythonTag("anim_speed", speed) print(f"[动画] 速度设为: {speed} ({display_name})") - def _dispatchAnimCommand(self,origin_model,cmd): + def _dispatchAnimCommand(self, origin_model, cmd): cache = self._actor_cache.get(origin_model) if not cache: return - kind,player = cache + kind, player = cache if kind == "actor": - actor=player + actor = player anim_name = self.animation_combo.currentText() actor.setPos(origin_model.getPos()) actor.setHpr(origin_model.getHpr()) - actor.setScale(origin_model.getScale()/100) + actor.setScale(origin_model.getScale() / 100) if cmd == "play": origin_model.hide() @@ -8672,12 +8671,13 @@ except Exception as e: actor.stop() if anim_name and actor.getAnimControl(anim_name): actor.getAnimControl(anim_name).pose(0) - actor.hide();origin_model.show() + actor.hide(); + origin_model.show() elif cmd == "loop": origin_model.hide() actor.show() actor.loop(anim_name) - elif isinstance(cmd,tuple) and cmd[0] == "speed": + elif isinstance(cmd, tuple) and cmd[0] == "speed": actor.setPlayRate(cmd[1], anim_name) def removeActorForModel(self, model): @@ -8694,59 +8694,59 @@ except Exception as e: # 创建碰撞检测组 collision_group = QGroupBox("碰撞检测") collision_layout = QGridLayout() - + # 检查模型是否已有碰撞 has_collision = self._hasCollision(model) - + # 碰撞状态标签 status_label = QLabel("状态:") collision_layout.addWidget(status_label, 0, 0) - + # 状态文本(需要保存引用以便更新) self.collision_status_text = QLabel("已启用" if has_collision else "未启用") self.collision_status_text.setStyleSheet("color: green;" if has_collision else "color: red;") collision_layout.addWidget(self.collision_status_text, 0, 1) - + # 形状选择标签(始终显示) self.collision_shape_label = QLabel("碰撞形状:") collision_layout.addWidget(self.collision_shape_label, 1, 0) - + # 形状选择下拉框(始终显示) self.collision_shape_combo = QComboBox() self.collision_shape_combo.addItems([ "球形 (Sphere)", - "盒型 (Box)", + "盒型 (Box)", "胶囊体 (Capsule)", "平面 (Plane)", "自动选择 (Auto)" ]) collision_layout.addWidget(self.collision_shape_combo, 1, 1) - + # 保存布局引用,用于动态添加/移除控件 self.collision_layout = collision_layout self.collision_group = collision_group - + current_row = 2 # 下一行的索引 - + # 显示/隐藏切换按钮(只有有碰撞时才显示) if has_collision: # 检查碰撞的当前可见性 is_collision_visible = self._isCollisionVisible(model) - + # 显示当前碰撞类型并设置为只读 current_shape = self._getCurrentCollisionShape(model) self._setComboToShape(current_shape) self.collision_shape_combo.setEnabled(False) - + # 添加碰撞参数调整控件 current_row = self._addCollisionParameterControls(model, collision_layout, current_row, current_shape) - + # 显示/隐藏切换按钮 self.collision_visibility_button = QPushButton("隐藏碰撞" if is_collision_visible else "显示碰撞") self.collision_visibility_button.clicked.connect(lambda: self._toggleCollisionVisibility(model)) collision_layout.addWidget(self.collision_visibility_button, current_row, 0, 1, 2) current_row += 1 - + # 移除碰撞按钮 self.collision_button = QPushButton("移除碰撞") self.collision_button.clicked.connect(lambda: self._removeCollisionAndUpdate(model)) @@ -8755,55 +8755,56 @@ except Exception as e: # 如果没有碰撞,设置默认选择并允许编辑 self.collision_shape_combo.setCurrentText("球形 (Sphere)") self.collision_shape_combo.setEnabled(True) - + # 清理之前的参数控件 self._clearCollisionParameterControls() - + # 隐藏显示/隐藏按钮 if hasattr(self, 'collision_visibility_button'): self.collision_visibility_button.setVisible(False) - + # 添加碰撞按钮 self.collision_button = QPushButton("添加碰撞") self.collision_button.clicked.connect(lambda: self._addCollisionAndUpdate(model)) collision_layout.addWidget(self.collision_button, current_row, 0, 1, 2) collision_group.setLayout(collision_layout) self._propertyLayout.addWidget(collision_group) - + except Exception as e: print(f"创建碰撞面板失败: {e}") + def _addCollisionParameterControls(self, model, layout, start_row, shape_type): """添加碰撞参数调整控件""" try: current_row = start_row - + # 位置调整控件(所有类型都有) pos_label = QLabel("位置偏移:") layout.addWidget(pos_label, current_row, 0) current_row += 1 - + # X, Y, Z 位置调整 self.collision_pos_x = self._createCollisionSpinBox(-100, 100, 2) - self.collision_pos_y = self._createCollisionSpinBox(-100, 100, 2) + self.collision_pos_y = self._createCollisionSpinBox(-100, 100, 2) self.collision_pos_z = self._createCollisionSpinBox(-100, 100, 2) - + layout.addWidget(QLabel("X:"), current_row, 0) layout.addWidget(self.collision_pos_x, current_row, 1) current_row += 1 - + layout.addWidget(QLabel("Y:"), current_row, 0) layout.addWidget(self.collision_pos_y, current_row, 1) current_row += 1 - + layout.addWidget(QLabel("Z:"), current_row, 0) layout.addWidget(self.collision_pos_z, current_row, 1) current_row += 1 - + # 连接位置变化信号 self.collision_pos_x.valueChanged.connect(lambda v: self._updateCollisionPosition(model, 'x', v)) self.collision_pos_y.valueChanged.connect(lambda v: self._updateCollisionPosition(model, 'y', v)) self.collision_pos_z.valueChanged.connect(lambda v: self._updateCollisionPosition(model, 'z', v)) - + # 根据形状类型添加特定参数 if shape_type == 'sphere': current_row = self._addSphereParameters(model, layout, current_row) @@ -8813,16 +8814,16 @@ except Exception as e: current_row = self._addCapsuleParameters(model, layout, current_row) elif shape_type == 'plane': current_row = self._addPlaneParameters(model, layout, current_row) - + # 获取并设置当前参数值 self._loadCurrentCollisionParameters(model, shape_type) - + return current_row - + except Exception as e: print(f"添加碰撞参数控件失败: {e}") return start_row - + def _createCollisionSpinBox(self, min_val, max_val, decimals=2): """创建碰撞参数调整用的SpinBox""" spinbox = QDoubleSpinBox() @@ -8830,124 +8831,189 @@ except Exception as e: spinbox.setDecimals(decimals) spinbox.setSingleStep(0.1) return spinbox - + def _addSphereParameters(self, model, layout, start_row): """添加球形碰撞参数""" current_row = start_row - + # 半径调整 radius_label = QLabel("半径:") layout.addWidget(radius_label, current_row, 0) - + self.collision_radius = self._createCollisionSpinBox(0.1, 100, 2) + + # 设置基于模型变换后尺寸的默认值 + if hasattr(self.world, 'collision_manager'): + transformed_info = self.world.collision_manager._getTransformedModelInfo(model) + if transformed_info: + default_radius = transformed_info['radius'] + self.collision_radius.setValue(default_radius) + else: + # 回退到原始包围盒 + bounds = model.getBounds() + if not bounds.isEmpty(): + default_radius = bounds.getRadius() + self.collision_radius.setValue(default_radius) + self.collision_radius.valueChanged.connect(lambda v: self._updateSphereRadius(model, v)) layout.addWidget(self.collision_radius, current_row, 1) current_row += 1 - + return current_row - + def _addBoxParameters(self, model, layout, start_row): """添加盒型碰撞参数""" current_row = start_row - + size_label = QLabel("尺寸:") layout.addWidget(size_label, current_row, 0) current_row += 1 - + # 宽度、长度、高度 self.collision_width = self._createCollisionSpinBox(0.1, 100, 2) self.collision_length = self._createCollisionSpinBox(0.1, 100, 2) self.collision_height = self._createCollisionSpinBox(0.1, 100, 2) - + + # 设置基于模型变换后尺寸的默认值 + if hasattr(self.world, 'collision_manager'): + transformed_info = self.world.collision_manager._getTransformedModelInfo(model) + if transformed_info: + actual_size = transformed_info['size'] + self.collision_width.setValue(actual_size.x) + self.collision_length.setValue(actual_size.y) + self.collision_height.setValue(actual_size.z) + else: + # 回退到原始包围盒 + bounds = model.getBounds() + if not bounds.isEmpty(): + model_size = bounds.getMax() - bounds.getMin() + self.collision_width.setValue(model_size.x) + self.collision_length.setValue(model_size.y) + self.collision_height.setValue(model_size.z) + layout.addWidget(QLabel("宽度:"), current_row, 0) layout.addWidget(self.collision_width, current_row, 1) current_row += 1 - + layout.addWidget(QLabel("长度:"), current_row, 0) layout.addWidget(self.collision_length, current_row, 1) current_row += 1 - + layout.addWidget(QLabel("高度:"), current_row, 0) layout.addWidget(self.collision_height, current_row, 1) current_row += 1 - + # 连接信号 self.collision_width.valueChanged.connect(lambda v: self._updateBoxSize(model, 'width', v)) self.collision_length.valueChanged.connect(lambda v: self._updateBoxSize(model, 'length', v)) self.collision_height.valueChanged.connect(lambda v: self._updateBoxSize(model, 'height', v)) - + return current_row - + def _addCapsuleParameters(self, model, layout, start_row): """添加胶囊体碰撞参数""" current_row = start_row - + # 半径和高度 radius_label = QLabel("半径:") layout.addWidget(radius_label, current_row, 0) - + self.collision_capsule_radius = self._createCollisionSpinBox(0.1, 100, 2) + + # 设置基于模型变换后尺寸的默认值 + if hasattr(self.world, 'collision_manager'): + transformed_info = self.world.collision_manager._getTransformedModelInfo(model) + if transformed_info: + actual_size = transformed_info['size'] + # 更合理的默认半径:基于变换后模型宽度的平均值 + default_radius = min(actual_size.x, actual_size.y) / 2.5 + self.collision_capsule_radius.setValue(default_radius) + else: + # 回退到原始包围盒 + bounds = model.getBounds() + if not bounds.isEmpty(): + model_size = bounds.getMax() - bounds.getMin() + # 更合理的默认半径:基于模型宽度的平均值 + default_radius = min(model_size.x, model_size.y) / 2.5 + self.collision_capsule_radius.setValue(default_radius) + self.collision_capsule_radius.valueChanged.connect(lambda v: self._updateCapsuleRadius(model, v)) layout.addWidget(self.collision_capsule_radius, current_row, 1) current_row += 1 - + height_label = QLabel("高度:") layout.addWidget(height_label, current_row, 0) - + self.collision_capsule_height = self._createCollisionSpinBox(0.1, 100, 2) + + # 设置基于模型变换后高度的默认值 + if hasattr(self.world, 'collision_manager'): + transformed_info = self.world.collision_manager._getTransformedModelInfo(model) + if transformed_info: + actual_size = transformed_info['size'] + self.collision_capsule_height.setValue(actual_size.z) + else: + # 回退到原始包围盒 + bounds = model.getBounds() + if not bounds.isEmpty(): + model_size = bounds.getMax() - bounds.getMin() + self.collision_capsule_height.setValue(model_size.z) + self.collision_capsule_height.valueChanged.connect(lambda v: self._updateCapsuleHeight(model, v)) layout.addWidget(self.collision_capsule_height, current_row, 1) current_row += 1 - + return current_row - + def _addPlaneParameters(self, model, layout, start_row): """添加平面碰撞参数""" current_row = start_row - + # 法向量 normal_label = QLabel("法向量:") layout.addWidget(normal_label, current_row, 0) current_row += 1 - + self.collision_normal_x = self._createCollisionSpinBox(-1, 1, 2) self.collision_normal_y = self._createCollisionSpinBox(-1, 1, 2) self.collision_normal_z = self._createCollisionSpinBox(-1, 1, 2) - + layout.addWidget(QLabel("Nx:"), current_row, 0) layout.addWidget(self.collision_normal_x, current_row, 1) current_row += 1 - + layout.addWidget(QLabel("Ny:"), current_row, 0) layout.addWidget(self.collision_normal_y, current_row, 1) current_row += 1 - + layout.addWidget(QLabel("Nz:"), current_row, 0) layout.addWidget(self.collision_normal_z, current_row, 1) current_row += 1 - + # 连接信号 self.collision_normal_x.valueChanged.connect(lambda v: self._updatePlaneNormal(model, 'x', v)) self.collision_normal_y.valueChanged.connect(lambda v: self._updatePlaneNormal(model, 'y', v)) self.collision_normal_z.valueChanged.connect(lambda v: self._updatePlaneNormal(model, 'z', v)) - + return current_row + def _hasCollision(self, model): """检查模型是否已有碰撞体""" try: from panda3d.core import CollisionNode - + # 检查模型及其子节点是否有碰撞节点 collision_nodes = model.findAllMatches("**/+CollisionNode") has_collision = collision_nodes.getNumPaths() > 0 - - print(f"碰撞检查:模型 {model.getName()} - {'有' if has_collision else '无'}碰撞 (找到{collision_nodes.getNumPaths()}个碰撞节点)") - + + print( + f"碰撞检查:模型 {model.getName()} - {'有' if has_collision else '无'}碰撞 (找到{collision_nodes.getNumPaths()}个碰撞节点)") + return has_collision except Exception as e: print(f"检查碰撞失败: {e}") return False - + def _isCollisionVisible(self, model): """检查碰撞是否可见""" try: @@ -8959,13 +9025,13 @@ except Exception as e: except Exception as e: print(f"检查碰撞可见性失败: {e}") return False - + def _toggleCollisionVisibility(self, model): """切换碰撞可见性""" try: collision_nodes = model.findAllMatches("**/+CollisionNode") is_visible = self._isCollisionVisible(model) - + for collision_np in collision_nodes: if is_visible: collision_np.hide() @@ -8973,13 +9039,13 @@ except Exception as e: else: collision_np.show() print(f"显示碰撞:{model.getName()}") - + # 立即更新按钮状态 self._updateCollisionVisibilityButton(model) - + except Exception as e: print(f"切换碰撞可见性失败: {e}") - + def _updateCollisionVisibilityButton(self, model): """更新碰撞可见性按钮状态""" try: @@ -8989,19 +9055,19 @@ except Exception as e: print(f"更新可见性按钮:{model.getName()} - {'可见' if is_visible else '隐藏'}") except Exception as e: print(f"更新碰撞可见性按钮失败: {e}") - + def _getCurrentCollisionShape(self, model): """获取当前模型的碰撞形状类型""" try: from panda3d.core import CollisionNode, CollisionSphere, CollisionBox, CollisionCapsule, CollisionPlane - + collision_nodes = model.findAllMatches("**/+CollisionNode") for collision_np in collision_nodes: collision_node = collision_np.node() if collision_node.getNumSolids() > 0: solid = collision_node.getSolid(0) solid_type = type(solid).__name__ - + if solid_type == "CollisionSphere": return "sphere" elif solid_type == "CollisionBox": @@ -9010,12 +9076,12 @@ except Exception as e: return "capsule" elif solid_type == "CollisionPlane": return "plane" - + return "sphere" # 默认返回球形 except Exception as e: print(f"获取碰撞形状失败: {e}") return "sphere" - + def _setComboToShape(self, shape_type): """根据形状类型设置下拉框选择""" shape_map = { @@ -9025,12 +9091,12 @@ except Exception as e: "plane": "平面 (Plane)", "auto": "自动选择 (Auto)" } - + if shape_type in shape_map: self.collision_shape_combo.setCurrentText(shape_map[shape_type]) else: self.collision_shape_combo.setCurrentText("球形 (Sphere)") - + def _getSelectedCollisionShape(self): """获取选中的碰撞形状类型""" if hasattr(self, 'collision_shape_combo'): @@ -9047,7 +9113,7 @@ except Exception as e: elif "自动选择" in shape_text: return 'auto' return 'sphere' # 默认返回球形 - + def _addCollisionAndUpdate(self, model): """添加指定形状的碰撞体并更新界面""" try: @@ -9055,9 +9121,9 @@ except Exception as e: if getattr(self, '_adding_collision', False): print("正在添加碰撞,跳过重复调用") return - + self._adding_collision = True - + if hasattr(self.world, 'scene_manager'): # 获取选中的碰撞形状 shape_type = self._getSelectedCollisionShape() @@ -9073,8 +9139,8 @@ except Exception as e: # 如果启用了模型间碰撞检测,添加额外的掩码 if (hasattr(self.world, 'collision_manager') and - hasattr(self.world.collision_manager, 'model_collision_enabled') and - self.world.collision_manager.model_collision_enabled): + hasattr(self.world.collision_manager, 'model_collision_enabled') 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) @@ -9114,21 +9180,11 @@ except Exception as e: print(f"✅ 为模型 {model.getName()} 添加了 {shape_type} 碰撞体") - # 简单更新按钮状态,不调用完整的状态更新 - if hasattr(self, 'collision_button'): - self.collision_button.setText("移除碰撞") - try: - self.collision_button.clicked.disconnect() - except: - pass - self.collision_button.clicked.connect(lambda: self._removeCollisionAndUpdate(model)) + # 重置更新标志,确保状态能够更新 + self._updating_collision_panel = False - if hasattr(self, 'collision_status_text'): - self.collision_status_text.setText("已启用") - self.collision_status_text.setStyleSheet("color: green;") - - if hasattr(self, 'collision_shape_combo'): - self.collision_shape_combo.setEnabled(False) + # 更新面板状态 - 添加参数控件和显示/隐藏按钮 + self._updateCollisionPanelState(model) else: print("场景管理器未初始化") @@ -9140,7 +9196,7 @@ except Exception as e: finally: # 确保标志被重置 self._adding_collision = False - + def _removeCollisionAndUpdate(self, model): """移除模型的碰撞体并更新界面""" try: @@ -9148,53 +9204,55 @@ except Exception as e: collision_nodes = model.findAllMatches("**/+CollisionNode") for collision_np in collision_nodes: collision_np.removeNode() - + print(f"移除了模型 {model.getName()} 的碰撞体") - + # 重置状态并更新界面 self._previous_collision_state = True # 强制刷新 + self._updating_collision_panel = False # 确保状态能够更新 self._updateCollisionPanelState(model) - + except Exception as e: print(f"移除碰撞失败: {e}") - + def _updateCollisionPanelState(self, model): """更新碰撞面板状态""" try: # 防止重复调用 if getattr(self, '_updating_collision_panel', False): return - + self._updating_collision_panel = True - - if hasattr(self, 'collision_button') and hasattr(self, 'collision_status_text') and hasattr(self, 'collision_shape_combo'): + + if hasattr(self, 'collision_button') and hasattr(self, 'collision_status_text') and hasattr(self, + 'collision_shape_combo'): has_collision = self._hasCollision(model) - + # 更新状态文本和颜色 self.collision_status_text.setText("已启用" if has_collision else "未启用") self.collision_status_text.setStyleSheet("color: green;" if has_collision else "color: red;") - + if has_collision: # 有碰撞:显示移除按钮,下拉框变为只读并显示当前类型 self.collision_button.setText("移除碰撞") - + # 先断开所有连接,再重新连接 try: self.collision_button.clicked.disconnect() except: pass self.collision_button.clicked.connect(lambda: self._removeCollisionAndUpdate(model)) - + # 获取并显示当前碰撞类型,设置为只读 current_shape = self._getCurrentCollisionShape(model) self._setComboToShape(current_shape) self.collision_shape_combo.setEnabled(False) - + # 确保参数控件存在 - 只在没有参数控件时才添加 if not hasattr(self, 'collision_pos_x'): print("添加碰撞参数控件") self._addParameterControlsToExistingPanel(model, current_shape) - + # 显示/隐藏按钮状态更新 if not hasattr(self, 'collision_visibility_button'): # 创建可见性按钮 @@ -9202,34 +9260,34 @@ except Exception as e: else: self.collision_visibility_button.setVisible(True) self._updateCollisionVisibilityButton(model) - + print(f"碰撞面板状态更新:有碰撞 - {current_shape}") - + else: # 无碰撞:显示添加按钮,下拉框变为可编辑 self.collision_button.setText("添加碰撞") - + # 先断开所有连接,再重新连接 try: self.collision_button.clicked.disconnect() except: pass self.collision_button.clicked.connect(lambda: self._addCollisionAndUpdate(model)) - + # 恢复为可编辑状态 self.collision_shape_combo.setEnabled(True) - + # 隐藏并清理参数控件 - 只在有参数控件时才清理 if hasattr(self, 'collision_pos_x'): print("清理碰撞参数控件") self._hideCollisionParameterControls() - + # 隐藏显示/隐藏按钮 if hasattr(self, 'collision_visibility_button'): self.collision_visibility_button.setVisible(False) - + print(f"碰撞面板状态更新:无碰撞 - 可编辑") - + except Exception as e: print(f"更新碰撞面板状态失败: {e}") import traceback @@ -9246,13 +9304,13 @@ except Exception as e: collision_node = collision_np.node() if collision_node.getNumSolids() > 0: solid = collision_node.getSolid(0) - + # 获取碰撞节点的位置 pos = collision_np.getPos() self.collision_pos_x.setValue(pos.x) self.collision_pos_y.setValue(pos.y) self.collision_pos_z.setValue(pos.z) - + if shape_type == 'sphere': self._loadSphereParameters(solid) elif shape_type == 'box': @@ -9262,10 +9320,10 @@ except Exception as e: elif shape_type == 'plane': self._loadPlaneParameters(solid) break - + except Exception as e: print(f"加载碰撞参数失败: {e}") - + def _loadSphereParameters(self, solid): """加载球形参数""" try: @@ -9275,7 +9333,7 @@ except Exception as e: self.collision_radius.setValue(radius) except Exception as e: print(f"加载球形参数失败: {e}") - + def _loadBoxParameters(self, solid): """加载盒型参数""" try: @@ -9286,13 +9344,13 @@ except Exception as e: width = max_point.x - min_point.x length = max_point.y - min_point.y height = max_point.z - min_point.z - + self.collision_width.setValue(width) self.collision_length.setValue(length) self.collision_height.setValue(height) except Exception as e: print(f"加载盒型参数失败: {e}") - + def _loadCapsuleParameters(self, solid): """加载胶囊体参数""" try: @@ -9302,12 +9360,12 @@ except Exception as e: point_a = solid.getPointA() point_b = solid.getPointB() height = (point_b - point_a).length() - + self.collision_capsule_radius.setValue(radius) self.collision_capsule_height.setValue(height) except Exception as e: print(f"加载胶囊体参数失败: {e}") - + def _loadPlaneParameters(self, solid): """加载平面参数""" try: @@ -9315,82 +9373,185 @@ except Exception as e: if isinstance(solid, CollisionPlane): plane = solid.getPlane() normal = plane.getNormal() - + self.collision_normal_x.setValue(normal.x) self.collision_normal_y.setValue(normal.y) self.collision_normal_z.setValue(normal.z) except Exception as e: print(f"加载平面参数失败: {e}") - + def _updateCollisionPosition(self, model, axis, value): - """更新碰撞位置""" + """更新碰撞位置偏移""" try: - collision_nodes = model.findAllMatches("**/+CollisionNode") - for collision_np in collision_nodes: - current_pos = collision_np.getPos() - if axis == 'x': - collision_np.setPos(value, current_pos.y, current_pos.z) - elif axis == 'y': - collision_np.setPos(current_pos.x, value, current_pos.z) - elif axis == 'z': - collision_np.setPos(current_pos.x, current_pos.y, value) - print(f"更新碰撞位置 {axis}: {value}") - break + # 防止重复调用导致无限循环 + if getattr(self, '_updating_collision_position', False): + return + self._updating_collision_position = True + + # 获取当前所有位置偏移值 + pos_x = self.collision_pos_x.value() if hasattr(self, 'collision_pos_x') else 0.0 + pos_y = self.collision_pos_y.value() if hasattr(self, 'collision_pos_y') else 0.0 + pos_z = self.collision_pos_z.value() if hasattr(self, 'collision_pos_z') else 0.0 + + # 获取当前碰撞形状类型 + current_shape = self._getCurrentCollisionShape(model) + if not current_shape: + return + + # 重新创建碰撞体,传入位置偏移 + from panda3d.core import Vec3 + position_offset = Vec3(pos_x, pos_y, pos_z) + + # 根据形状类型收集其他参数 + if current_shape == 'sphere': + radius = self.collision_radius.value() if hasattr(self, 'collision_radius') else 1.0 + self._recreateCollisionShape(model, current_shape, radius=radius, position_offset=position_offset) + elif current_shape == 'box': + width = self.collision_width.value() if hasattr(self, 'collision_width') else 2.0 + length = self.collision_length.value() if hasattr(self, 'collision_length') else 2.0 + height = self.collision_height.value() if hasattr(self, 'collision_height') else 2.0 + self._recreateCollisionShape(model, current_shape, width=width, length=length, height=height, position_offset=position_offset) + elif current_shape == 'capsule': + cap_radius = self.collision_capsule_radius.value() if hasattr(self, 'collision_capsule_radius') else 0.5 + cap_height = self.collision_capsule_height.value() if hasattr(self, 'collision_capsule_height') else 2.0 + self._recreateCollisionShape(model, current_shape, radius=cap_radius, height=cap_height, position_offset=position_offset) + elif current_shape == 'plane': + normal_x = self.collision_normal_x.value() if hasattr(self, 'collision_normal_x') else 0 + normal_y = self.collision_normal_y.value() if hasattr(self, 'collision_normal_y') else 0 + normal_z = self.collision_normal_z.value() if hasattr(self, 'collision_normal_z') else 1 + self._recreateCollisionShape(model, current_shape, normal=(normal_x, normal_y, normal_z), position_offset=position_offset) + + print(f"更新碰撞位置偏移 {axis}: {value}") + except Exception as e: print(f"更新碰撞位置失败: {e}") - + finally: + self._updating_collision_position = False + def _updateSphereRadius(self, model, radius): """更新球形半径""" try: - self._recreateCollisionShape(model, 'sphere', radius=radius) + # 防止重复调用导致无限循环 + if getattr(self, '_updating_sphere_radius', False): + return + self._updating_sphere_radius = True + + # 获取当前位置偏移 + from panda3d.core import Vec3 + pos_x = self.collision_pos_x.value() if hasattr(self, 'collision_pos_x') else 0.0 + pos_y = self.collision_pos_y.value() if hasattr(self, 'collision_pos_y') else 0.0 + pos_z = self.collision_pos_z.value() if hasattr(self, 'collision_pos_z') else 0.0 + position_offset = Vec3(pos_x, pos_y, pos_z) + + self._recreateCollisionShape(model, 'sphere', radius=radius, position_offset=position_offset) print(f"更新球形半径: {radius}") except Exception as e: print(f"更新球形半径失败: {e}") - + finally: + self._updating_sphere_radius = False + def _updateBoxSize(self, model, dimension, value): """更新盒型尺寸""" try: + # 防止重复调用导致无限循环 + if getattr(self, '_updating_box_size', False): + return + self._updating_box_size = True + # 获取当前所有尺寸 width = self.collision_width.value() if hasattr(self, 'collision_width') else value - length = self.collision_length.value() if hasattr(self, 'collision_length') else value + length = self.collision_length.value() if hasattr(self, 'collision_length') else value height = self.collision_height.value() if hasattr(self, 'collision_height') else value - - self._recreateCollisionShape(model, 'box', width=width, length=length, height=height) + + # 获取当前位置偏移 + from panda3d.core import Vec3 + pos_x = self.collision_pos_x.value() if hasattr(self, 'collision_pos_x') else 0.0 + pos_y = self.collision_pos_y.value() if hasattr(self, 'collision_pos_y') else 0.0 + pos_z = self.collision_pos_z.value() if hasattr(self, 'collision_pos_z') else 0.0 + position_offset = Vec3(pos_x, pos_y, pos_z) + + self._recreateCollisionShape(model, 'box', width=width, length=length, height=height, position_offset=position_offset) print(f"更新盒型{dimension}: {value}") except Exception as e: print(f"更新盒型尺寸失败: {e}") - + finally: + self._updating_box_size = False + def _updateCapsuleRadius(self, model, radius): """更新胶囊体半径""" try: + # 防止重复调用导致无限循环 + if getattr(self, '_updating_capsule_radius', False): + return + self._updating_capsule_radius = True + height = self.collision_capsule_height.value() if hasattr(self, 'collision_capsule_height') else 2.0 - self._recreateCollisionShape(model, 'capsule', radius=radius, height=height) + + # 获取当前位置偏移 + from panda3d.core import Vec3 + pos_x = self.collision_pos_x.value() if hasattr(self, 'collision_pos_x') else 0.0 + pos_y = self.collision_pos_y.value() if hasattr(self, 'collision_pos_y') else 0.0 + pos_z = self.collision_pos_z.value() if hasattr(self, 'collision_pos_z') else 0.0 + position_offset = Vec3(pos_x, pos_y, pos_z) + + self._recreateCollisionShape(model, 'capsule', radius=radius, height=height, position_offset=position_offset) print(f"更新胶囊体半径: {radius}") except Exception as e: print(f"更新胶囊体半径失败: {e}") - + finally: + self._updating_capsule_radius = False + def _updateCapsuleHeight(self, model, height): """更新胶囊体高度""" try: + # 防止重复调用导致无限循环 + if getattr(self, '_updating_capsule_height', False): + return + self._updating_capsule_height = True + radius = self.collision_capsule_radius.value() if hasattr(self, 'collision_capsule_radius') else 0.5 - self._recreateCollisionShape(model, 'capsule', radius=radius, height=height) + + # 获取当前位置偏移 + from panda3d.core import Vec3 + pos_x = self.collision_pos_x.value() if hasattr(self, 'collision_pos_x') else 0.0 + pos_y = self.collision_pos_y.value() if hasattr(self, 'collision_pos_y') else 0.0 + pos_z = self.collision_pos_z.value() if hasattr(self, 'collision_pos_z') else 0.0 + position_offset = Vec3(pos_x, pos_y, pos_z) + + self._recreateCollisionShape(model, 'capsule', radius=radius, height=height, position_offset=position_offset) print(f"更新胶囊体高度: {height}") except Exception as e: print(f"更新胶囊体高度失败: {e}") - + finally: + self._updating_capsule_height = False + def _updatePlaneNormal(self, model, axis, value): """更新平面法向量""" try: + # 防止重复调用导致无限循环 + if getattr(self, '_updating_plane_normal', False): + return + self._updating_plane_normal = True + # 获取当前法向量 normal_x = self.collision_normal_x.value() if hasattr(self, 'collision_normal_x') else 0 normal_y = self.collision_normal_y.value() if hasattr(self, 'collision_normal_y') else 0 normal_z = self.collision_normal_z.value() if hasattr(self, 'collision_normal_z') else 1 - - self._recreateCollisionShape(model, 'plane', normal=(normal_x, normal_y, normal_z)) + + # 获取当前位置偏移 + from panda3d.core import Vec3 + pos_x = self.collision_pos_x.value() if hasattr(self, 'collision_pos_x') else 0.0 + pos_y = self.collision_pos_y.value() if hasattr(self, 'collision_pos_y') else 0.0 + pos_z = self.collision_pos_z.value() if hasattr(self, 'collision_pos_z') else 0.0 + position_offset = Vec3(pos_x, pos_y, pos_z) + + self._recreateCollisionShape(model, 'plane', normal=(normal_x, normal_y, normal_z), position_offset=position_offset) print(f"更新平面法向量 {axis}: {value}") except Exception as e: print(f"更新平面法向量失败: {e}") - + finally: + self._updating_plane_normal = False + def _recreateCollisionShape(self, model, shape_type, **kwargs): """重新创建碰撞形状(保持位置和可见性)""" try: @@ -9398,19 +9559,19 @@ except Exception as e: collision_nodes = model.findAllMatches("**/+CollisionNode") if not collision_nodes: return - + collision_np = collision_nodes[0] current_pos = collision_np.getPos() is_visible = not collision_np.isHidden() - + # 移除旧的碰撞体 collision_np.removeNode() - + # 创建新的碰撞体 from panda3d.core import CollisionNode, BitMask32 cNode = CollisionNode(f'modelCollision_{model.getName()}') cNode.setIntoCollideMask(BitMask32.bit(2)) - + # 创建新形状 if hasattr(self.world, 'collision_manager'): collision_shape = self.world.collision_manager.createCollisionShape(model, shape_type, **kwargs) @@ -9418,23 +9579,23 @@ except Exception as e: # 回退方案 from panda3d.core import CollisionSphere, Point3 collision_shape = CollisionSphere(Point3(0, 0, 0), kwargs.get('radius', 1.0)) - + cNode.addSolid(collision_shape) - + # 重新附加并恢复状态 new_collision_np = model.attachNewNode(cNode) new_collision_np.setPos(current_pos) - + if is_visible: new_collision_np.show() else: new_collision_np.hide() - + except Exception as e: print(f"重新创建碰撞形状失败: {e}") import traceback traceback.print_exc() - + def _clearCollisionParameterControls(self): """清理碰撞参数控件""" try: @@ -9492,7 +9653,7 @@ except Exception as e: print("使用简化的面板刷新") # 直接调用状态更新,不删除面板 self._updateCollisionPanelState(model) - + except Exception as e: print(f"刷新碰撞面板失败: {e}") import traceback @@ -9503,52 +9664,52 @@ except Exception as e: try: if not hasattr(self, 'collision_layout'): return - + layout = self.collision_layout - + # 首先清理可能存在的旧控件 self._hideCollisionParameterControls() - + # 找到插入位置(在按钮之前) current_row = 2 - + # 位置调整控件 pos_label = QLabel("位置偏移:") pos_label.setVisible(True) # 确保可见 layout.addWidget(pos_label, current_row, 0) current_row += 1 - + # X, Y, Z 位置调整 self.collision_pos_x = self._createCollisionSpinBox(-100, 100, 2) - self.collision_pos_y = self._createCollisionSpinBox(-100, 100, 2) + self.collision_pos_y = self._createCollisionSpinBox(-100, 100, 2) self.collision_pos_z = self._createCollisionSpinBox(-100, 100, 2) - + x_label = QLabel("X:") x_label.setVisible(True) layout.addWidget(x_label, current_row, 0) self.collision_pos_x.setVisible(True) layout.addWidget(self.collision_pos_x, current_row, 1) current_row += 1 - + y_label = QLabel("Y:") y_label.setVisible(True) layout.addWidget(y_label, current_row, 0) self.collision_pos_y.setVisible(True) layout.addWidget(self.collision_pos_y, current_row, 1) current_row += 1 - + z_label = QLabel("Z:") z_label.setVisible(True) layout.addWidget(z_label, current_row, 0) self.collision_pos_z.setVisible(True) layout.addWidget(self.collision_pos_z, current_row, 1) current_row += 1 - + # 连接位置变化信号 self.collision_pos_x.valueChanged.connect(lambda v: self._updateCollisionPosition(model, 'x', v)) self.collision_pos_y.valueChanged.connect(lambda v: self._updateCollisionPosition(model, 'y', v)) self.collision_pos_z.valueChanged.connect(lambda v: self._updateCollisionPosition(model, 'z', v)) - + # 根据形状类型添加特定参数 if shape_type == 'sphere': current_row = self._addSphereParametersToExisting(model, layout, current_row) @@ -9558,13 +9719,13 @@ except Exception as e: current_row = self._addCapsuleParametersToExisting(model, layout, current_row) elif shape_type == 'plane': current_row = self._addPlaneParametersToExisting(model, layout, current_row) - + # 重新定位按钮 self._repositionButtons(current_row) - + # 加载参数值 self._loadCurrentCollisionParameters(model, shape_type) - + except Exception as e: print(f"添加参数控件到现有面板失败: {e}") import traceback @@ -9573,148 +9734,148 @@ except Exception as e: def _addSphereParametersToExisting(self, model, layout, start_row): """向现有面板添加球形参数""" current_row = start_row - + radius_label = QLabel("半径:") radius_label.setVisible(True) layout.addWidget(radius_label, current_row, 0) - + self.collision_radius = self._createCollisionSpinBox(0.1, 100, 2) self.collision_radius.setVisible(True) self.collision_radius.valueChanged.connect(lambda v: self._updateSphereRadius(model, v)) layout.addWidget(self.collision_radius, current_row, 1) current_row += 1 - + return current_row - + def _addBoxParametersToExisting(self, model, layout, start_row): """向现有面板添加盒型参数""" current_row = start_row - + size_label = QLabel("尺寸:") size_label.setVisible(True) layout.addWidget(size_label, current_row, 0) current_row += 1 - + self.collision_width = self._createCollisionSpinBox(0.1, 100, 2) self.collision_length = self._createCollisionSpinBox(0.1, 100, 2) self.collision_height = self._createCollisionSpinBox(0.1, 100, 2) - + width_label = QLabel("宽度:") width_label.setVisible(True) layout.addWidget(width_label, current_row, 0) self.collision_width.setVisible(True) layout.addWidget(self.collision_width, current_row, 1) current_row += 1 - + length_label = QLabel("长度:") length_label.setVisible(True) layout.addWidget(length_label, current_row, 0) self.collision_length.setVisible(True) layout.addWidget(self.collision_length, current_row, 1) current_row += 1 - + height_label = QLabel("高度:") height_label.setVisible(True) layout.addWidget(height_label, current_row, 0) self.collision_height.setVisible(True) layout.addWidget(self.collision_height, current_row, 1) current_row += 1 - + # 连接信号 self.collision_width.valueChanged.connect(lambda v: self._updateBoxSize(model, 'width', v)) self.collision_length.valueChanged.connect(lambda v: self._updateBoxSize(model, 'length', v)) self.collision_height.valueChanged.connect(lambda v: self._updateBoxSize(model, 'height', v)) - + return current_row - + def _addCapsuleParametersToExisting(self, model, layout, start_row): """向现有面板添加胶囊体参数""" current_row = start_row - + radius_label = QLabel("半径:") layout.addWidget(radius_label, current_row, 0) - + self.collision_capsule_radius = self._createCollisionSpinBox(0.1, 100, 2) self.collision_capsule_radius.valueChanged.connect(lambda v: self._updateCapsuleRadius(model, v)) layout.addWidget(self.collision_capsule_radius, current_row, 1) current_row += 1 - + height_label = QLabel("高度:") layout.addWidget(height_label, current_row, 0) - + self.collision_capsule_height = self._createCollisionSpinBox(0.1, 100, 2) self.collision_capsule_height.valueChanged.connect(lambda v: self._updateCapsuleHeight(model, v)) layout.addWidget(self.collision_capsule_height, current_row, 1) current_row += 1 - + return current_row - + def _addPlaneParametersToExisting(self, model, layout, start_row): """向现有面板添加平面参数""" current_row = start_row - + normal_label = QLabel("法向量:") layout.addWidget(normal_label, current_row, 0) current_row += 1 - + self.collision_normal_x = self._createCollisionSpinBox(-1, 1, 2) self.collision_normal_y = self._createCollisionSpinBox(-1, 1, 2) self.collision_normal_z = self._createCollisionSpinBox(-1, 1, 2) - + layout.addWidget(QLabel("Nx:"), current_row, 0) layout.addWidget(self.collision_normal_x, current_row, 1) current_row += 1 - + layout.addWidget(QLabel("Ny:"), current_row, 0) layout.addWidget(self.collision_normal_y, current_row, 1) current_row += 1 - + layout.addWidget(QLabel("Nz:"), current_row, 0) layout.addWidget(self.collision_normal_z, current_row, 1) current_row += 1 - + # 连接信号 self.collision_normal_x.valueChanged.connect(lambda v: self._updatePlaneNormal(model, 'x', v)) self.collision_normal_y.valueChanged.connect(lambda v: self._updatePlaneNormal(model, 'y', v)) self.collision_normal_z.valueChanged.connect(lambda v: self._updatePlaneNormal(model, 'z', v)) - + return current_row - + def _addVisibilityButtonToExistingPanel(self, model): """向现有面板添加可见性按钮""" try: if hasattr(self, 'collision_layout'): layout = self.collision_layout is_collision_visible = self._isCollisionVisible(model) - + # 找到合适的行位置 current_row = layout.rowCount() - + self.collision_visibility_button = QPushButton("隐藏碰撞" if is_collision_visible else "显示碰撞") self.collision_visibility_button.clicked.connect(lambda: self._toggleCollisionVisibility(model)) layout.addWidget(self.collision_visibility_button, current_row - 1, 0, 1, 2) - + except Exception as e: print(f"添加可见性按钮失败: {e}") - + def _repositionButtons(self, new_row): """重新定位按钮位置""" try: if hasattr(self, 'collision_layout'): layout = self.collision_layout - + # 移动可见性按钮 if hasattr(self, 'collision_visibility_button'): layout.addWidget(self.collision_visibility_button, new_row, 0, 1, 2) new_row += 1 - + # 移动主按钮 if hasattr(self, 'collision_button'): layout.addWidget(self.collision_button, new_row, 0, 1, 2) - + except Exception as e: print(f"重新定位按钮失败: {e}") - + def _hideCollisionParameterControls(self): """隐藏碰撞参数控件(保留按钮)""" try: @@ -9722,11 +9883,11 @@ except Exception as e: param_attrs = [ 'collision_pos_x', 'collision_pos_y', 'collision_pos_z', 'collision_radius', - 'collision_width', 'collision_length', 'collision_height', + 'collision_width', 'collision_length', 'collision_height', 'collision_capsule_radius', 'collision_capsule_height', 'collision_normal_x', 'collision_normal_y', 'collision_normal_z' ] - + # 隐藏并删除参数控件 for attr in param_attrs: if hasattr(self, attr): @@ -9736,14 +9897,14 @@ except Exception as e: widget.setParent(None) widget.deleteLater() delattr(self, attr) - + # 同时清理可能的标签控件 if hasattr(self, 'collision_layout'): layout = self.collision_layout - + # 收集需要移除的控件(不包括基本控件和按钮) widgets_to_remove = [] - + for i in range(layout.rowCount()): if i >= 2: # 从第3行开始检查 for j in range(layout.columnCount()): @@ -9753,17 +9914,19 @@ except Exception as e: if widget and hasattr(widget, 'text'): # 检查是否是参数相关的标签 text = widget.text() - if any(keyword in text for keyword in ['位置偏移', 'X:', 'Y:', 'Z:', '半径:', '尺寸:', '宽度:', '长度:', '高度:', '法向量:', 'Nx:', 'Ny:', 'Nz:']): + if any(keyword in text for keyword in + ['位置偏移', 'X:', 'Y:', 'Z:', '半径:', '尺寸:', '宽度:', '长度:', '高度:', + '法向量:', 'Nx:', 'Ny:', 'Nz:']): widgets_to_remove.append(widget) - + # 移除参数标签 for widget in widgets_to_remove: widget.setVisible(False) widget.setParent(None) widget.deleteLater() - + print("隐藏碰撞参数控件完成(保留按钮)") - + except Exception as e: print(f"隐藏碰撞参数控件失败: {e}") import traceback