EG/ui/panels/property_helpers.py
2026-02-25 11:49:31 +08:00

1461 lines
56 KiB
Python

import os
from pathlib import Path
from imgui_bundle import imgui, imgui_ctx
class PropertyHelpers:
"""Property, collision, material, and picker helper methods."""
def __init__(self, app):
self.app = app
def __getattr__(self, name):
return getattr(self.app, name)
def __setattr__(self, name, value):
if name == "app" or name in self.__dict__ or hasattr(type(self), name):
object.__setattr__(self, name, value)
else:
setattr(self.app, name, value)
def _apply_gui_font(self, gui_element, font_path):
"""应用GUI元素的字体"""
try:
if hasattr(gui_element, 'setFont') and font_path:
gui_element.setFont(font_path)
gui_element.font_path = font_path
except Exception as e:
print(f"应用GUI字体失败: {e}")
def _apply_gui_font_size(self, gui_element, font_size):
"""应用GUI元素的字体大小"""
try:
if hasattr(gui_element, 'setFontSize'):
gui_element.setFontSize(font_size)
gui_element.font_size = font_size
except Exception as e:
print(f"应用GUI字体大小失败: {e}")
def _apply_gui_font_style(self, gui_element):
"""应用GUI元素的字体样式"""
try:
if hasattr(gui_element, 'setFontStyle'):
style = 0
if getattr(gui_element, 'font_bold', False):
style |= 1 # 粗体
if getattr(gui_element, 'font_italic', False):
style |= 2 # 斜体
gui_element.setFontStyle(style)
except Exception as e:
print(f"应用GUI字体样式失败: {e}")
# 特定类型的属性
if gui_type == "button":
if imgui.collapsing_header("按钮属性"):
# 按钮状态
is_pressed = getattr(gui_element, 'pressed', False)
changed, new_pressed = imgui.checkbox("按下状态", is_pressed)
if changed:
gui_element.pressed = new_pressed
# 按钮回调
callback_name = getattr(gui_element, 'callback_name', '')
changed, new_callback = imgui.input_text("回调函数", callback_name, 64)
if changed:
gui_element.callback_name = new_callback
elif gui_type == "entry":
if imgui.collapsing_header("输入框属性"):
# 输入框内容
entry_text = getattr(gui_element, 'entry_text', '')
changed, new_text = imgui.input_text("输入内容", entry_text, 256)
if changed:
gui_element.entry_text = new_text
if hasattr(gui_element, 'set'):
gui_element.set(new_text)
# 最大长度
max_length = getattr(gui_element, 'max_length', 256)
changed, new_max = imgui.input_int("最大长度", max_length)
if changed:
gui_element.max_length = max(max_length, 1)
# 密码模式
is_password = getattr(gui_element, 'is_password', False)
changed, new_password = imgui.checkbox("密码模式", is_password)
if changed:
gui_element.is_password = new_password
if hasattr(gui_element, 'obscure'):
gui_element.obscure(new_password)
elif gui_type in ["2d_image", "3d_image"]:
if imgui.collapsing_header("图像属性"):
# 图像路径
image_path = getattr(gui_element, 'image_path', '')
changed, new_path = imgui.input_text("图像路径", image_path, 256)
if changed and hasattr(self, 'gui_manager'):
gui_element.image_path = new_path
# TODO: 重新加载图像
# 图像缩放模式
scale_mode = getattr(gui_element, 'scale_mode', 'stretch')
if imgui.begin_combo("缩放模式", scale_mode):
if imgui.selectable("拉伸##stretch"):
gui_element.scale_mode = 'stretch'
if imgui.selectable("适应##fit"):
gui_element.scale_mode = 'fit'
if imgui.selectable("填充##fill"):
gui_element.scale_mode = 'fill'
imgui.end_combo()
def _has_collision(self, node):
"""检查节点是否有碰撞体"""
try:
if not node or node.isEmpty():
return False
# 检查是否有碰撞节点
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
return True
return False
except Exception as e:
print(f"检查碰撞状态失败: {e}")
return False
def _get_current_collision_shape(self, node):
"""获取当前碰撞形状"""
try:
if not self._has_collision(node):
return "球形 (Sphere)"
# 查找碰撞节点并判断形状
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
# 根据碰撞节点名称判断形状
if 'sphere' in name.lower():
return "球形 (Sphere)"
elif 'box' in name.lower():
return "盒型 (Box)"
elif 'capsule' in name.lower():
return "胶囊体 (Capsule)"
elif 'plane' in name.lower():
return "平面 (Plane)"
return "球形 (Sphere)" # 默认
except Exception as e:
print(f"获取碰撞形状失败: {e}")
return "球形 (Sphere)"
def _get_current_collision_shape_type(self, node):
"""获取当前碰撞形状类型(内部标识)"""
try:
shape_name = self._get_current_collision_shape(node)
if "Sphere" in shape_name:
return "sphere"
elif "Box" in shape_name:
return "box"
elif "Capsule" in shape_name:
return "capsule"
elif "Plane" in shape_name:
return "plane"
else:
return "sphere"
except Exception as e:
print(f"获取碰撞形状类型失败: {e}")
return "sphere"
def _get_collision_position_offset(self, node):
"""获取碰撞体位置偏移"""
try:
if not self._has_collision(node):
return (0.0, 0.0, 0.0)
# 查找碰撞节点并获取位置
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
pos = child.getPos()
return (pos.x, pos.y, pos.z)
return (0.0, 0.0, 0.0)
except Exception as e:
print(f"获取碰撞位置失败: {e}")
return (0.0, 0.0, 0.0)
def _is_collision_visible(self, node):
"""检查碰撞体是否可见"""
try:
if not self._has_collision(node):
return False
# 查找碰撞节点并检查可见性
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
return child.isHidden() == False
return False
except Exception as e:
print(f"检查碰撞可见性失败: {e}")
return False
def _add_collision_to_node(self, node):
"""为节点添加碰撞体"""
try:
if not node or node.isEmpty():
print("无效的节点")
return
if self._has_collision(node):
print("节点已有碰撞体")
return
# 获取选择的形状
shape_name = getattr(self, '_selected_collision_shape', '球形 (Sphere)')
if hasattr(self, 'collision_manager'):
# 使用碰撞管理器添加碰撞体
shape_type = self._get_shape_type_from_name(shape_name)
collision_node = self.collision_manager.setupAdvancedCollision(
node,
shape_type=shape_type,
mask_type='MODEL_COLLISION'
)
if collision_node:
print(f"成功为节点 {node.getName()} 添加 {shape_name} 碰撞体")
else:
print(f"添加碰撞体失败")
else:
print("碰撞管理器未初始化")
except Exception as e:
print(f"添加碰撞体失败: {e}")
import traceback
traceback.print_exc()
def _remove_collision_from_node(self, node):
"""从节点移除碰撞体"""
try:
if not node or node.isEmpty():
print("无效的节点")
return
if not self._has_collision(node):
print("节点没有碰撞体")
return
# 查找并移除碰撞节点
children_to_remove = []
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
children_to_remove.append(child)
# 移除找到的碰撞节点
for child in children_to_remove:
child.removeNode()
if children_to_remove:
print(f"成功移除节点 {node.getName()} 的碰撞体")
else:
print(f"未找到碰撞体")
except Exception as e:
print(f"移除碰撞体失败: {e}")
import traceback
traceback.print_exc()
def _toggle_collision_visibility(self, node):
"""切换碰撞体可见性"""
try:
if not node or node.isEmpty():
return
# 查找碰撞节点并切换可见性
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
if child.isHidden():
child.show()
else:
child.hide()
break
except Exception as e:
print(f"切换碰撞可见性失败: {e}")
def _update_collision_position(self, node, axis, value):
"""更新碰撞体位置"""
try:
if not node or node.isEmpty():
return
# 查找碰撞节点并更新位置
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
current_pos = child.getPos()
if axis == 'x':
child.setPos(value, current_pos.y, current_pos.z)
elif axis == 'y':
child.setPos(current_pos.x, value, current_pos.z)
elif axis == 'z':
child.setPos(current_pos.x, current_pos.y, value)
break
except Exception as e:
print(f"更新碰撞位置失败: {e}")
def _get_shape_type_from_name(self, shape_name):
"""从形状名称获取形状类型"""
if "Sphere" in shape_name:
return "sphere"
elif "Box" in shape_name:
return "box"
elif "Capsule" in shape_name:
return "capsule"
elif "Plane" in shape_name:
return "plane"
else:
return "sphere"
def _get_sphere_radius(self, node):
"""获取球形半径"""
try:
# 从碰撞节点获取半径信息
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
if hasattr(child.node(), 'getSolids') and child.node().getNumSolids() > 0:
solid = child.node().getSolid(0)
from panda3d.core import CollisionSphere
if isinstance(solid, CollisionSphere):
return solid.getRadius()
return 1.0
except Exception as e:
print(f"获取球形半径失败: {e}")
return 1.0
def _update_sphere_radius(self, node, radius):
"""更新球形半径"""
try:
# 重新创建碰撞体来更新参数
if hasattr(self, 'collision_manager'):
# 先移除旧的碰撞体
self._remove_collision_from_node(node)
# 重新创建带有新参数的碰撞体
self.collision_manager.setupAdvancedCollision(
node,
shape_type='sphere',
mask_type='MODEL_COLLISION',
radius=radius
)
print(f"更新球形半径为: {radius}")
except Exception as e:
print(f"更新球形半径失败: {e}")
def _get_box_size(self, node):
"""获取盒型尺寸"""
try:
# 从碰撞节点获取尺寸信息
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
# 尝试从碰撞体获取尺寸
if hasattr(child.node(), 'getSolids') and child.node().getNumSolids() > 0:
solid = child.node().getSolid(0)
from panda3d.core import CollisionBox
if isinstance(solid, CollisionBox):
min_p = solid.getMin()
max_p = solid.getMax()
return (
max_p.x - min_p.x,
max_p.y - min_p.y,
max_p.z - min_p.z
)
return (1.0, 1.0, 1.0)
except Exception as e:
print(f"获取盒型尺寸失败: {e}")
return (1.0, 1.0, 1.0)
def _update_box_size(self, node, axis, value):
"""更新盒型尺寸"""
try:
# 获取当前尺寸
current_size = self._get_box_size(node)
new_size = list(current_size)
# 更新指定轴的尺寸
if axis == 'x':
new_size[0] = value
elif axis == 'y':
new_size[1] = value
elif axis == 'z':
new_size[2] = value
# 重新创建碰撞体
if hasattr(self, 'collision_manager'):
self._remove_collision_from_node(node)
self.collision_manager.setupAdvancedCollision(
node,
shape_type='box',
mask_type='MODEL_COLLISION',
width=new_size[0],
length=new_size[1],
height=new_size[2]
)
print(f"更新盒型尺寸: {new_size}")
except Exception as e:
print(f"更新盒型尺寸失败: {e}")
def _get_capsule_radius(self, node):
"""获取胶囊体半径"""
try:
# 从碰撞节点获取半径信息
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
if hasattr(child.node(), 'getSolids') and child.node().getNumSolids() > 0:
solid = child.node().getSolid(0)
from panda3d.core import CollisionCapsule
if isinstance(solid, CollisionCapsule):
return solid.getRadius()
return 1.0
except Exception as e:
print(f"获取胶囊体半径失败: {e}")
return 1.0
def _update_capsule_radius(self, node, radius):
"""更新胶囊体半径"""
try:
# 获取当前高度
height = self._get_capsule_height(node)
# 重新创建碰撞体
if hasattr(self, 'collision_manager'):
self._remove_collision_from_node(node)
self.collision_manager.setupAdvancedCollision(
node,
shape_type='capsule',
mask_type='MODEL_COLLISION',
radius=radius,
height=height
)
print(f"更新胶囊体半径为: {radius}")
except Exception as e:
print(f"更新胶囊体半径失败: {e}")
def _get_capsule_height(self, node):
"""获取胶囊体高度"""
try:
# 从碰撞节点获取高度信息
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
if hasattr(child.node(), 'getSolids') and child.node().getNumSolids() > 0:
solid = child.node().getSolid(0)
from panda3d.core import CollisionCapsule
if isinstance(solid, CollisionCapsule):
point_a = solid.getPointA()
point_b = solid.getPointB()
return (point_b - point_a).length() + 2 * solid.getRadius()
return 2.0
except Exception as e:
print(f"获取胶囊体高度失败: {e}")
return 2.0
def _update_capsule_height(self, node, height):
"""更新胶囊体高度"""
try:
# 获取当前半径
radius = self._get_capsule_radius(node)
# 重新创建碰撞体
if hasattr(self, 'collision_manager'):
self._remove_collision_from_node(node)
self.collision_manager.setupAdvancedCollision(
node,
shape_type='capsule',
mask_type='MODEL_COLLISION',
radius=radius,
height=height
)
print(f"更新胶囊体高度为: {height}")
except Exception as e:
print(f"更新胶囊体高度失败: {e}")
def _get_plane_normal(self, node):
"""获取平面法向量"""
try:
# 从碰撞节点获取法向量信息
for child in node.getChildren():
if hasattr(child, 'getName') and child.getName():
name = child.getName()
if 'collision' in name.lower() or 'Collision' in name:
if hasattr(child.node(), 'getSolids') and child.node().getNumSolids() > 0:
solid = child.node().getSolid(0)
from panda3d.core import CollisionPlane
if isinstance(solid, CollisionPlane):
plane = solid.getPlane()
normal = plane.getNormal()
return (normal.x, normal.y, normal.z)
return (0.0, 0.0, 1.0)
except Exception as e:
print(f"获取平面法向量失败: {e}")
return (0.0, 0.0, 1.0)
def _update_plane_normal(self, node, axis, value):
"""更新平面法向量"""
try:
# 获取当前法向量
current_normal = self._get_plane_normal(node)
new_normal = list(current_normal)
# 更新指定轴的值
if axis == 'x':
new_normal[0] = value
elif axis == 'y':
new_normal[1] = value
elif axis == 'z':
new_normal[2] = value
# 标准化法向量
from panda3d.core import Vec3
normal_vec = Vec3(*new_normal)
normal_vec.normalize()
# 重新创建碰撞体
if hasattr(self, 'collision_manager'):
self._remove_collision_from_node(node)
self.collision_manager.setupAdvancedCollision(
node,
shape_type='plane',
mask_type='MODEL_COLLISION',
normal=normal_vec
)
print(f"更新平面法向量为: ({normal_vec.x:.2f}, {normal_vec.y:.2f}, {normal_vec.z:.2f})")
except Exception as e:
print(f"更新平面法向量失败: {e}")
def _manual_collision_detection(self):
"""手动执行碰撞检测"""
try:
if hasattr(self, 'collision_manager'):
results = self.collision_manager.detectModelCollisions(log_results=True)
if results:
print(f"手动碰撞检测完成,发现 {len(results)} 个碰撞")
else:
print("手动碰撞检测完成,未发现碰撞")
except Exception as e:
print(f"手动碰撞检测失败: {e}")
def _update_node_name(self, node, new_name):
"""更新节点名称"""
if new_name and new_name != node.getName():
node.setName(new_name)
# 更新场景树显示
if hasattr(self, 'scene_tree'):
self.scene_tree.refresh()
def _get_material_base_color(self, material):
"""获取材质基础颜色"""
try:
if hasattr(material, 'base_color') and material.base_color is not None:
return (material.base_color.x, material.base_color.y, material.base_color.z, material.base_color.w)
elif hasattr(material, 'get_base_color'):
color = material.get_base_color()
return (color.x, color.y, color.z, color.w)
elif hasattr(material, 'getDiffuse'):
color = material.getDiffuse()
return (color.x, color.y, color.z, color.w if hasattr(color, 'w') else 1.0)
else:
return (1.0, 1.0, 1.0, 1.0) # 默认白色
except:
return (1.0, 1.0, 1.0, 1.0) # 默认白色
def _update_material_base_color(self, material, component, value):
"""更新材质基础颜色"""
try:
base_color = self._get_material_base_color(material)
new_color = list(base_color)
if component == 'r':
new_color[0] = value
elif component == 'g':
new_color[1] = value
elif component == 'b':
new_color[2] = value
elif component == 'a':
new_color[3] = value
new_color_tuple = tuple(new_color)
if hasattr(material, 'set_base_color'):
from panda3d.core import Vec4
material.set_base_color(Vec4(*new_color_tuple))
elif hasattr(material, 'setDiffuse'):
from panda3d.core import Vec4
material.setDiffuse(Vec4(*new_color_tuple))
except Exception as e:
print(f"更新材质基础颜色失败: {e}")
def _update_material_roughness(self, material, value):
"""更新材质粗糙度"""
try:
if hasattr(material, 'set_roughness'):
material.set_roughness(value)
except Exception as e:
print(f"更新材质粗糙度失败: {e}")
def _update_material_metallic(self, material, value):
"""更新材质金属性"""
try:
if hasattr(material, 'set_metallic'):
material.set_metallic(value)
except Exception as e:
print(f"更新材质金属性失败: {e}")
def _update_material_ior(self, material, value):
"""更新材质折射率"""
try:
if hasattr(material, 'set_refractive_index'):
material.set_refractive_index(value)
except Exception as e:
print(f"更新材质折射率失败: {e}")
def _apply_material_preset(self, material, preset_name):
"""应用材质预设"""
try:
from panda3d.core import Vec4, Material
presets = {
"默认": {
"base_color": Vec4(0.8, 0.8, 0.8, 1.0),
"roughness": 0.5,
"metallic": 0.0,
"ior": 1.5
},
"金属": {
"base_color": Vec4(0.7, 0.7, 0.8, 1.0),
"roughness": 0.2,
"metallic": 1.0,
"ior": 2.5
},
"塑料": {
"base_color": Vec4(0.9, 0.9, 0.9, 1.0),
"roughness": 0.8,
"metallic": 0.0,
"ior": 1.45
},
"玻璃": {
"base_color": Vec4(0.9, 0.9, 1.0, 0.2),
"roughness": 0.0,
"metallic": 0.0,
"ior": 1.5
},
"木材": {
"base_color": Vec4(0.6, 0.4, 0.2, 1.0),
"roughness": 0.9,
"metallic": 0.0,
"ior": 1.55
},
"混凝土": {
"base_color": Vec4(0.5, 0.5, 0.5, 1.0),
"roughness": 1.0,
"metallic": 0.0,
"ior": 1.5
}
}
if preset_name in presets:
preset = presets[preset_name]
# 应用基础颜色
if hasattr(material, 'set_base_color'):
material.set_base_color(preset["base_color"])
elif hasattr(material, 'setDiffuse'):
material.setDiffuse(preset["base_color"])
# 应用PBR属性
if hasattr(material, 'set_roughness'):
material.set_roughness(preset["roughness"])
if hasattr(material, 'set_metallic'):
material.set_metallic(preset["metallic"])
if hasattr(material, 'set_refractive_index'):
material.set_refractive_index(preset["ior"])
print(f"已应用材质预设: {preset_name}")
except Exception as e:
print(f"应用材质预设失败: {e}")
def _apply_material_to_node(self, node):
"""为节点应用材质"""
try:
from panda3d.core import Material, Vec4
# 检查是否已有材质
materials = node.find_all_materials()
if not materials:
# 创建新材质
material = Material(f"default-material-{node.getName()}")
material.setBaseColor(Vec4(0.8, 0.8, 0.8, 1.0))
material.setDiffuse(Vec4(0.8, 0.8, 0.8, 1.0))
material.setAmbient(Vec4(0.4, 0.4, 0.4, 1.0))
material.setSpecular(Vec4(0.1, 0.1, 0.1, 1.0))
material.setShininess(10.0)
node.setMaterial(material, 1)
print(f"已为新节点创建默认材质")
else:
print(f"节点已有 {len(materials)} 个材质")
except Exception as e:
print(f"应用材质失败: {e}")
def _reset_material(self, node):
"""重置节点材质"""
try:
materials = node.find_all_materials()
for material in materials:
# 重置为默认材质属性
from panda3d.core import Vec4
default_color = Vec4(0.8, 0.8, 0.8, 1.0)
if hasattr(material, 'set_base_color'):
material.set_base_color(default_color)
elif hasattr(material, 'setDiffuse'):
material.setDiffuse(default_color)
if hasattr(material, 'set_roughness'):
material.set_roughness(0.5)
if hasattr(material, 'set_metallic'):
material.set_metallic(0.0)
if hasattr(material, 'set_refractive_index'):
material.set_refractive_index(1.5)
print(f"已重置材质")
except Exception as e:
print(f"重置材质失败: {e}")
def _select_texture_for_material(self, node, material, texture_type):
"""为材质选择纹理"""
try:
# 设置当前纹理对话框状态
self._current_texture_dialog = {
'node': node,
'material': material,
'texture_type': texture_type
}
# 初始化路径
if not hasattr(self, '_texture_dialog_path'):
self._texture_dialog_path = '/home/hello/EG/Resources'
# 设置文件类型过滤
self._texture_dialog_filter = "*.png"
except Exception as e:
print(f"选择纹理失败: {e}")
def _apply_texture_to_material(self, node, material, texture_type, texture_path):
"""应用纹理到材质"""
try:
from panda3d.core import TextureStage
from direct.showbase import Loader
# 加载纹理
loader = Loader.Loader(self)
texture = loader.loadTexture(texture_path)
if not texture:
print(f"无法加载纹理: {texture_path}")
return
# 设置纹理属性
texture.setMagfilter(texture.FTLinear)
texture.setMinfilter(texture.FTLinearMipmapLinear)
# 纹理槽位映射
texture_slots = {
"diffuse": 0, # p3d_Texture0
"ior": 2, # p3d_Texture2
"normal": 1, # p3d_Texture1
"roughness": 3, # p3d_Texture3
"parallax": 4, # p3d_Texture4
"metallic": 5, # p3d_Texture5
"emission": 6, # p3d_Texture6
"ao": 7, # p3d_Texture7
"alpha": 8, # p3d_Texture8
"detail": 9, # p3d_Texture9
"gloss": 10 # p3d_Texture10
}
slot = texture_slots.get(texture_type, 0)
# 创建纹理阶段
texture_stage = TextureStage(f"{texture_type}_map")
texture_stage.setSort(slot)
texture_stage.setMode(TextureStage.MModulate)
# 应用纹理到节点
node.setTexture(texture_stage, texture)
print(f"已应用{texture_type}纹理: {texture_path}")
except Exception as e:
print(f"应用纹理失败: {e}")
def _clear_all_textures(self, node):
"""清除节点所有纹理"""
try:
# 清除所有纹理阶段
node.clearTexture()
node.clearTexture()
print("已清除所有纹理")
except Exception as e:
print(f"清除纹理失败: {e}")
def _display_current_textures(self, node, material):
"""显示当前纹理信息"""
try:
from panda3d.core import TextureStage
# 获取所有纹理阶段
texture_stages = node.findAllTextureStages()
if not texture_stages:
imgui.text_colored((0.7, 0.7, 0.7, 1.0), "当前无纹理")
return
imgui.text("当前纹理:")
for stage in texture_stages:
texture = node.getTexture(stage)
if texture:
texture_name = texture.getName() or "未命名"
stage_name = stage.getName() or "未命名"
imgui.text(f" {stage_name}: {texture_name}")
except Exception as e:
print(f"显示纹理信息失败: {e}")
def _update_shading_model(self, material, model_index):
"""更新着色模型"""
try:
from panda3d.core import Vec4
# 根据不同的着色模型设置相应的参数
if model_index == 1: # 自发光着色模型
print("设置自发光着色模型...")
if hasattr(material, 'set_emission'):
current_emission = material.emission or Vec4(0, 0, 0, 0)
new_emission = Vec4(1.0, current_emission.y, current_emission.z, current_emission.w)
material.set_emission(new_emission)
print("自发光着色模型设置完成")
elif model_index == 3: # 透明着色模型
print("设置透明着色模型...")
if hasattr(material, 'set_emission'):
current_emission = material.emission or Vec4(0, 0, 0, 0)
new_emission = Vec4(3.0, 0, 0, 0) # 3表示透明着色模型
material.set_emission(new_emission)
# 设置默认透明度
if hasattr(material, 'shading_model_param0'):
material.shading_model_param0 = 0.8 # 默认80%不透明度
print("透明着色模型设置完成")
else: # 默认着色模型
print("设置默认着色模型...")
if hasattr(material, 'set_emission'):
current_emission = material.emission or Vec4(0, 0, 0, 0)
new_emission = Vec4(0.0, current_emission.y, current_emission.z, current_emission.w)
material.set_emission(new_emission)
print("默认着色模型设置完成")
print(f"着色模型已更新为: {model_index} ({'自发光' if model_index == 1 else '透明' if model_index == 3 else '默认'})")
except Exception as e:
print(f"更新着色模型失败: {e}")
def _update_transparency(self, material, opacity_value):
"""更新透明度"""
try:
if hasattr(material, 'shading_model_param0'):
material.shading_model_param0 = opacity_value
print(f"透明度已更新: {opacity_value}")
except Exception as e:
print(f"更新透明度失败: {e}")
def _draw_texture_file_dialog(self):
"""绘制纹理文件选择对话框"""
if not hasattr(self, '_current_texture_dialog') or not self._current_texture_dialog:
return
try:
dialog_data = self._current_texture_dialog
node = dialog_data['node']
material = dialog_data['material']
texture_type = dialog_data['texture_type']
# 设置对话框标志
flags = (imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.modal)
# 获取屏幕尺寸,居中显示对话框
display_size = imgui.get_io().display_size
dialog_width = 600
dialog_height = 400
imgui.set_next_window_size((dialog_width, dialog_height))
imgui.set_next_window_pos(
((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2)
)
# 显示文件选择对话框
opened, window_open = imgui.begin(f"选择{texture_type}纹理文件##texture_dialog", True, flags)
if not window_open:
self._current_texture_dialog = None
imgui.end()
return
imgui.text(f"选择{texture_type}纹理文件")
imgui.separator()
# 当前路径显示
current_path = getattr(self, '_texture_dialog_path', '/home/hello/EG/Resources')
imgui.text(f"当前路径: {current_path}")
imgui.separator()
# 文件类型过滤
imgui.text("支持的纹理格式:")
file_types = ["*.png", "*.jpg", "*.jpeg", "*.bmp", "*.tga", "*.dds"]
current_filter = getattr(self, '_texture_dialog_filter', "*.png")
if imgui.begin_combo("文件类型##texture_filter", current_filter):
for i, file_type in enumerate(file_types):
if imgui.selectable(file_type, i == file_types.index(current_filter)):
self._texture_dialog_filter = file_type
imgui.end_combo()
imgui.separator()
# 路径导航按钮
if imgui.button("上级目录##up_dir"):
current_path = os.path.dirname(current_path)
self._texture_dialog_path = current_path
imgui.same_line()
if imgui.button("主目录##home_dir"):
self._texture_dialog_path = '/home/hello/EG/Resources'
imgui.same_line()
if imgui.button("当前目录##current_dir"):
self._texture_dialog_path = '/home/hello/EG/Resources'
imgui.same_line()
if imgui.button("纹理目录##textures_dir"):
self._texture_dialog_path = '/home/hello/EG/Resources/textures'
imgui.separator()
# 文件列表
if imgui.begin_child("file_list##texture_files", (580, 200)):
try:
# 列出目录和文件
items = []
if os.path.exists(current_path):
for item in os.listdir(current_path):
item_path = os.path.join(current_path, item)
if os.path.isdir(item_path):
items.append(('dir', item, item_path))
elif any(item.lower().endswith(ext[1:]) for ext in file_types):
items.append(('file', item, item_path))
# 排序:目录在前,文件在后
items.sort(key=lambda x: (x[0], x[1].lower()))
for item_type, item_name, item_path in items:
if item_type == 'dir':
if imgui.selectable(f"📁 {item_name}##dir_{item_name}", False)[0]:
self._texture_dialog_path = item_path
else:
selected, _ = imgui.selectable(f"📄 {item_name}##file_{item_name}", False)
if selected:
# 应用选择的纹理
self._apply_texture_to_material(node, material, texture_type, item_path)
# 关闭对话框
self._current_texture_dialog = None
break
except Exception as e:
imgui.text_colored((1.0, 0.5, 0.5, 1.0), f"读取目录失败: {e}")
imgui.end_child()
imgui.separator()
# 路径输入框
changed, new_path = imgui.input_text("文件路径##texture_path", current_path, 512)
if changed:
self._texture_dialog_path = new_path
imgui.same_line()
if imgui.button("确认路径##confirm_path"):
if os.path.isfile(new_path):
# 检查文件扩展名
file_ext = os.path.splitext(new_path)[1].lower()
if file_ext in [ext[1:] for ext in file_types]:
self._apply_texture_to_material(node, material, texture_type, new_path)
self._current_texture_dialog = None
else:
print(f"不支持的文件格式: {file_ext}")
else:
print("请选择有效的文件")
imgui.separator()
# 按钮
if imgui.button("取消##cancel_texture"):
self._current_texture_dialog = None
imgui.end()
except Exception as e:
print(f"绘制纹理对话框失败: {e}")
# 确保在异常情况下也调用 imgui.end()
try:
imgui.end()
except:
pass
def start_transform_monitoring(self, node):
"""开始变换监控"""
if node and not node.isEmpty():
self._monitored_node = node
self._transform_monitoring = True
self._transform_update_timer = 0
# 记录初始变换值
self._update_last_transform_values()
def stop_transform_monitoring(self):
"""停止变换监控"""
self._transform_monitoring = False
self._monitored_node = None
self._last_transform_values = {}
def _update_last_transform_values(self):
"""更新最后记录的变换值"""
if self._monitored_node and not self._monitored_node.isEmpty():
try:
pos = self._monitored_node.getPos()
hpr = self._monitored_node.getHpr()
scale = self._monitored_node.getScale()
self._last_transform_values = {
'pos': (pos.x, pos.y, pos.z),
'hpr': (hpr.x, hpr.y, hpr.z),
'scale': (scale.x, scale.y, scale.z)
}
except Exception as e:
print(f"更新变换值失败: {e}")
def _check_transform_changes(self):
"""检查变换变化"""
if not self._transform_monitoring or not self._monitored_node:
return
try:
pos = self._monitored_node.getPos()
hpr = self._monitored_node.getHpr()
scale = self._monitored_node.getScale()
current_values = {
'pos': (pos.x, pos.y, pos.z),
'hpr': (hpr.x, hpr.y, hpr.z),
'scale': (scale.x, scale.y, scale.z)
}
# 检查是否有变化
if current_values != self._last_transform_values:
# 更新记录的值
self._last_transform_values = current_values
# 触发属性面板更新(通过设置更新标志)
self.property_panel_update_timer = 0
except Exception as e:
print(f"检查变换变化失败: {e}")
def update_transform_monitoring(self, dt):
"""更新变换监控(在主循环中调用)"""
if not self._transform_monitoring:
return
self._transform_update_timer += dt
if self._transform_update_timer >= self._transform_update_interval:
self._transform_update_timer = 0
self._check_transform_changes()
def show_color_picker(self, target_object, property_name, initial_color, callback=None):
"""显示颜色选择器"""
self._color_picker_active = True
self._color_picker_target = (target_object, property_name)
self._color_picker_current_color = initial_color
self._color_picker_callback = callback
def _draw_color_picker(self):
"""绘制颜色选择器对话框"""
if not self._color_picker_active:
return
# 设置对话框标志
flags = (imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.modal)
# 获取屏幕尺寸,居中显示对话框
display_size = imgui.get_io().display_size
dialog_width = 300
dialog_height = 400
imgui.set_next_window_size((dialog_width, dialog_height))
imgui.set_next_window_pos(
((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2)
)
with imgui_ctx.begin("颜色选择器", True, flags) as window:
if not window.opened:
self._color_picker_active = False
self._color_picker_target = None
return
imgui.text("选择颜色")
imgui.separator()
# 颜色编辑器
changed, new_color = imgui.color_edit4(
"颜色##color_picker",
self._color_picker_current_color
)
if changed:
self._color_picker_current_color = new_color
# 预设颜色
imgui.text("预设颜色")
preset_colors = [
(1.0, 0.0, 0.0, 1.0), # 红色
(0.0, 1.0, 0.0, 1.0), # 绿色
(0.0, 0.0, 1.0, 1.0), # 蓝色
(1.0, 1.0, 0.0, 1.0), # 黄色
(1.0, 0.0, 1.0, 1.0), # 洋红
(0.0, 1.0, 1.0, 1.0), # 青色
(1.0, 1.0, 1.0, 1.0), # 白色
(0.0, 0.0, 0.0, 1.0), # 黑色
(0.5, 0.5, 0.5, 1.0), # 灰色
(0.188, 0.404, 0.753, 1.0), # 主题蓝色
(0.176, 1.0, 0.769, 1.0), # 主题绿色
(0.953, 0.616, 0.471, 1.0), # 主题橙色
]
# 绘制预设颜色按钮
colors_per_row = 6
for i, color in enumerate(preset_colors):
if i % colors_per_row != 0:
imgui.same_line()
imgui.color_button(f"preset_{i}", color, 0, (30, 30))
if imgui.is_item_clicked():
self._color_picker_current_color = color
imgui.separator()
# 按钮区域
if imgui.button("确定"):
self._apply_color_selection()
self._color_picker_active = False
self._color_picker_target = None
imgui.same_line()
if imgui.button("取消"):
self._color_picker_active = False
self._color_picker_target = None
def _apply_color_selection(self):
"""应用颜色选择"""
if not self._color_picker_target:
return
target_object, property_name = self._color_picker_target
try:
# 应用颜色到目标对象
if hasattr(target_object, 'setColor'):
target_object.setColor(self._color_picker_current_color)
elif hasattr(target_object, property_name):
setattr(target_object, property_name, self._color_picker_current_color)
# 调用回调函数
if self._color_picker_callback:
self._color_picker_callback(self._color_picker_current_color)
except Exception as e:
print(f"应用颜色失败: {e}")
def _draw_color_button(self, label, color, size=(50, 20)):
"""绘制颜色按钮并支持点击打开颜色选择器"""
imgui.color_button(label, color, 0, size)
if imgui.is_item_clicked():
# 打开颜色选择器
self.show_color_picker(None, None, color)
def _refresh_available_fonts(self):
"""刷新可用字体列表"""
try:
import platform
from pathlib import Path
system = platform.system().lower()
font_paths = []
if system == "linux":
font_dirs = [
"/usr/share/fonts/truetype/",
"/usr/share/fonts/opentype/",
"/usr/local/share/fonts/",
"~/.fonts/"
]
elif system == "windows":
font_dirs = [
"C:/Windows/Fonts/",
]
elif system == "darwin":
font_dirs = [
"/System/Library/Fonts/",
"/Library/Fonts/",
"~/Library/Fonts/"
]
else:
font_dirs = []
# 扫描字体目录
common_fonts = []
for font_dir in font_dirs:
font_path = Path(font_dir).expanduser()
if font_path.exists():
for font_file in font_path.rglob("*.ttf"):
common_fonts.append(str(font_file))
for font_file in font_path.rglob("*.otf"):
common_fonts.append(str(font_file))
for font_file in font_path.rglob("*.ttc"):
common_fonts.append(str(font_file))
# 过滤常见字体
font_keywords = [
"arial", "helvetica", "times", "courier", "verdana", "georgia",
"comic", "impact", "trebuchet", "palatino", "garamond",
"noto", "dejavu", "liberation", "ubuntu", "roboto", "open",
"droid", "source", "wenquanyi", "wqy", "pingfang", "stheiti",
"microsoft", "msyh", "simsun", "simhei", "kaiti", "fangsong"
]
self._available_fonts = []
for font_path in common_fonts:
font_name = Path(font_path).name.lower()
if any(keyword in font_name for keyword in font_keywords):
self._available_fonts.append(font_path)
# 添加一些默认字体路径
default_fonts = [
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
"/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
"/usr/share/fonts/opentype/noto/NotoSans-Regular.ttf",
"C:/Windows/Fonts/arial.ttf",
"C:/Windows/Fonts/msyh.ttc",
"/System/Library/Fonts/PingFang.ttc"
]
for font_path in default_fonts:
if Path(font_path).exists() and font_path not in self._available_fonts:
self._available_fonts.append(font_path)
print(f"✓ 找到 {len(self._available_fonts)} 个可用字体")
except Exception as e:
print(f"⚠ 字体扫描失败: {e}")
self._available_fonts = []
def show_font_selector(self, target_object, property_name, current_font, callback=None):
"""显示字体选择器"""
self._font_selector_active = True
self._font_selector_target = (target_object, property_name)
self._font_selector_current_font = current_font or ""
self._font_selector_callback = callback
def _draw_font_selector(self):
"""绘制字体选择器对话框"""
if not self._font_selector_active:
return
# 设置对话框标志
flags = (imgui.WindowFlags_.no_resize |
imgui.WindowFlags_.no_collapse |
imgui.WindowFlags_.modal)
# 获取屏幕尺寸,居中显示对话框
display_size = imgui.get_io().display_size
dialog_width = 400
dialog_height = 500
imgui.set_next_window_size((dialog_width, dialog_height))
imgui.set_next_window_pos(
((display_size.x - dialog_width) / 2, (display_size.y - dialog_height) / 2)
)
with imgui_ctx.begin("字体选择器", True, flags) as window:
if not window.opened:
self._font_selector_active = False
self._font_selector_target = None
return
imgui.text("选择字体")
imgui.separator()
# 当前字体显示
imgui.text(f"当前字体: {self._font_selector_current_font or '默认'}")
# 字体搜索框
changed, search_text = imgui.input_text("搜索", "", 256)
imgui.separator()
# 字体列表
if imgui.begin_child("font_list", (380, 300)):
for font_path in self._available_fonts:
font_name = Path(font_path).name
# 搜索过滤
if search_text and search_text.lower() not in font_name.lower():
continue
# 字体项
if imgui.selectable(font_name, font_path == self._font_selector_current_font):
self._font_selector_current_font = font_path
# 显示字体路径作为工具提示
if imgui.is_item_hovered():
imgui.set_tooltip(font_path)
imgui.end_child()
imgui.separator()
# 按钮区域
if imgui.button("确定"):
self._apply_font_selection()
self._font_selector_active = False
self._font_selector_target = None
imgui.same_line()
if imgui.button("取消"):
self._font_selector_active = False
self._font_selector_target = None
imgui.same_line()
if imgui.button("刷新字体"):
self._refresh_available_fonts()
def _apply_font_selection(self):
"""应用字体选择"""
if not self._font_selector_target:
return
target_object, property_name = self._font_selector_target
try:
# 应用字体到目标对象
if hasattr(target_object, property_name):
setattr(target_object, property_name, self._font_selector_current_font)
# 调用回调函数
if self._font_selector_callback:
self._font_selector_callback(self._font_selector_current_font)
except Exception as e:
print(f"应用字体失败: {e}")
def _draw_font_selector_button(self, label, current_font):
"""绘制字体选择器按钮"""
font_name = Path(current_font).name if current_font else "默认字体"
display_text = f"{font_name[:20]}..." if len(font_name) > 20 else font_name
if imgui.button(f"{label}: {display_text}##font_selector"):
self.show_font_selector(None, None, current_font)