1
0
forked from Rowland/EG
EG/ui/property_panel.py
2025-07-24 11:37:46 +08:00

1266 lines
52 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from types import new_class
from typing import Hashable
from PyQt5.QtWidgets import (QLabel, QLineEdit, QDoubleSpinBox, QPushButton,
QTreeWidget, QTreeWidgetItem, QMenu,QCheckBox)
from PyQt5.QtCore import Qt
from panda3d.core import Vec3, Vec4
class PropertyPanelManager:
"""属性面板管理器"""
def __init__(self, world):
"""初始化属性面板管理器"""
self.world = world
self._propertyLayout = None
def setPropertyLayout(self, layout):
"""设置属性面板布局引用"""
print("开始设置属性布局")
print(f"布局类型: {type(layout)}")
# 保存布局引用
self._propertyLayout = layout
# 确保布局有父部件
if not layout.parent():
print("布局没有父部件,创建新的容器")
from PyQt5.QtWidgets import QWidget
container = QWidget()
container.setObjectName("PropertyContainer")
container.setLayout(layout)
print(f"布局父部件: {self._propertyLayout.parent().objectName() if self._propertyLayout.parent() else 'None'}")
print(f"布局项目数: {self._propertyLayout.count()}")
return True
def clearPropertyPanel(self):
"""清空属性面板"""
if self._propertyLayout:
while self._propertyLayout.count():
item = self._propertyLayout.takeAt(0)
if item.widget():
item.widget().deleteLater()
def updatePropertyPanel(self, item):
"""更新属性面板显示"""
if not self._propertyLayout or not self._propertyLayout.parent():
print("属性布局未设置或没有父部件!")
return
self.clearPropertyPanel()
itemText = item.text(0)
# 如果点击的是场景根节点,显示提示信息
if itemText == "场景":
tipLabel = QLabel("")
tipLabel.setStyleSheet("color: gray;")
self._propertyLayout.addRow(tipLabel)
return
# 创建通用属性
nameLabel = QLabel("名称:")
nameEdit = QLineEdit(itemText)
self._propertyLayout.addRow(nameLabel, nameEdit)
# 获取节点对象
model = item.data(0, Qt.UserRole)
# 检查是否是GUI元素
if model and hasattr(model, 'getTag') and model.getTag("gui_type"):
self.updateGUIPropertyPanel(model)
elif model and hasattr(model,'getTag') and model.getTag("light_type"):
self.updateLightPropertyPanel(model)
elif model and hasattr(model,'getTag') and model.getTag("light_type"):
self.updateLightPropertyPanel(model)
# 如果找到模型,显示其属性
elif model:
self._updateModelPropertyPanel(model)
# 显示脚本属性
self._updateScriptPropertyPanel(model)
# 强制更新布局
if self._propertyLayout:
self._propertyLayout.update()
propertyWidget = self._propertyLayout.parentWidget()
if propertyWidget:
propertyWidget.update()
def _updateModelPropertyPanel(self, model):
"""更新模型属性面板"""
# 获取父节点
parent = model.getParent()
# 位置属性(相对于父节点)
relativePos = model.getPos(parent) if parent else model.getPos()
xPos = QDoubleSpinBox()
xPos.setRange(-1000, 1000)
xPos.setValue(relativePos.getX())
xPos.valueChanged.connect(lambda v: model.setX(parent, v) if parent else model.setX(v))
self._propertyLayout.addRow("相对位置 X:", xPos)
yPos = QDoubleSpinBox()
yPos.setRange(-1000, 1000)
yPos.setValue(relativePos.getY())
yPos.valueChanged.connect(lambda v: model.setY(parent, v) if parent else model.setY(v))
self._propertyLayout.addRow("相对位置 Y:", yPos)
zPos = QDoubleSpinBox()
zPos.setRange(-1000, 1000)
zPos.setValue(relativePos.getZ())
zPos.valueChanged.connect(lambda v: model.setZ(parent, v) if parent else model.setZ(v))
self._propertyLayout.addRow("相对位置 Z:", zPos)
# 世界位置(只读)
worldPos = model.getPos(self.world.render)
worldXPos = QDoubleSpinBox()
worldXPos.setRange(-1000, 1000)
worldXPos.setValue(worldPos.getX())
worldXPos.setReadOnly(True)
self._propertyLayout.addRow("世界位置 X:", worldXPos)
worldYPos = QDoubleSpinBox()
worldYPos.setRange(-1000, 1000)
worldYPos.setValue(worldPos.getY())
worldYPos.setReadOnly(True)
self._propertyLayout.addRow("世界位置 Y:", worldYPos)
worldZPos = QDoubleSpinBox()
worldZPos.setRange(-1000, 1000)
worldZPos.setValue(worldPos.getZ())
worldZPos.setReadOnly(True)
self._propertyLayout.addRow("世界位置 Z:", worldZPos)
# 旋转属性
hRot = QDoubleSpinBox()
hRot.setRange(-180, 180)
hRot.setValue(model.getH())
hRot.valueChanged.connect(lambda v: model.setH(v))
self._propertyLayout.addRow("旋转 H:", hRot)
pRot = QDoubleSpinBox()
pRot.setRange(-180, 180)
pRot.setValue(model.getP())
pRot.valueChanged.connect(lambda v: model.setP(v))
self._propertyLayout.addRow("旋转 P:", pRot)
rRot = QDoubleSpinBox()
rRot.setRange(-180, 180)
rRot.setValue(model.getR())
rRot.valueChanged.connect(lambda v: model.setR(v))
self._propertyLayout.addRow("旋转 R:", rRot)
# 缩放属性
xScale = QDoubleSpinBox()
xScale.setRange(0.01, 100)
xScale.setSingleStep(0.1)
xScale.setValue(model.getScale().getX())
xScale.valueChanged.connect(lambda v: model.setScale(v, model.getScale().getY(), model.getScale().getZ()))
self._propertyLayout.addRow("缩放 X:", xScale)
yScale = QDoubleSpinBox()
yScale.setRange(0.01, 100)
yScale.setSingleStep(0.1)
yScale.setValue(model.getScale().getY())
yScale.valueChanged.connect(lambda v: model.setScale(model.getScale().getX(), v, model.getScale().getZ()))
self._propertyLayout.addRow("缩放 Y:", yScale)
zScale = QDoubleSpinBox()
zScale.setRange(0.01, 100)
zScale.setSingleStep(0.1)
zScale.setValue(model.getScale().getZ())
zScale.valueChanged.connect(lambda v: model.setScale(model.getScale().getX(), model.getScale().getY(), v))
self._propertyLayout.addRow("缩放 Z:", zScale)
material_title = QLabel("材质属性")
material_title.setStyleSheet("color: #FF6B6B;font-weight:bold;font-size:14px;margin-top:10px;")
self._propertyLayout.addRow(material_title)
self._updateModelMaterialPanel(model)
def updateGUIPropertyPanel(self, gui_element):
"""更新GUI元素属性面板"""
gui_type = gui_element.getTag("gui_type")
gui_text = gui_element.getTag("gui_text")
# GUI类型显示
typeLabel = QLabel("GUI类型:")
typeValue = QLabel(gui_type)
typeValue.setStyleSheet("color: #00AAFF; font-weight: bold;")
self._propertyLayout.addRow(typeLabel, typeValue)
# 文本属性(如果适用)
if gui_type in ["button", "label", "entry", "3d_text", "virtual_screen"]:
textLabel = QLabel("文本:")
textEdit = QLineEdit(gui_text or "")
# 创建一个更新函数来处理文本变化
def updateText(text):
success = self.world.gui_manager.editGUIElement(gui_element, "text", text)
if success:
# 更新场景树显示的名称
self.world.scene_manager.updateSceneTree()
textEdit.textChanged.connect(updateText)
self._propertyLayout.addRow(textLabel, textEdit)
# 位置属性
if hasattr(gui_element, 'getPos'):
pos = gui_element.getPos()
# 根据GUI类型决定位置编辑方式
if gui_type in ["button", "label", "entry"]:
# 2D GUI组件使用屏幕坐标
logical_x = pos.getX() / 0.1 # 反向转换为逻辑坐标
logical_z = pos.getZ() / 0.1
xPos = QDoubleSpinBox()
xPos.setRange(-50, 50)
xPos.setValue(logical_x)
xPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "x", v))
self._propertyLayout.addRow("屏幕位置 X:", xPos)
zPos = QDoubleSpinBox()
zPos.setRange(-50, 50)
zPos.setValue(logical_z)
zPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUI2DPosition(gui_element, "z", v))
self._propertyLayout.addRow("屏幕位置 Z:", zPos)
# 显示实际屏幕坐标(只读)
actualXLabel = QLabel(f"{pos.getX():.3f}")
actualXLabel.setStyleSheet("color: gray; font-size: 10px;")
self._propertyLayout.addRow("实际屏幕 X:", actualXLabel)
actualZLabel = QLabel(f"{pos.getZ():.3f}")
actualZLabel.setStyleSheet("color: gray; font-size: 10px;")
self._propertyLayout.addRow("实际屏幕 Z:", actualZLabel)
else:
# 3D GUI组件使用世界坐标
xPos = QDoubleSpinBox()
xPos.setRange(-1000, 1000)
xPos.setValue(pos.getX())
xPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUIElement(gui_element, "position", [v, pos.getY(), pos.getZ()]))
self._propertyLayout.addRow("位置 X:", xPos)
yPos = QDoubleSpinBox()
yPos.setRange(-1000, 1000)
yPos.setValue(pos.getY())
yPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUIElement(gui_element, "position", [pos.getX(), v, pos.getZ()]))
self._propertyLayout.addRow("位置 Y:", yPos)
zPos = QDoubleSpinBox()
zPos.setRange(-1000, 1000)
zPos.setValue(pos.getZ())
zPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUIElement(gui_element, "position", [pos.getX(), pos.getY(), v]))
self._propertyLayout.addRow("位置 Z:", zPos)
# 缩放属性
if hasattr(gui_element, 'getScale'):
scale = gui_element.getScale()
scaleSpinBox = QDoubleSpinBox()
scaleSpinBox.setRange(0.01, 10)
scaleSpinBox.setSingleStep(0.1)
scaleSpinBox.setValue(scale.getX())
scaleSpinBox.valueChanged.connect(lambda v: self.world.gui_manager.editGUIElement(gui_element, "scale", v))
self._propertyLayout.addRow("缩放:", scaleSpinBox)
# 颜色属性针对2D GUI
if gui_type in ["button", "label"]:
colorButton = QPushButton("选择颜色")
colorButton.clicked.connect(lambda: self.world.gui_manager.selectGUIColor(gui_element))
self._propertyLayout.addRow("背景颜色:", colorButton)
def _updateScriptPropertyPanel(self, game_object):
"""更新脚本属性面板"""
# 获取对象上的脚本
scripts = self.world.getScripts(game_object)
if scripts:
# 添加脚本信息标题
scriptTitleLabel = QLabel("已挂载脚本:")
scriptTitleLabel.setStyleSheet("color: #00AAFF; font-weight: bold; font-size: 12px;")
self._propertyLayout.addRow(scriptTitleLabel)
# 显示每个脚本的信息
for i, script_component in enumerate(scripts):
script_name = script_component.script_name
enabled = script_component.enabled
# 脚本名称和状态
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)
# 脚本启用/禁用按钮
enableButton = QPushButton("禁用" if enabled else "启用")
enableButton.setStyleSheet(
"background-color: #FF6B6B; color: white;" if enabled
else "background-color: #4ECDC4; color: white;"
)
enableButton.clicked.connect(
lambda checked, sc=script_component: self._toggleScriptEnabled(sc)
)
self._propertyLayout.addRow("状态:", enableButton)
# 分隔线
if i < len(scripts) - 1:
separator = QLabel("" * 20)
separator.setStyleSheet("color: lightgray;")
self._propertyLayout.addRow(separator)
else:
# 显示无脚本信息
noScriptLabel = QLabel("无挂载脚本")
noScriptLabel.setStyleSheet("color: gray; font-style: italic;")
self._propertyLayout.addRow("脚本:", noScriptLabel)
def _toggleScriptEnabled(self, script_component):
"""切换脚本启用状态"""
script_component.enabled = not script_component.enabled
status = "启用" if script_component.enabled else "禁用"
print(f"脚本 {script_component.script_name}{status}")
# 刷新属性面板显示
if hasattr(self.world.selection, 'selectedObject') and self.world.selection.selectedObject:
# 找到当前选中项并更新
tree_widget = self.world.treeWidget
if tree_widget and tree_widget.currentItem():
self.updatePropertyPanel(tree_widget.currentItem())
def updateLightPropertyPanel(self, model):
"""更新模型属性面板"""
light_object = model.getPythonTag("rp_light_object")
if light_object:
current_pos = light_object.pos
xPos = QDoubleSpinBox()
xPos.setRange(-1000, 1000)
xPos.setValue(current_pos.getX())
xPos.valueChanged.connect(lambda v: self._updateLightPosition(light_object, model, 'x', v))
self._propertyLayout.addRow("相对位置 X:", xPos)
yPos = QDoubleSpinBox()
yPos.setRange(-1000,1000)
yPos.setValue(current_pos.getY())
yPos.valueChanged.connect(lambda v:self._updateLightPosition(light_object,model,'y',v))
self._propertyLayout.addRow("相对位置 X:",yPos)
zPos = QDoubleSpinBox()
zPos.setRange(-1000,1000)
zPos.setValue(current_pos.getZ())
zPos.valueChanged.connect(lambda v:self._updateLightPosition(light_object,model,'z',v))
self._propertyLayout.addRow("相对位置 Z:",zPos)
if hasattr(light_object,'direction'):
current_hpr = model.getHpr()
hRot = QDoubleSpinBox()
hRot.setRange(-180,180)
hRot.setValue(current_hpr.getX())
hRot.valueChanged.connect(lambda v:self._updateLightRotation(light_object,model,'h',v))
self._propertyLayout.addRow("旋转 H:",hRot)
pRot = QDoubleSpinBox()
pRot.setRange(-180,180)
pRot.setValue(current_hpr.getY())
pRot.valueChanged.connect(lambda v:self._updateLightRotation(light_object,model,'p',v))
self._propertyLayout.addRow("旋转 P:",pRot)
rRot = QDoubleSpinBox()
rRot.setRange(-180,180)
rRot.setValue(current_hpr.getZ())
rRot.valueChanged.connect(lambda v:self._updateLightRotation(light_object,model,'r',v))
self._propertyLayout.addRow("旋转 R:",rRot)
energySpinBox = QDoubleSpinBox()
energySpinBox.setRange(0,10000)
energySpinBox.setValue(light_object.energy)
energySpinBox.valueChanged.connect(lambda v:self._updateLightEnergy(light_object,v))
self._propertyLayout.addRow("能量:",energySpinBox)
radiusSpinBox = QDoubleSpinBox()
radiusSpinBox.setRange(1,2000)
radiusSpinBox.setValue(light_object.radius)
radiusSpinBox.valueChanged.connect(lambda v:self._updateLightRadius(light_object,v))
self._propertyLayout.addRow("半径:",radiusSpinBox)
if hasattr(light_object,'fov'):
fovSpinBox = QDoubleSpinBox()
fovSpinBox.setRange(1,180)
fovSpinBox.setValue(light_object.fov)
fovSpinBox.valueChanged.connect(lambda v:self._updateLightFOV(light_object,v))
self._propertyLayout.addRow("视野角度:",fovSpinBox)
shadowCheckBox = QCheckBox()
shadowCheckBox.setChecked(light_object.casts_shadows)
shadowCheckBox.stateChanged.connect(lambda state:self._updateLightCastsShadows(light_object,state==2))
self._propertyLayout.addRow("投射阴影:",shadowCheckBox)
current_scale = model.getScale()
xScaleSpinBox = QDoubleSpinBox()
xScaleSpinBox.setRange(0.01, 100)
xScaleSpinBox.setSingleStep(0.1)
xScaleSpinBox.setValue(current_scale.getX())
xScaleSpinBox.valueChanged.connect(lambda v: self._updateLightScale(model, 'x', v))
self._propertyLayout.addRow("缩放 X:", xScaleSpinBox)
yScaleSpinBox = QDoubleSpinBox()
yScaleSpinBox.setRange(0.01, 100)
yScaleSpinBox.setSingleStep(0.1)
yScaleSpinBox.setValue(current_scale.getY())
yScaleSpinBox.valueChanged.connect(lambda v: self._updateLightScale(model, 'y', v))
self._propertyLayout.addRow("缩放 Y:", yScaleSpinBox)
zScaleSpinBox = QDoubleSpinBox()
zScaleSpinBox.setRange(0.01, 100)
zScaleSpinBox.setSingleStep(0.1)
zScaleSpinBox.setValue(current_scale.getZ())
zScaleSpinBox.valueChanged.connect(lambda v: self._updateLightScale(model, 'z', v))
self._propertyLayout.addRow("缩放 Z:", zScaleSpinBox)
# 获取父节点
#parent = model.getParent()
# 位置属性(相对于父节点)
#relativePos = model.getPos(parent) if parent else model.getPos()
# xPos = QDoubleSpinBox()
# xPos.setRange(-1000, 1000)
# xPos.setValue(relativePos.getX())
# xPos.valueChanged.connect(lambda v: model.setX(parent, v) if parent else model.setX(v))
# self._propertyLayout.addRow("相对位置 X:", xPos)
#print(f"{model} x :{model.getPos()}")
# yPos = QDoubleSpinBox()
# yPos.setRange(-1000, 1000)
# yPos.setValue(relativePos.getY())
# yPos.valueChanged.connect(lambda v: model.setY(parent, v) if parent else model.setY(v))
# self._propertyLayout.addRow("相对位置 Y:", yPos)
#
# zPos = QDoubleSpinBox()
# zPos.setRange(-1000, 1000)
# zPos.setValue(relativePos.getZ())
# zPos.valueChanged.connect(lambda v: model.setZ(parent, v) if parent else model.setZ(v))
# self._propertyLayout.addRow("相对位置 Z:", zPos)
# 世界位置(只读)
worldPos = model.getPos(self.world.render)
worldXPos = QDoubleSpinBox()
worldXPos.setRange(-1000, 1000)
worldXPos.setValue(worldPos.getX())
worldXPos.setReadOnly(True)
self._propertyLayout.addRow("世界位置 X:", worldXPos)
worldYPos = QDoubleSpinBox()
worldYPos.setRange(-1000, 1000)
worldYPos.setValue(worldPos.getY())
worldYPos.setReadOnly(True)
self._propertyLayout.addRow("世界位置 Y:", worldYPos)
worldZPos = QDoubleSpinBox()
worldZPos.setRange(-1000, 1000)
worldZPos.setValue(worldPos.getZ())
worldZPos.setReadOnly(True)
self._propertyLayout.addRow("世界位置 Z:", worldZPos)
# 旋转属性
# hRot = QDoubleSpinBox()
# hRot.setRange(-180, 180)
# hRot.setValue(model.getH())
# hRot.valueChanged.connect(lambda v: model.setH(v))
# self._propertyLayout.addRow("旋转 H:", hRot)
#
# pRot = QDoubleSpinBox()
# pRot.setRange(-180, 180)
# pRot.setValue(model.getP())
# pRot.valueChanged.connect(lambda v: model.setP(v))
# self._propertyLayout.addRow("旋转 P:", pRot)
#
# rRot = QDoubleSpinBox()
# rRot.setRange(-180, 180)
# rRot.setValue(model.getR())
# rRot.valueChanged.connect(lambda v: model.setR(v))
# self._propertyLayout.addRow("旋转 R:", rRot)
# 缩放属性
# xScale = QDoubleSpinBox()
# xScale.setRange(0.01, 100)
# xScale.setSingleStep(0.1)
# xScale.setValue(model.getScale().getX())
# xScale.valueChanged.connect(lambda v: model.setScale(v, model.getScale().getY(), model.getScale().getZ()))
# self._propertyLayout.addRow("缩放 X:", xScale)
#
# yScale = QDoubleSpinBox()
# yScale.setRange(0.01, 100)
# yScale.setSingleStep(0.1)
# yScale.setValue(model.getScale().getY())
# yScale.valueChanged.connect(lambda v: model.setScale(model.getScale().getX(), v, model.getScale().getZ()))
# self._propertyLayout.addRow("缩放 Y:", yScale)
#
# zScale = QDoubleSpinBox()
# zScale.setRange(0.01, 100)
# zScale.setSingleStep(0.1)
# zScale.setValue(model.getScale().getZ())
# zScale.valueChanged.connect(lambda v: model.setScale(model.getScale().getX(), model.getScale().getY(), v))
# self._propertyLayout.addRow("缩放 Z:", zScale)
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())
elif axis == 'y':
new_pos = Vec3(current_pos.getX(), value, current_pos.getZ())
else: # z
new_pos = Vec3(current_pos.getX(), current_pos.getY(), value)
# 更新RenderPipeline光源位置
light_object.pos = new_pos
# 同步更新场景节点位置(用于显示)
node_path.setPos(new_pos)
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())
else:
new_hpr = Vec3(current_hpr.getX(),current_hpr.getY(),value)
node_path.setHpr(new_hpr)
if hasattr(light_object,'direction'):
direction_mat = node_path.getMat()
new_direction = direction_mat.xformVec(Vec3(0,1,0))
light_object.direction = new_direction
print(f"光源旋转已更新:{axis}={value}")
def _updateLightEnergy(self,light_object,value):
"""更新光源强度"""
light_object.energy = value
def _updateLightRadius(self,light_object,value):
"""更新光源半径"""
light_object.radius = value
def _updateLightFOV(self,light_Object,value):
"""更新聚光灯视野角度"""
if hasattr(light_Object,'fov'):
light_Object.fov = value
def _updateLightTemperature(self,light_object,value):
"""更新光源色温"""
light_object.set_color_from_temperature(value)
#保存色温值以便下次显示
light_object._temperature=value
def _updateLightInnerRadius(self,light_object,value):
"""更新点光源内半径"""
if hasattr(light_object,'inner_radius'):
light_object.inner_radius=value
def _updateLightShaowResolution(self,light_object,value):
"""更新阴影分辨率"""
light_object.shadow_map_resolution = value
def _updateLightNearPlane(self,light_object,value):
"""更新近平面距离"""
light_object.near_plane = value
def _updateLightCastsShadows(self,light_object,casts_shadows):
"""更新光源是否投射阴影"""
light_object.casts_shadows = casts_shadows
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())
else:
new_scale = Vec3(current_scale.getX(),current_scale.getY(),value)
node_path.setScale(new_scale)
def _updateModelMaterialPanel(self,model):
"""模型材质属性"""
materials = model.find_all_materials()
if not materials:
no_material_label=QLabel("无材质")
no_material_label.setStyleSheet(("color: gray;font-style:italic;"))
self._propertyLayout.addRow("材质:",no_material_label)
return
model_name=model.getName() or "未命名模型"
for i,material in enumerate(materials):
#材质名称属性
#print(f"Material{i+1}name:{material.get_name()}")
material_title = QLabel(f"材质{i+1}:{model_name}")
material_title.setStyleSheet("color:#00AAFF;font-weight:bold;font-size:12px")
self._propertyLayout.addRow(material_title)
#检查是否为PBR材质
#and material.has_roughness() and material.has_refractive_index()
if not (material.has_base_color() ):
non_pbr_label = QLabel("非PBR材质无法编辑")
non_pbr_label.setStyleSheet("color:orange;font-style:italic;")
self._propertyLayout.addRow("状态:",non_pbr_label)
continue
# if not material.has_base_color():
# non_pbr_label = QLabel("非PBR材质部分属性可能不可用")
# non_pbr_label.setStyleSheet("color:orange;font-style:italic;")
# self._propertyLayout.addRow("状态:", non_pbr_label)
#基础颜色编辑
base_color = material.base_color
#R分量
r_spinbox = QDoubleSpinBox()
r_spinbox.setRange(0.0,1.0)
r_spinbox.setSingleStep(0.01)
r_spinbox.setValue(base_color.x)
r_spinbox.valueChanged.connect(lambda v,mat = material:self._updateMaterialBaseColor(mat,'r',v))
self._propertyLayout.addRow("基础颜色 R:",r_spinbox)
#G分量
g_spinbox = QDoubleSpinBox()
g_spinbox.setRange(0.0, 1.0)
g_spinbox.setSingleStep(0.01)
g_spinbox.setValue(base_color.y)
g_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialBaseColor(mat, 'g', v))
self._propertyLayout.addRow("基础颜色 G:", g_spinbox)
# B分量
b_spinbox = QDoubleSpinBox()
b_spinbox.setRange(0.0, 1.0)
b_spinbox.setSingleStep(0.01)
b_spinbox.setValue(base_color.z)
b_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialBaseColor(mat, 'b', v))
self._propertyLayout.addRow("基础颜色 B:", b_spinbox)
# 添加Alpha分量透明度
alpha_spinbox = QDoubleSpinBox()
alpha_spinbox.setRange(0.0, 1.0)
alpha_spinbox.setSingleStep(0.01)
alpha_spinbox.setValue(base_color.w) # Alpha是Vec4的w分量
alpha_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialBaseColor(mat, 'a', v))
self._propertyLayout.addRow("透明度 (Alpha):", alpha_spinbox)
# 粗糙度
roughness_spinbox = QDoubleSpinBox()
roughness_spinbox.setRange(0.0, 1.0)
roughness_spinbox.setSingleStep(0.01)
roughness_spinbox.setValue(material.roughness)
roughness_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialRoughness(mat, v))
self._propertyLayout.addRow("粗糙度:", roughness_spinbox)
# 金属性
metallic_spinbox = QDoubleSpinBox()
metallic_spinbox.setRange(0.0, 1.0)
metallic_spinbox.setSingleStep(0.01)
metallic_spinbox.setValue(material.metallic)
metallic_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialMetallic(mat, v))
self._propertyLayout.addRow("金属性:", metallic_spinbox)
# 折射率
ior_spinbox = QDoubleSpinBox()
ior_spinbox.setRange(1.0, 3.0)
ior_spinbox.setSingleStep(0.01)
ior_spinbox.setValue(material.refractive_index)
ior_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialIOR(mat, v))
self._propertyLayout.addRow("折射率:", ior_spinbox)
texture_title = QLabel("纹理贴图")
texture_title.setStyleSheet("color: #4CAF50; font-weight:bold;font-size:11px;margin-top:5px;")
self._propertyLayout.addRow(texture_title)
#漫反射贴图
diffuse_button = QPushButton("选择漫反射贴图")
diffuse_button.clicked.connect(lambda checked,mat=material:self._selectDiffuseTexture(mat))
self._propertyLayout.addRow("漫反射贴图:",diffuse_button)
# #法线贴图
# normal_button = QPushButton("选择法线贴图")
# normal_button.clicked.connect(lambda checked,mat=material:self._selectNormalTexture(mat))
# self._propertyLayout.addRow("法线贴图:",normal_button)
#
# #粗糙度贴图
# roughness_button = QPushButton("选择粗糙度贴图")
# roughness_button.clicked.connect(lambda checked,mat=material:self._selectRoughnessTexture((mat)))
# self._propertyLayout.addRow("粗糙度贴图:",roughness_button)
#
# #金属性贴图
# metallic_button = QPushButton("选择金属性贴图")
# metallic_button.clicked.connect(lambda checked,mat=material:self._selectMetallicTexture(mat))
# self._propertyLayout.addRow("金属性贴图:",metallic_button)
# 显示当前贴图信息
self._displayCurrentTextures(material)
self._addShadingModelPanel(material)
self._addEmissionPanel(material)
self._addMaterialPresetPanel(material)
#self._addColorSpacePanel(material)
# 分隔线
if i < len(materials) - 1:
separator = QLabel("" * 30)
separator.setStyleSheet("color: lightgray;")
self._propertyLayout.addRow(separator)
def _updateMaterialBaseColor(self, material, component, value):
"""更新材质基础颜色"""
from panda3d.core import Vec4
current_color = material.base_color
if component == 'r':
new_color = Vec4(value, current_color.y, current_color.z, current_color.w)
elif component == 'g':
new_color = Vec4(current_color.x, value, current_color.z, current_color.w)
elif component == 'b':
new_color = Vec4(current_color.x, current_color.y, value, current_color.w)
elif component == 'a': # 添加Alpha分量处理
new_color = Vec4(current_color.x, current_color.y, current_color.z, value)
material.set_base_color(new_color)
self._invalidateRenderState()
def _updateMaterialRoughness(self, material, value):
"""更新材质粗糙度"""
material.set_roughness(value)
self._invalidateRenderState()
def _updateMaterialMetallic(self, material, value):
"""更新材质金属性"""
material.set_metallic(value)
self._invalidateRenderState()
def _updateMaterialIOR(self, material, value):
"""更新材质折射率"""
material.set_refractive_index(value)
self._invalidateRenderState()
def _invalidateRenderState(self):
"""使渲染状态失效以应用材质更改"""
from panda3d.core import RenderState
RenderState.clear_cache()
def _selectDiffuseTexture(self,material):
"""漫反射贴图"""
from PyQt5.QtWidgets import QFileDialog
import os
file_dialog = QFileDialog(None,"选择漫反射贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)")
if file_dialog.exec_():
filename = file_dialog.selectedFiles()[0]
if filename:
self._applyDiffuseTexture(material,filename)
print(f"已选择漫反射贴图:{filename}")
def _selectNormalTexture(self,material):
"""选择法线贴图"""
from PyQt5.QtWidgets import QFileDialog
file_dialog = QFileDialog(None,"选择法线贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)")
if file_dialog.exec_():
filename = file_dialog.selectedFiles()[0]
if filename:
self._applyNormalTexture(material,filename)
print(f"已选择法线贴图:{filename}")
def _selectRoughnessTexture(self,material):
"""选择粗糙度贴图"""
from PyQt5.QtWidgets import QFileDialog
file_dialog = QFileDialog(None,"选择粗糙度贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)")
if file_dialog.exec_():
filename = file_dialog.selectedFiles()[0]
if filename:
self._applyRoughnessTexture(material,filename)
print(f"已选择粗糙度贴图:{filename}")
def _selectMetallicTexture(self,material):
"""选择金属性贴图"""
from PyQt5.QtWidgets import QFileDialog
file_dialog = QFileDialog(None,"选择金属性贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)")
if file_dialog.exec_():
filename = file_dialog.selectedFiles()[0]
if filename:
self._applyMetallicTexture(material,filename)
print(f"已选择金属性贴图:{filename}")
def _applyDiffuseTexture(self,material,texture_path):
"""应用漫反射贴图"""
try:
from RenderPipelineFile.rpcore.loader import RPLoader
from panda3d.core import TextureStage
#加载纹理
texture = RPLoader.load_texture(texture_path)
if texture:
#material.set_base_color_texture(texture)
#获取材质所属的节点
node = self._findNodeWithMaterial(material)
if node:
#应用漫反射贴图到第一个纹理阶段
node.setTexture(TextureStage.getDefault(),texture,1)
self._invalidateRenderState()
print(f"漫反射贴图已应用:{texture_path}")
else:
print("没有找到材质")
else:
print("未找到材质对应的节点")
except Exception as e:
print(f"应用漫反射贴图失败{e}")
import traceback
traceback.print_exc()
def _applyNormalTexture(self,material,texture_path):
"""应用法线贴图"""
try:
from RenderPipelineFile.rpcore.loader import RPLoader
from panda3d.core import TextureStage
texture = RPLoader.load_texture(texture_path)
if texture:
node = self._findNodeWithMaterial(material)
if node:
#创建法线贴图纹理阶段
#normal_stage = TextureStage("normal")
node.setTexture(1,texture)
#normal_stage.setMode(TextureStage.MNormal)
#node.setTexture(normal_stage,texture)
self._invalidateRenderState()
print(f"法线贴图一应用:{texture_path}")
else:
print("未找到材质对应的节点")
except Exception as e:
print(f"应用法线贴图失败:{e}")
import traceback
traceback.print_exc()
def _applyRoughnessTexture(self,material,texture_path):
"""应用粗糙度贴图"""
try:
from RenderPipelineFile.rpcore.loader import RPLoader
from panda3d.core import TextureStage
texture = RPLoader.load_texture(texture_path)
if texture:
node = self._findNodeWithMaterial(material)
if node:
#创建粗糙度贴图纹理阶段
roughness_stage = TextureStage("roughtness")
roughness_stage.setMode(TextureStage.MModulate)
node.setTexture(roughness_stage,texture)
print(f"粗糙度贴图已应用:{texture_path}")
except Exception as e:
print(f"应用粗糙度贴图失败:{e}")
def _applyMetallicTexture(self,material,texture_path):
"""应用金属性贴图"""
try:
from RenderPipelineFile.rpcore.loader import RPLoader
from panda3d.core import TextureStage
texture = RPLoader.load_texture(texture_path)
if texture:
node = self._findNodeWithMaterial(material)
if node:
#创建金属性贴图纹理阶段
metallic_stage = TextureStage("metallic")
metallic_stage.setSort(2)
#metallic_stage.setMode(TextureStage.MModulate)
node.setTexture(metallic_stage,texture)
self._invalidateRenderState()
print(f"金属性贴图已应用:{texture_path}")
else:
print("未找到材质对应的节点")
except Exception as e:
print(f"应用金属性贴图失败:{e}")
import traceback
traceback.print_exc()
def _findNodeWithMaterial(self, target_material):
"""查找使用指定材质的节点"""
# 这里需要根据你的场景结构来实现
# 遍历场景中的所有节点,找到使用该材质的节点
# for model in self.world.scene_manager.models:
# materials = model.find_all_materials()
# if target_material in materials:
# return model
"""查找使用指定材质的节点"""
# 首先尝试在当前选中的模型中查找
current_item = self.world.treeWidget.currentItem()
if current_item:
current_model = current_item.data(0, Qt.UserRole)
if current_model:
materials = current_model.find_all_materials()
if target_material in materials:
return current_model
# 如果在当前选中模型中没找到,再遍历所有模型
for model in self.world.scene_manager.models:
materials = model.find_all_materials()
if target_material in materials:
return model
return None
def _displayCurrentTextures(self, material):
"""显示当前材质的贴图信息"""
node = self._findNodeWithMaterial(material)
if node:
# 显示当前应用的纹理信息
texture = node.getTexture()
if texture:
texture_name = texture.getName() or "未命名贴图"
texture_info = QLabel(f"当前贴图: {texture_name}")
texture_info.setStyleSheet("color: #666; font-size: 10px;")
self._propertyLayout.addRow("", texture_info)
def _applyToAllMaterials(self, model, property_name, value):
"""将属性应用到模型的所有材质"""
materials = model.find_all_materials()
for material in materials:
if property_name == "base_color":
material.set_base_color(value)
elif property_name == "roughness":
material.set_roughness(value)
elif property_name == "metallic":
material.set_metallic(value)
elif property_name == "ior":
material.set_refractive_index(value)
self._invalidateRenderState()
def _addShadingModelPanel(self, material):
"""添加着色模型选择面板"""
from PyQt5.QtWidgets import QComboBox
# RenderPipeline 支持的着色模型
SHADING_MODELS = [
("默认", 0),
("自发光", 1),
("透明涂层", 2),
("透明", 3),
("皮肤", 4),
("植物", 5),
]
shading_title = QLabel("着色模型")
shading_title.setStyleSheet("color: #4CAF50; font-weight:bold;")
self._propertyLayout.addRow(shading_title)
shading_combo = QComboBox()
for name, value in SHADING_MODELS:
shading_combo.addItem(name)
# 安全地获取当前着色模型
current_model = 0 # 默认值
try:
if hasattr(material, 'emission') and material.emission is not None:
current_model = int(material.emission.x)
except (AttributeError, TypeError, ValueError):
current_model = 0
shading_combo.setCurrentIndex(current_model)
shading_combo.currentIndexChanged.connect(
lambda idx: self._updateShadingModel(material, idx)
)
self._propertyLayout.addRow("着色模型:", shading_combo)
def _updateShadingModel(self, material, model_index):
"""更新着色模型"""
from panda3d.core import Vec4
# 安全地获取当前 emission 值
current_emission = Vec4(0, 0, 0, 0)
if hasattr(material, 'emission') and material.emission is not None:
current_emission = material.emission
# 如果切换到自发光模式,设置默认发光强度
if model_index == 1: # 自发光模式
default_emission_strength = 2.0 if current_emission.z == 0 else current_emission.z
new_emission = Vec4(float(model_index), current_emission.y, default_emission_strength, current_emission.w)
else:
new_emission = Vec4(float(model_index), current_emission.y, current_emission.z, current_emission.w)
material.set_emission(new_emission)
self._invalidateRenderState()
# 刷新UI以更新发光强度控件的值
if model_index == 1:
self._refreshMaterialUI()
print(f"着色模型已更新为: {model_index}")
def _addEmissionPanel(self, material):
"""添加自发光控制面板"""
emission_title = QLabel("自发光属性")
emission_title.setStyleSheet("color: #FF6B6B; font-weight:bold;")
self._propertyLayout.addRow(emission_title)
# 自发光强度
emission_spinbox = QDoubleSpinBox()
emission_spinbox.setRange(0.0, 10.0)
emission_spinbox.setSingleStep(0.1)
# 安全地获取当前自发光强度
current_emission_z = 0.0 # 默认值
if hasattr(material, 'emission') and material.emission is not None:
current_emission_z = material.emission.z
emission_spinbox.setValue(current_emission_z)
emission_spinbox.valueChanged.connect(lambda v: self._updateEmissionStrength(material, v))
self._propertyLayout.addRow("发光强度:", emission_spinbox)
def _updateEmissionStrength(self, material, strength):
"""更新自发光强度"""
from panda3d.core import Vec4
# 安全地获取当前 emission 值
current_emission = Vec4(0, 0, 0, 0)
if hasattr(material, 'emission') and material.emission is not None:
current_emission = material.emission
# 更新 emission.z 存储发光强度用于UI显示
new_emission = Vec4(current_emission.x, current_emission.y, strength, current_emission.w)
material.set_emission(new_emission)
# 对于自发光材质,直接调整基础颜色的亮度
if current_emission.x == 1: # 如果是自发光着色模型
# 获取原始基础颜色(假设存储在某处,或使用当前值的归一化版本)
base_intensity = 0.5 # 基础亮度
intensity_multiplier = strength / 5.0 # 将0-10范围映射到合理的倍数
# 设置发光颜色
emissive_color = Vec4(
base_intensity * intensity_multiplier,
base_intensity * intensity_multiplier,
base_intensity * intensity_multiplier,
1.0
)
material.set_base_color(emissive_color)
self._invalidateRenderState()
print(f"自发光强度已更新为: {strength}")
def _addMaterialPresetPanel(self, material):
"""添加材质预设面板"""
from PyQt5.QtWidgets import QComboBox
preset_title = QLabel("材质预设")
preset_title.setStyleSheet("color: #9C27B0; font-weight:bold;")
self._propertyLayout.addRow(preset_title)
preset_combo = QComboBox()
preset_combo.addItems([
"自定义", "塑料", "金属", "玻璃", "橡胶", "木材", "陶瓷", "皮革"
])
# 优先检查存储的预设名称
if hasattr(material, '_applied_preset'):
preset_combo.setCurrentText(material._applied_preset)
else:
# 回退到检测当前材质最接近的预设
current_preset = self._detectCurrentPreset(material)
preset_combo.setCurrentText(current_preset)
preset_combo.currentTextChanged.connect(
lambda preset: self._applyMaterialPreset(material, preset)
)
self._propertyLayout.addRow("选择预设:", preset_combo)
def _detectCurrentPreset(self, material):
"""检测当前材质最接近的预设"""
# 定义预设的精确匹配条件
presets = {
"塑料": {"base_color": (0.8, 0.8, 0.8), "roughness": 0.7, "metallic": 0.0, "ior": 1.4},
"金属": {"base_color": (0.7, 0.7, 0.7), "roughness": 0.1, "metallic": 1.0, "ior": 1.5},
"玻璃": {"base_color": (0.9, 0.9, 1.0), "roughness": 0.0, "metallic": 0.0, "ior": 1.5},
"橡胶": {"base_color": (0.2, 0.2, 0.2), "roughness": 0.9, "metallic": 0.0, "ior": 1.3},
"自发光": {"base_color": (1.0, 1.0, 1.0), "roughness": 0.5, "metallic": 0.0, "ior": 1.0}
}
# 容差值,用于浮点数比较
tolerance = 0.05
for preset_name, preset_values in presets.items():
# 检查基础颜色
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
)
# 检查其他属性
roughness_match = abs(material.roughness - preset_values["roughness"]) < tolerance
metallic_match = abs(material.metallic - preset_values["metallic"]) < tolerance
ior_match = abs(material.refractive_index - preset_values["ior"]) < tolerance
# 如果所有属性都匹配,返回预设名称
if base_color_match and roughness_match and metallic_match and ior_match:
return preset_name
return "自定义" # 如果没有匹配的预设
def _applyMaterialPreset(self, material, preset_name):
"""应用材质预设"""
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, 1.0), "roughness": 0.0, "metallic": 0.0, "ior": 1.5,"shading_model":3,"transparency":0.8},
"橡胶": {"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},
"皮革": {"base_color": Vec4(0.4, 0.3, 0.2, 1.0), "roughness": 0.6, "metallic": 0.0, "ior": 1.4}
}
if preset_name not in presets:
print(f"未知的材质预设: {preset_name}")
return
preset = presets[preset_name]
material.set_base_color(preset["base_color"])
material.set_roughness(preset["roughness"])
material.set_metallic(preset["metallic"])
material.set_refractive_index(preset["ior"])
if "shading_model" in preset:
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:
# self._apply_transparent_effect()
self._invalidateRenderState()
#material._applied_preset = preset_name
self._refreshMaterialUI()
print(f"已应用材质预设: {preset_name}")
def _apply_transparent_effect(self):
"""为当前选中的模型应用透明渲染效果"""
current_item = self.world.treeWidget.currentItem()
if current_item:
model = current_item.data(0, Qt.UserRole)
if model:
# 应用透明渲染效果
self.world.render_pipeline.set_effect(
model,
"effects/default.yaml",
{"render_forward": True, "render_gbuffer": False},
100
)
print("已应用透明渲染效果")
def _refreshMaterialUI(self):
"""刷新材质 UI 显示"""
# 重新更新当前选中项的属性面板
if hasattr(self.world, 'treeWidget') and self.world.treeWidget.currentItem():
current_item = self.world.treeWidget.currentItem()
# 触发属性面板更新
self.updatePropertyPanel(current_item)
def _addColorSpacePanel(self, material):
"""添加颜色空间选择面板"""
from PyQt5.QtWidgets import QButtonGroup, QRadioButton, QHBoxLayout, QWidget
# color_space_title = QLabel("颜色空间")
# color_space_title.setStyleSheet("color: #FF9800; font-weight:bold;")
# self._propertyLayout.addRow(color_space_title)
#
# color_space_widget = QWidget()
# color_space_layout = QHBoxLayout(color_space_widget)
# color_space_group = QButtonGroup()
# rgb_radio = QRadioButton("RGB")
# srgb_radio = QRadioButton("sRGB")
# hsv_radio = QRadioButton("HSV")
#
# rgb_radio.setChecked(True)
#
# color_space_group.addButton(rgb_radio, 0)
# color_space_group.addButton(srgb_radio, 1)
# color_space_group.addButton(hsv_radio, 2)
#
# color_space_layout.addWidget(rgb_radio)
# color_space_layout.addWidget(srgb_radio)
# color_space_layout.addWidget(hsv_radio)
#
# color_space_group.buttonClicked.connect(
# lambda button: self._updateColorSpace(material, color_space_group.id(button))
# )
#
# self._propertyLayout.addRow("颜色空间:", color_space_widget)
def _addBatchOperationsPanel(self, model):
"""添加批量操作面板"""
batch_title = QLabel("批量操作")
batch_title.setStyleSheet("color: #E91E63; font-weight:bold;")
self._propertyLayout.addRow(batch_title)
# 批量设置粗糙度
batch_roughness_btn = QPushButton("统一粗糙度")
batch_roughness_btn.clicked.connect(lambda: self._batchSetRoughness(model))
self._propertyLayout.addRow("批量粗糙度:", batch_roughness_btn)
# 批量设置金属性
batch_metallic_btn = QPushButton("统一金属性")
batch_metallic_btn.clicked.connect(lambda: self._batchSetMetallic(model))
self._propertyLayout.addRow("批量金属性:", batch_metallic_btn)
def _batchSetRoughness(self, model):
"""批量设置粗糙度"""
from PyQt5.QtWidgets import QInputDialog
value, ok = QInputDialog.getDouble(None, "批量设置", "粗糙度值:", 0.5, 0.0, 1.0, 2)
if ok:
self._applyToAllMaterials(model, "roughness", value)
print(f"已将所有材质粗糙度设置为: {value}")