1
0
forked from Rowland/EG

addRender #27

Merged
Hector merged 36 commits from addRender into main 2025-09-23 01:32:01 +00:00
2 changed files with 853 additions and 579 deletions
Showing only changes of commit f9bd83c876 - Show all commits

View File

@ -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']):
"""创建鼠标射线"""
# 组合掩码

View File

@ -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():
@ -292,16 +292,19 @@ 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)
@ -563,6 +566,7 @@ 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)
self._syncEffectiveVisibility(node)
@ -830,6 +834,7 @@ class PropertyPanelManager:
except:
pass
setattr(self, name, None)
def _refreshWorldPos(self, model):
if not hasattr(self, 'worldXSpin'):
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 标签居中
@ -1423,8 +1431,11 @@ class PropertyPanelManager:
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)
self.scale_x = QDoubleSpinBox()
@ -1532,7 +1543,6 @@ 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)
@ -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)
@ -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)
@ -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:
@ -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)
@ -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)
@ -3754,6 +3769,7 @@ class PropertyPanelManager:
except Exception as e:
print(f"❌ 停止视频失败: {e}")
return False
def _loadNew2DVideo(self, video_screen, item):
"""为2D视频屏幕加载新视频文件"""
try:
@ -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")
@ -4031,7 +4048,6 @@ class PropertyPanelManager:
print(f"❌ 停止3D视频失败: {e}")
return False
def _loadNewVideo(self, video_screen, item):
try:
file_path, _ = QFileDialog.getOpenFileName(
@ -4355,7 +4371,6 @@ class PropertyPanelManager:
traceback.print_exc()
return False
def _updateScriptPropertyPanel(self, game_object):
"""更新脚本属性面板"""
# 获取对象上的脚本
@ -4683,7 +4698,6 @@ class PropertyPanelManager:
return unique_names
def _updateModelMaterialPanel(self, model):
"""模型材质属性"""
if model.is_empty():
@ -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)
@ -5101,7 +5113,6 @@ class PropertyPanelManager:
print(f"检查材质状态时出错: {e}")
return "未知材质类型(可尝试编辑)"
def _getTextureModeString(self, mode):
"""获取纹理模式的字符串表示"""
from panda3d.core import TextureStage
@ -5369,8 +5380,6 @@ class PropertyPanelManager:
self._applyGlossTexture(material, normalized_path)
print(f"已选择光泽贴图:{filename} -> 标准化路径:{normalized_path}")
# def _applyDiffuseTexture(self, texture_path):
# from panda3d.core import TextureStage
# try:
@ -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}")
@ -5602,7 +5612,6 @@ class PropertyPanelManager:
import traceback
traceback.print_exc()
def _applyRoughnessTexture_FINAL(self, material, texture_path):
"""应用粗糙度贴图 - 先编译后绑定策略"""
try:
@ -5642,8 +5651,6 @@ class PropertyPanelManager:
else:
print("✅ 检测到材质已有法线贴图")
# 5. 检查是否有金属性贴图和透明漫反射贴图选择合适的PBR效果
print("🔧 步骤2检查金属性贴图和透明度设置...")
has_metallic = self._hasMetallicTexture(node)
@ -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:
@ -6439,8 +6445,6 @@ class PropertyPanelManager:
except:
pass
def _applyEmissionTexture(self, material, texture_path):
"""应用自发光贴图"""
try:
@ -7004,7 +7008,8 @@ 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):
"""添加透明度控制面板"""
@ -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
"""为当前选中的模型应用透明渲染效果(简化版本)"""
@ -7113,7 +7117,6 @@ class PropertyPanelManager:
sort=100
)
# 让RenderPipeline自动处理透明材质
# 当emission.x=3时RenderPipeline会自动设置正确的渲染参数
self.world.render_pipeline.prepare_scene(model)
@ -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},
@ -7649,7 +7653,6 @@ class PropertyPanelManager:
sun_group.setLayout(sun_layout)
self._propertyLayout.addWidget(sun_group)
def _onSunAzimuthSliderChanged(self, value):
"""滑块值改变时的回调"""
try:
@ -8544,8 +8547,6 @@ except Exception as e:
print(f"[系统转换] 系统转换失败: {e}")
return None
def _playAnimation(self, origin_model):
actor = self._getActor(origin_model)
if not actor:
@ -8570,7 +8571,6 @@ except Exception as e:
actor.play(display_name)
print(f"『动画播放』:{display_name}")
def _pauseAnimation(self, origin_model):
actor = self._getActor(origin_model)
if not actor:
@ -8603,7 +8603,6 @@ except Exception as e:
origin_model.show()
print("『动画』停止切换至原始模型")
def _loopAnimation(self, origin_model):
actor = self._getActor(origin_model)
if not actor:
@ -8672,7 +8671,8 @@ 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()
@ -8772,6 +8772,7 @@ except Exception as e:
except Exception as e:
print(f"创建碰撞面板失败: {e}")
def _addCollisionParameterControls(self, model, layout, start_row, shape_type):
"""添加碰撞参数调整控件"""
try:
@ -8840,6 +8841,20 @@ except Exception as e:
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
@ -8859,6 +8874,23 @@ 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)
layout.addWidget(QLabel("宽度:"), current_row, 0)
layout.addWidget(self.collision_width, current_row, 1)
current_row += 1
@ -8887,6 +8919,24 @@ except Exception as e:
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
@ -8895,6 +8945,20 @@ except Exception as e:
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
@ -8932,6 +8996,7 @@ except Exception as e:
self.collision_normal_z.valueChanged.connect(lambda v: self._updatePlaneNormal(model, 'z', v))
return current_row
def _hasCollision(self, model):
"""检查模型是否已有碰撞体"""
try:
@ -8941,7 +9006,8 @@ except Exception as e:
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:
@ -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("场景管理器未初始化")
@ -9153,6 +9209,7 @@ except Exception as e:
# 重置状态并更新界面
self._previous_collision_state = True # 强制刷新
self._updating_collision_panel = False # 确保状态能够更新
self._updateCollisionPanelState(model)
except Exception as e:
@ -9167,7 +9224,8 @@ except Exception as e:
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)
# 更新状态文本和颜色
@ -9323,73 +9381,176 @@ 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
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):
"""重新创建碰撞形状(保持位置和可见性)"""
@ -9753,7 +9914,9 @@ 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)
# 移除参数标签