From a1380f9a46d2f537a3999bf341071dd37f87f387 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=A8=AA?= <2938139566@qq.com> Date: Thu, 18 Sep 2025 09:49:59 +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=E4=BF=9D=E5=AD=98=E4=BF=AE=E6=94=B9=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/property_panel.py | 246 +++++++++++++++++++++++++++++-------------- 1 file changed, 168 insertions(+), 78 deletions(-) diff --git a/ui/property_panel.py b/ui/property_panel.py index 251f1a6c..4903b708 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -37,6 +37,9 @@ class PropertyPanelManager: self.world.terrain_edit_strength = 0.3 if not hasattr(self.world, 'terrain_edit_operation'): # 这里原来是 terrain_edit_opertaion self.world.terrain_edit_operation = "add" + + # 初始化碰撞参数加载标志位 + self._loading_collision_params = False # 定义紧凑样式 self.compact_style = """ @@ -8788,6 +8791,13 @@ except Exception as e: self.collision_pos_y = self._createCollisionSpinBox(-100, 100, 2) self.collision_pos_z = self._createCollisionSpinBox(-100, 100, 2) + # 只在没有现有碰撞时设置默认值,否则由_loadCurrentCollisionParameters加载实际值 + if not self._hasCollision(model): + # 设置默认位置偏移(无偏移) + self.collision_pos_x.setValue(0.0) + self.collision_pos_y.setValue(0.0) + self.collision_pos_z.setValue(0.0) + layout.addWidget(QLabel("X:"), current_row, 0) layout.addWidget(self.collision_pos_x, current_row, 1) current_row += 1 @@ -8842,18 +8852,20 @@ except Exception as e: 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) + # 只在没有现有碰撞时设置默认值,否则由_loadCurrentCollisionParameters加载实际值 + if not self._hasCollision(model): + # 设置基于模型变换后尺寸的默认值 + 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) @@ -8874,22 +8886,24 @@ except Exception as e: 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) + # 只在没有现有碰撞时设置默认值,否则由_loadCurrentCollisionParameters加载实际值 + if not self._hasCollision(model): + # 设置基于模型变换后尺寸的默认值 + 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) @@ -8920,22 +8934,24 @@ except Exception as e: 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) + # 只在没有现有碰撞时设置默认值,否则由_loadCurrentCollisionParameters加载实际值 + if not self._hasCollision(model): + # 设置基于模型变换后尺寸的默认值 + 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) @@ -8946,18 +8962,20 @@ except Exception as e: 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) + # 只在没有现有碰撞时设置默认值,否则由_loadCurrentCollisionParameters加载实际值 + if not self._hasCollision(model): + # 设置基于模型变换后高度的默认值 + 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) @@ -8978,6 +8996,13 @@ except Exception as e: self.collision_normal_y = self._createCollisionSpinBox(-1, 1, 2) self.collision_normal_z = self._createCollisionSpinBox(-1, 1, 2) + # 只在没有现有碰撞时设置默认值,否则由_loadCurrentCollisionParameters加载实际值 + if not self._hasCollision(model): + # 设置默认法向量(向上) + self.collision_normal_x.setValue(0.0) + self.collision_normal_y.setValue(0.0) + self.collision_normal_z.setValue(1.0) + layout.addWidget(QLabel("Nx:"), current_row, 0) layout.addWidget(self.collision_normal_x, current_row, 1) current_row += 1 @@ -9123,6 +9148,10 @@ except Exception as e: return self._adding_collision = True + + # 初始化加载参数标志位 + if not hasattr(self, '_loading_collision_params'): + self._loading_collision_params = False if hasattr(self.world, 'scene_manager'): # 获取选中的碰撞形状 @@ -9299,17 +9328,44 @@ except Exception as e: def _loadCurrentCollisionParameters(self, model, shape_type): """加载当前碰撞参数到界面""" try: + # 设置标志位,防止在加载参数时触发更新 + self._loading_collision_params = True + 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) - # 获取碰撞节点的位置 - 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 hasattr(self, 'collision_pos_x'): + # 获取模型的实际中心(考虑变换) + if hasattr(self.world, 'collision_manager'): + transformed_info = self.world.collision_manager._getTransformedModelInfo(model) + if transformed_info: + model_center = transformed_info['center'] + else: + model_center = model.getBounds().getCenter() if not model.getBounds().isEmpty() else Point3(0, 0, 0) + else: + model_center = model.getBounds().getCenter() if not model.getBounds().isEmpty() else Point3(0, 0, 0) + + # 获取碰撞体的中心 + collision_center = self._getCollisionShapeCenter(solid) + if collision_center: + # 计算偏移:碰撞体中心 - 模型中心 + offset_x = collision_center.x - model_center.x + offset_y = collision_center.y - model_center.y + offset_z = collision_center.z - model_center.z + self.collision_pos_x.setValue(offset_x) + self.collision_pos_y.setValue(offset_y) + self.collision_pos_z.setValue(offset_z) + print(f"加载位置偏移: ({offset_x:.2f}, {offset_y:.2f}, {offset_z:.2f})") + else: + # 如果无法计算偏移,设置为0 + self.collision_pos_x.setValue(0.0) + self.collision_pos_y.setValue(0.0) + self.collision_pos_z.setValue(0.0) + print("无法计算位置偏移,设置为0") if shape_type == 'sphere': self._loadSphereParameters(solid) @@ -9323,6 +9379,40 @@ except Exception as e: except Exception as e: print(f"加载碰撞参数失败: {e}") + finally: + # 重置标志位 + self._loading_collision_params = False + + def _getCollisionShapeCenter(self, solid): + """从碰撞体形状中获取中心点""" + try: + from panda3d.core import CollisionSphere, CollisionBox, CollisionCapsule, CollisionPlane, Point3 + + if isinstance(solid, CollisionSphere): + return solid.getCenter() + elif isinstance(solid, CollisionBox): + # 盒子的中心是最小点和最大点的中点 + min_pt = solid.getMin() + max_pt = solid.getMax() + return Point3((min_pt.x + max_pt.x) * 0.5, + (min_pt.y + max_pt.y) * 0.5, + (min_pt.z + max_pt.z) * 0.5) + elif isinstance(solid, CollisionCapsule): + # 胶囊体的中心是两个端点的中点 + point_a = solid.getPointA() + point_b = solid.getPointB() + return Point3((point_a.x + point_b.x) * 0.5, + (point_a.y + point_b.y) * 0.5, + (point_a.z + point_b.z) * 0.5) + elif isinstance(solid, CollisionPlane): + # 平面没有明确的中心,返回平面上的一个点 + plane = solid.getPlane() + return plane.getPoint() + else: + return None + except Exception as e: + print(f"获取碰撞体中心失败: {e}") + return None def _loadSphereParameters(self, solid): """加载球形参数""" @@ -9383,8 +9473,8 @@ except Exception as e: def _updateCollisionPosition(self, model, axis, value): """更新碰撞位置偏移""" try: - # 防止重复调用导致无限循环 - if getattr(self, '_updating_collision_position', False): + # 防止重复调用导致无限循环,以及在加载参数时防止更新 + if getattr(self, '_updating_collision_position', False) or getattr(self, '_loading_collision_params', False): return self._updating_collision_position = True @@ -9431,8 +9521,8 @@ except Exception as e: def _updateSphereRadius(self, model, radius): """更新球形半径""" try: - # 防止重复调用导致无限循环 - if getattr(self, '_updating_sphere_radius', False): + # 防止重复调用导致无限循环,以及在加载参数时防止更新 + if getattr(self, '_updating_sphere_radius', False) or getattr(self, '_loading_collision_params', False): return self._updating_sphere_radius = True @@ -9453,8 +9543,8 @@ except Exception as e: def _updateBoxSize(self, model, dimension, value): """更新盒型尺寸""" try: - # 防止重复调用导致无限循环 - if getattr(self, '_updating_box_size', False): + # 防止重复调用导致无限循环,以及在加载参数时防止更新 + if getattr(self, '_updating_box_size', False) or getattr(self, '_loading_collision_params', False): return self._updating_box_size = True @@ -9480,8 +9570,8 @@ except Exception as e: def _updateCapsuleRadius(self, model, radius): """更新胶囊体半径""" try: - # 防止重复调用导致无限循环 - if getattr(self, '_updating_capsule_radius', False): + # 防止重复调用导致无限循环,以及在加载参数时防止更新 + if getattr(self, '_updating_capsule_radius', False) or getattr(self, '_loading_collision_params', False): return self._updating_capsule_radius = True @@ -9504,8 +9594,8 @@ except Exception as e: def _updateCapsuleHeight(self, model, height): """更新胶囊体高度""" try: - # 防止重复调用导致无限循环 - if getattr(self, '_updating_capsule_height', False): + # 防止重复调用导致无限循环,以及在加载参数时防止更新 + if getattr(self, '_updating_capsule_height', False) or getattr(self, '_loading_collision_params', False): return self._updating_capsule_height = True @@ -9528,8 +9618,8 @@ except Exception as e: def _updatePlaneNormal(self, model, axis, value): """更新平面法向量""" try: - # 防止重复调用导致无限循环 - if getattr(self, '_updating_plane_normal', False): + # 防止重复调用导致无限循环,以及在加载参数时防止更新 + if getattr(self, '_updating_plane_normal', False) or getattr(self, '_loading_collision_params', False): return self._updating_plane_normal = True @@ -9553,7 +9643,7 @@ except Exception as e: self._updating_plane_normal = False def _recreateCollisionShape(self, model, shape_type, **kwargs): - """重新创建碰撞形状(保持位置和可见性)""" + """重新创建碰撞形状(保持可见性)""" try: # 保存当前状态 collision_nodes = model.findAllMatches("**/+CollisionNode") @@ -9561,7 +9651,6 @@ except Exception as e: return collision_np = collision_nodes[0] - current_pos = collision_np.getPos() is_visible = not collision_np.isHidden() # 移除旧的碰撞体 @@ -9572,7 +9661,7 @@ except Exception as e: 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) else: @@ -9582,9 +9671,10 @@ except Exception as e: cNode.addSolid(collision_shape) - # 重新附加并恢复状态 + # 重新附加(不设置额外位置,因为位置偏移已经在形状中) new_collision_np = model.attachNewNode(cNode) - new_collision_np.setPos(current_pos) + # 碰撞节点默认位置为 (0, 0, 0),位置偏移通过形状几何体处理 + new_collision_np.setPos(0, 0, 0) if is_visible: new_collision_np.show()