diff --git a/.idea/AugmentWebviewStateStore.xml b/.idea/AugmentWebviewStateStore.xml index a67ef38e..366351c1 100644 --- a/.idea/AugmentWebviewStateStore.xml +++ b/.idea/AugmentWebviewStateStore.xml @@ -3,7 +3,7 @@ diff --git a/RenderPipelineFile/config/daytime.yaml b/RenderPipelineFile/config/daytime.yaml index 31da0757..6bf933cb 100644 --- a/RenderPipelineFile/config/daytime.yaml +++ b/RenderPipelineFile/config/daytime.yaml @@ -17,8 +17,8 @@ control_points: scattering: sun_intensity: [[[0.0000000000,0.0000000000],[0.0041666667,0.0000000000],[0.0083333333,0.0000000000],[0.0125000000,0.0000000000],[0.0166666667,0.0000000000],[0.0208333333,0.0000000000],[0.0250000000,0.0000000000],[0.0291666667,0.0000000000],[0.0333333333,0.0000000000],[0.0375000000,0.0000000000],[0.0416666667,0.0000000000],[0.0458333333,0.0000000000],[0.0500000000,0.0000000000],[0.0541666667,0.0000000000],[0.0583333333,0.0000000000],[0.0625000000,0.0000000000],[0.0666666667,0.0000000000],[0.0708333333,0.0000000000],[0.0750000000,0.0000000000],[0.0791666667,0.0000000000],[0.0833333333,0.0000000000],[0.0875000000,0.0000000000],[0.0916666667,0.0000000000],[0.0958333333,0.0000000000],[0.1000000000,0.0000000000],[0.1041666667,0.0000000000],[0.1083333333,0.0000000000],[0.1125000000,0.0000000000],[0.1166666667,0.0000000000],[0.1208333333,0.0000000000],[0.1250000000,0.0000000000],[0.1291666667,0.0000000000],[0.1333333333,0.0000000000],[0.1375000000,0.0000000000],[0.1416666667,0.0000000000],[0.1458333333,0.0000000000],[0.1500000000,0.0000000000],[0.1541666667,0.0000000000],[0.1583333333,0.0000028805],[0.1625000000,0.0003577724],[0.1666666667,0.0013331400],[0.1708333333,0.0029671803],[0.1750000000,0.0052963381],[0.1791666667,0.0083550556],[0.1833333333,0.0121755589],[0.1875000000,0.0167876159],[0.1916666667,0.0222183530],[0.1958333333,0.0284919947],[0.2000000000,0.0356297193],[0.2041666667,0.0436494349],[0.2083333333,0.0525656099],[0.2125000000,0.0623891610],[0.2166666667,0.0731272461],[0.2208333333,0.0847831708],[0.2250000000,0.0973563167],[0.2291666667,0.1108419698],[0.2333333333,0.1252313631],[0.2375000000,0.1405115250],[0.2416666667,0.1566653434],[0.2458333333,0.1736715009],[0.2500000000,0.1915046014],[0.2541666667,0.2101350464],[0.2583333333,0.2295292930],[0.2625000000,0.2496498145],[0.2666666667,0.2704552670],[0.2708333333,0.2919006662],[0.2750000000,0.3139375192],[0.2791666667,0.3365139497],[0.2833333333,0.3595750662],[0.2875000000,0.3830630359],[0.2916666667,0.4069173972],[0.2958333333,0.4310753462],[0.3000000000,0.4554720417],[0.3041666667,0.4800408236],[0.3083333333,0.5047136020],[0.3125000000,0.5294212108],[0.3166666667,0.5540936424],[0.3208333333,0.5786605298],[0.3250000000,0.6030514553],[0.3291666667,0.6271963182],[0.3333333333,0.6510256858],[0.3375000000,0.6744711982],[0.3416666667,0.6974659988],[0.3458333333,0.7199450163],[0.3500000000,0.7418453485],[0.3541666667,0.7631067095],[0.3583333333,0.7836717291],[0.3625000000,0.8034862953],[0.3666666667,0.8224999302],[0.3708333333,0.8406661079],[0.3750000000,0.8579425235],[0.3791666667,0.8742914270],[0.3833333333,0.8896799131],[0.3875000000,0.9040801386],[0.3916666667,0.9174695289],[0.3958333333,0.9298310650],[0.4000000000,0.9411533765],[0.4041666667,0.9514309312],[0.4083333333,0.9606641691],[0.4125000000,0.9688595571],[0.4166666667,0.9760296330],[0.4208333333,0.9821930708],[0.4250000000,0.9873746114],[0.4291666667,0.9916050060],[0.4333333333,0.9949209310],[0.4375000000,0.9973647924],[0.4416666667,0.9989845508],[0.4458333333,0.9998334497],[0.4500000000,0.9999696949],[0.4541666667,0.9994560801],[0.4583333333,0.9983595429],[0.4625000000,0.9967506613],[0.4666666667,0.9947030614],[0.4708333333,0.9922927758],[0.4750000000,0.9895975125],[0.4791666667,0.9866958610],[0.4833333333,0.9836664262],[0.4875000000,0.9805868867],[0.4916666667,0.9775330316],[0.4958333333,0.9745777179],[0.5000000000,0.9717898417],[0.5041666667,0.9692332877],[0.5083333333,0.9669658924],[0.5125000000,0.9650384806],[0.5089595376,0.9690650222],[0.5208333333,0.9623666659],[0.5250000000,0.9616814371],[0.5291666667,0.9614534423],[0.5333333333,0.9616877089],[0.5375000000,0.9623790807],[0.5416666667,0.9635123329],[0.5458333333,0.9650624244],[0.5500000000,0.9669949804],[0.5541666667,0.9692669864],[0.5583333333,0.9718275065],[0.5625000000,0.9746185969],[0.5666666667,0.9775762863],[0.5708333333,0.9806315864],[0.5750000000,0.9837115661],[0.5791666667,0.9867403433],[0.5833333333,0.9896401655],[0.5875000000,0.9923323562],[0.5916666667,0.9947382579],[0.5958333333,0.9967800977],[0.6000000000,0.9983817820],[0.6041666667,0.9994696263],[0.6083333333,0.9999730028],[0.6125000000,0.9998249266],[0.6166666667,0.9989625601],[0.6208333333,0.9973276624],[0.6250000000,0.9948669567],[0.6291666667,0.9915324664],[0.6333333333,0.9872817545],[0.6375000000,0.9820781426],[0.6416666667,0.9758908775],[0.6458333333,0.9686952146],[0.6500000000,0.9604725211],[0.6541666667,0.9512102537],[0.6583333333,0.9409019858],[0.6625000000,0.9295473441],[0.6666666667,0.9171518878],[0.6708333333,0.9037270619],[0.6750000000,0.8892899902],[0.6791666667,0.8738633008],[0.6833333333,0.8574749656],[0.6875000000,0.8401579787],[0.6916666667,0.8219502453],[0.6958333333,0.8028941798],[0.7000000000,0.7830364456],[0.7041666667,0.7624277344],[0.7083333333,0.7411222520],[0.7125000000,0.7191776044],[0.7166666667,0.6966542563],[0.7208333333,0.6736152714],[0.7250000000,0.6501259629],[0.7291666667,0.6262533880],[0.7333333333,0.6020661121],[0.7375000000,0.5776338043],[0.7416666667,0.5530267796],[0.7458333333,0.5283156992],[0.7500000000,0.5035711751],[0.7541666667,0.4788634341],[0.7583333333,0.4542618347],[0.7625000000,0.4298347613],[0.7666666667,0.4056490351],[0.7708333333,0.3817697830],[0.7750000000,0.3582600107],[0.7791666667,0.3351803495],[0.7833333333,0.3125888445],[0.7875000000,0.2905406366],[0.7916666667,0.2690876955],[0.7958333333,0.2482787388],[0.8000000000,0.2281588906],[0.8041666667,0.2087696425],[0.8083333333,0.1901486315],[0.8125000000,0.1723295359],[0.8166666667,0.1553419918],[0.8208333333,0.1392115328],[0.8250000000,0.1239595144],[0.8291666667,0.1096030703],[0.8333333333,0.0961551918],[0.8375000000,0.0836246599],[0.8416666667,0.0720161369],[0.8458333333,0.0613302273],[0.8500000000,0.0515635598],[0.8541666667,0.0427088803],[0.8583333333,0.0347551990],[0.8625000000,0.0276878920],[0.8666666667,0.0214889271],[0.8708333333,0.0161369711],[0.8750000000,0.0116076130],[0.8791666667,0.0078735477],[0.8833333333,0.0049047927],[0.8875000000,0.0026688977],[0.8916666667,0.0011311782],[0.8958333333,0.0002549473],[0.9000000000,0.0000000000],[0.9041666667,0.0000000000],[0.9083333333,0.0000000000],[0.9125000000,0.0000000000],[0.9166666667,0.0000000000],[0.9208333333,0.0000000000],[0.9250000000,0.0000000000],[0.9291666667,0.0000000000],[0.9333333333,0.0000000000],[0.9375000000,0.0000000000],[0.9416666667,0.0000000000],[0.9458333333,0.0000000000],[0.9500000000,0.0000000000],[0.9541666667,0.0000000000],[0.9583333333,0.0000000000],[0.9625000000,0.0000000000],[0.9666666667,0.0000000000],[0.9708333333,0.0000000000],[0.9750000000,0.0000000000],[0.9791666667,0.0000000000],[0.9833333333,0.0000000000],[0.9875000000,0.0000000000],[0.9916666667,0.0000000000],[0.9958333333,0.0000000000]]] sun_color: [[[0.5010435645,0.5818710306],[0.0433100000,0.8999700000],[0.8635787716,0.9130000000],[0.1785000000,0.8973600000],[0.8099800000,0.8651100000],[0.2360800000,0.7712700000],[0.6583432177,0.8485126184],[0.1266806142,0.9648102053],[0.9558541267,0.9090909091],[0.5568400771,0.7353760446]],[[0.5001318426,0.5160300000],[0.0572700000,0.6541600000],[0.2395000000,0.5976800000],[0.8104600000,0.6009000000],[0.6967400000,0.5483900000]],[[0.0862400000,0.4257800000],[0.4955600000,0.4033000000],[0.8234200000,0.4340200000]]] - sun_azimuth: [[[0.5000000000,0.4472222222]]] - sun_altitude: [[[0.5000000000,1.0000000000]]] + sun_azimuth: [[[0.5000000000,0.3000000000]]] + sun_altitude: [[[0.5000000000,0.5000000000]]] extinction: [[[0.4913294798,0.6378830084]]] volumetrics: fog_ramp_size: [[[0.5510597303,0.7409470752]]] diff --git a/ui/main_window.py b/ui/main_window.py index 71752d1e..1422be63 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -12,7 +12,7 @@ from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QMenu, QAction QDockWidget, QTreeWidget, QListWidget, QWidget, QVBoxLayout, QTreeWidgetItem, QLabel, QLineEdit, QFormLayout, QDoubleSpinBox, QScrollArea, QFileSystemModel, QButtonGroup, QToolButton, QPushButton, QHBoxLayout, - QComboBox, QGroupBox, QInputDialog, QFileDialog, QMessageBox, QDesktopWidget) + QComboBox, QGroupBox, QInputDialog, QFileDialog, QMessageBox, QDesktopWidget, QGridLayout) from PyQt5.QtCore import Qt, QDir, QTimer from ui.widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget @@ -24,8 +24,8 @@ class MainWindow(QMainWindow): self.world = world self.setupCenterWidget() # 创建中间部分Panda3D self.setupMenus() # 创建菜单栏 - self.setupDockWindows() - self.setupToolbar() + self.setupDockWindows() # 可停靠窗口 + self.setupToolbar() # 工具栏 self.connectEvents() # 移动窗口到屏幕中央 @@ -126,12 +126,12 @@ class MainWindow(QMainWindow): self.leftDock = QDockWidget("层级", self) self.leftDock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) self.treeWidget = CustomTreeWidget(self.world) - self.treeWidget.setHeaderLabel("场景") self.world.setTreeWidget(self.treeWidget) # 设置树形控件引用 self.leftDock.setWidget(self.treeWidget) - self.leftDock.setMinimumWidth(300) - self.addDockWidget(Qt.LeftDockWidgetArea, self.leftDock) - + # self.leftDock.setMinimumWidth(300) + self.addDockWidget(Qt.DockWidgetArea.LeftDockWidgetArea, self.leftDock) + + # 创建右侧停靠窗口(属性窗口) self.rightDock = QDockWidget("属性", self) self.rightDock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) @@ -139,13 +139,15 @@ class MainWindow(QMainWindow): # 创建属性面板的主容器和布局 self.propertyContainer = QWidget() self.propertyContainer.setObjectName("PropertyContainer") - self.propertyLayout = QFormLayout(self.propertyContainer) - + self.propertyLayout = QVBoxLayout(self.propertyContainer) + # self.propertyLayout = QFormLayout(self.propertyContainer) + # 添加初始提示信息 tipLabel = QLabel("") tipLabel.setStyleSheet("color: gray;") # 使用灰色字体 - self.propertyLayout.addRow(tipLabel) - + # self.propertyLayout.addRow(tipLabel) + self.propertyLayout.addWidget(tipLabel) + # 创建滚动区域并设置属性 self.scrollArea = QScrollArea() self.scrollArea.setWidgetResizable(True) @@ -158,7 +160,8 @@ class MainWindow(QMainWindow): # 设置属性面板到世界对象 self.world.setPropertyLayout(self.propertyLayout) - + + # 创建脚本管理停靠窗口 self.scriptDock = QDockWidget("脚本管理", self) self.scriptDock.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea) @@ -167,7 +170,8 @@ class MainWindow(QMainWindow): # 将右侧停靠窗口设为标签形式 self.tabifyDockWidget(self.rightDock, self.scriptDock) - + + # 创建底部停靠窗口(资源窗口) self.bottomDock = QDockWidget("资源", self) self.bottomDock.setAllowedAreas(Qt.BottomDockWidgetArea) diff --git a/ui/property_panel.py b/ui/property_panel.py index 27bd8632..9aa9d327 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -3,12 +3,15 @@ from types import new_class from typing import Hashable from PyQt5.QtWidgets import (QLabel, QLineEdit, QDoubleSpinBox, QPushButton, - QTreeWidget, QTreeWidgetItem, QMenu, QCheckBox, QComboBox, QHBoxLayout, QWidget) + QTreeWidget, QTreeWidgetItem, QMenu, QCheckBox, QComboBox, QHBoxLayout, QWidget, QGroupBox, + QVBoxLayout, QGridLayout) from PyQt5.QtCore import Qt from deploy_libs.unicodedata import normalize from direct.actor.Actor import Actor from panda3d.core import Vec3, Vec4, transpose, TransparencyAttrib +from scene.util import CrossPlatformPathHandler + class PropertyPanelManager: """属性面板管理器""" @@ -61,13 +64,23 @@ class PropertyPanelManager: if itemText == "场景": tipLabel = QLabel("") tipLabel.setStyleSheet("color: gray;") - self._propertyLayout.addRow(tipLabel) + # self._propertyLayout.addRow(tipLabel) + self._propertyLayout.addWidget(tipLabel) return # 创建通用属性 - nameLabel = QLabel("名称:") - nameEdit = QLineEdit(itemText) - self._propertyLayout.addRow(nameLabel, nameEdit) + self.name_group = QGroupBox("物体名称") + name_layout = QHBoxLayout() + self.active_check = QCheckBox() + self.active_check.setChecked(True) # 默认激活 + self.name_input = QLineEdit(itemText) + name_layout.addWidget(self.active_check) + name_layout.addWidget(self.name_input) + self.name_group.setLayout(name_layout) + self._propertyLayout.addWidget(self.name_group) + # nameLabel = QLabel("名称:") + # nameEdit = QLineEdit(itemText) + # self._propertyLayout.addRow(nameLabel, nameEdit) # 获取节点对象 model = item.data(0, Qt.UserRole) @@ -75,13 +88,18 @@ class PropertyPanelManager: # 检查是否是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"): + pass + elif model and hasattr(model, 'getTag') and model.getTag("light_type"): self.updateLightPropertyPanel(model) + pass # 如果找到模型,显示其属性 elif model: self._updateModelPropertyPanel(model) + pass # 显示脚本属性 - # self._updateScriptPropertyPanel(model) + # self._updateScriptPropertyPanel(model) + + self._propertyLayout.addStretch() # 强制更新布局 if self._propertyLayout: @@ -95,96 +113,154 @@ class PropertyPanelManager: # 获取父节点 parent = model.getParent() - # 位置属性(相对于父节点) + # 变换属性部分 (Transform) + self.transform_group = QGroupBox("变换 Transform") + transform_layout = QGridLayout() + + # 获取当前值 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) + # 位置 (Position) + transform_layout.addWidget(QLabel("相对位置"), 0, 0) + self.pos_x = QDoubleSpinBox() + self.pos_y = QDoubleSpinBox() + self.pos_z = QDoubleSpinBox() - worldZPos = QDoubleSpinBox() - worldZPos.setRange(-1000, 1000) - worldZPos.setValue(worldPos.getZ()) - worldZPos.setReadOnly(True) - self._propertyLayout.addRow("世界位置 Z:", worldZPos) + # 设置位置控件属性 + for pos_widget in [self.pos_x, self.pos_y, self.pos_z]: + pos_widget.setRange(-1000, 1000) - # 旋转属性 - hRot = QDoubleSpinBox() - hRot.setRange(-180, 180) - hRot.setValue(model.getH()) - hRot.valueChanged.connect(lambda v: model.setH(v)) - self._propertyLayout.addRow("旋转 H:", hRot) + self.pos_x.setValue(relativePos.getX()) + self.pos_y.setValue(relativePos.getY()) + self.pos_z.setValue(relativePos.getZ()) - pRot = QDoubleSpinBox() - pRot.setRange(-180, 180) - pRot.setValue(model.getP()) - pRot.valueChanged.connect(lambda v: model.setP(v)) - self._propertyLayout.addRow("旋转 P:", pRot) + # 连接位置变化事件 + self.pos_x.valueChanged.connect(lambda v: model.setX(parent, v) if parent else model.setX(v)) + self.pos_y.valueChanged.connect(lambda v: model.setY(parent, v) if parent else model.setY(v)) + self.pos_z.valueChanged.connect(lambda v: model.setZ(parent, v) if parent else model.setZ(v)) - rRot = QDoubleSpinBox() - rRot.setRange(-180, 180) - rRot.setValue(model.getR()) - rRot.valueChanged.connect(lambda v: model.setR(v)) - self._propertyLayout.addRow("旋转 R:", rRot) + # 创建并设置 X, Y, Z 标签居中 + x_label1 = QLabel("X") + y_label1 = QLabel("Y") + z_label1 = QLabel("Z") + x_label1.setAlignment(Qt.AlignCenter) + y_label1.setAlignment(Qt.AlignCenter) + z_label1.setAlignment(Qt.AlignCenter) - # 缩放属性 - 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) + transform_layout.addWidget(x_label1, 0, 1) + transform_layout.addWidget(y_label1, 0, 2) + transform_layout.addWidget(z_label1, 0, 3) + transform_layout.addWidget(self.pos_x, 1, 1) + transform_layout.addWidget(self.pos_y, 1, 2) + transform_layout.addWidget(self.pos_z, 1, 3) - 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) + # 世界位置 (只读) + transform_layout.addWidget(QLabel("世界位置"), 2, 0) + self.world_pos_x = QDoubleSpinBox() + self.world_pos_y = QDoubleSpinBox() + self.world_pos_z = QDoubleSpinBox() - 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) + # 设置世界位置控件属性 + for world_pos_widget in [self.world_pos_x, self.world_pos_y, self.world_pos_z]: + world_pos_widget.setRange(-1000, 1000) + world_pos_widget.setReadOnly(True) - self._addAnimationPanel(model,model) + self.world_pos_x.setValue(worldPos.getX()) + self.world_pos_y.setValue(worldPos.getY()) + self.world_pos_z.setValue(worldPos.getZ()) + + transform_layout.addWidget(self.world_pos_x, 3, 1) + transform_layout.addWidget(self.world_pos_y, 3, 2) + transform_layout.addWidget(self.world_pos_z, 3, 3) + + # 旋转 (Rotation) + transform_layout.addWidget(QLabel("旋转"), 4, 0) + self.rot_h = QDoubleSpinBox() + self.rot_p = QDoubleSpinBox() + self.rot_r = QDoubleSpinBox() + + # 设置旋转控件属性 + for rot_widget in [self.rot_h, self.rot_p, self.rot_r]: + rot_widget.setRange(-180, 180) + + self.rot_h.setValue(model.getH()) + self.rot_p.setValue(model.getP()) + self.rot_r.setValue(model.getR()) + + # 连接旋转变化事件 + self.rot_h.valueChanged.connect(lambda v: model.setH(v)) + self.rot_p.valueChanged.connect(lambda v: model.setP(v)) + self.rot_r.valueChanged.connect(lambda v: model.setR(v)) + + # 创建并设置 H, P, R 标签居中 + h_label = QLabel("H") + p_label = QLabel("P") + r_label = QLabel("R") + h_label.setAlignment(Qt.AlignCenter) + p_label.setAlignment(Qt.AlignCenter) + r_label.setAlignment(Qt.AlignCenter) + + transform_layout.addWidget(h_label, 4, 1) + transform_layout.addWidget(p_label, 4, 2) + transform_layout.addWidget(r_label, 4, 3) + transform_layout.addWidget(self.rot_h, 5, 1) + transform_layout.addWidget(self.rot_p, 5, 2) + transform_layout.addWidget(self.rot_r, 5, 3) + + # 缩放 (Scale) + transform_layout.addWidget(QLabel("缩放"), 6, 0) + self.scale_x = QDoubleSpinBox() + self.scale_y = QDoubleSpinBox() + self.scale_z = QDoubleSpinBox() + + # 设置缩放控件属性 + for scale_widget in [self.scale_x, self.scale_y, self.scale_z]: + scale_widget.setRange(0.01, 100) + scale_widget.setSingleStep(0.1) + + self.scale_x.setValue(model.getScale().getX()) + self.scale_y.setValue(model.getScale().getY()) + self.scale_z.setValue(model.getScale().getZ()) + + # 连接缩放变化事件 + self.scale_x.valueChanged.connect(lambda v: model.setScale(v, model.getScale().getY(), model.getScale().getZ())) + self.scale_y.valueChanged.connect(lambda v: model.setScale(model.getScale().getX(), v, model.getScale().getZ())) + self.scale_z.valueChanged.connect(lambda v: model.setScale(model.getScale().getX(), model.getScale().getY(), v)) + + # 创建并设置 X, Y, Z 标签居中 + x_label3 = QLabel("X") + y_label3 = QLabel("Y") + z_label3 = QLabel("Z") + x_label3.setAlignment(Qt.AlignCenter) + y_label3.setAlignment(Qt.AlignCenter) + z_label3.setAlignment(Qt.AlignCenter) + + transform_layout.addWidget(x_label3, 6, 1) + transform_layout.addWidget(y_label3, 6, 2) + transform_layout.addWidget(z_label3, 6, 3) + transform_layout.addWidget(self.scale_x, 7, 1) + transform_layout.addWidget(self.scale_y, 7, 2) + transform_layout.addWidget(self.scale_z, 7, 3) + + self.transform_group.setLayout(transform_layout) + self._propertyLayout.addWidget(self.transform_group) + + # 动画和太阳方位角面板 + self._addAnimationPanel(model, model) self._addSunAzimuthPanel() - - 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) + # material_group = QGroupBox("材质属性") + # material_layout = QVBoxLayout() + + # material_title = QLabel("材质属性") + # material_title.setStyleSheet("color: #FF6B6B;font-weight:bold;font-size:14px;margin-top:10px;") + # material_layout.addWidget(material_title) + + # material_group.setLayout(material_layout) + # self._propertyLayout.addWidget(material_group) def updateGUIPropertyPanel(self, gui_element): @@ -192,15 +268,19 @@ class PropertyPanelManager: gui_type = gui_element.getTag("gui_type") gui_text = gui_element.getTag("gui_text") + # GUI基本信息组 + gui_info_group = QGroupBox("GUI信息") + gui_info_layout = QGridLayout() + # GUI类型显示 - typeLabel = QLabel("GUI类型:") + gui_info_layout.addWidget(QLabel("GUI类型:"), 0, 0) typeValue = QLabel(gui_type) - typeValue.setStyleSheet("color: #00AAFF; font-weight: bold;") - self._propertyLayout.addRow(typeLabel, typeValue) + # typeValue.setStyleSheet("color: #00AAFF; font-weight: bold;") + gui_info_layout.addWidget(typeValue, 0, 1) # 文本属性(如果适用) if gui_type in ["button", "label", "entry", "3d_text", "virtual_screen"]: - textLabel = QLabel("文本:") + gui_info_layout.addWidget(QLabel("文本:"), 1, 0) textEdit = QLineEdit(gui_text or "") # 创建一个更新函数来处理文本变化 @@ -211,10 +291,21 @@ class PropertyPanelManager: self.world.scene_manager.updateSceneTree() textEdit.textChanged.connect(updateText) - self._propertyLayout.addRow(textLabel, textEdit) + gui_info_layout.addWidget(textEdit, 1, 1) - # 位置属性 + gui_info_group.setLayout(gui_info_layout) + self._propertyLayout.addWidget(gui_info_group) + + # 变换属性组(合并位置和变换) if hasattr(gui_element, 'getPos'): + # 根据GUI类型设置组名 + if gui_type in ["button", "label", "entry"]: + transform_group = QGroupBox("变换 Rect Transform") + else: + transform_group = QGroupBox("变换 Transform") + + transform_layout = QGridLayout() + pos = gui_element.getPos() # 根据GUI类型决定位置编辑方式 @@ -223,63 +314,109 @@ class PropertyPanelManager: logical_x = pos.getX() / 0.1 # 反向转换为逻辑坐标 logical_z = pos.getZ() / 0.1 + # 屏幕位置控件 + transform_layout.addWidget(QLabel("屏幕位置"), 0, 0) + + # X, Z 标签居中 + x_label = QLabel("X") + z_label = QLabel("Z") + x_label.setAlignment(Qt.AlignCenter) + z_label.setAlignment(Qt.AlignCenter) + + transform_layout.addWidget(x_label, 0, 1) + transform_layout.addWidget(z_label, 0, 2) + 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) + transform_layout.addWidget(xPos, 1, 1) 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) + transform_layout.addWidget(zPos, 1, 2) # 显示实际屏幕坐标(只读) + transform_layout.addWidget(QLabel("实际坐标"), 2, 0) + 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) + + transform_layout.addWidget(actualXLabel, 3, 1) + transform_layout.addWidget(actualZLabel, 3, 2) else: # 3D GUI组件使用世界坐标 + transform_layout.addWidget(QLabel("世界位置"), 0, 0) + + # X, Y, Z 标签居中 + x_label = QLabel("X") + y_label = QLabel("Y") + z_label = QLabel("Z") + x_label.setAlignment(Qt.AlignCenter) + y_label.setAlignment(Qt.AlignCenter) + z_label.setAlignment(Qt.AlignCenter) + + transform_layout.addWidget(x_label, 0, 1) + transform_layout.addWidget(y_label, 0, 2) + transform_layout.addWidget(z_label, 0, 3) + 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) + xPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUIElement(gui_element, "position", + [v, pos.getY(), pos.getZ()])) + transform_layout.addWidget(xPos, 1, 1) 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) + yPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUIElement(gui_element, "position", + [pos.getX(), v, pos.getZ()])) + transform_layout.addWidget(yPos, 1, 2) 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) + zPos.valueChanged.connect(lambda v: self.world.gui_manager.editGUIElement(gui_element, "position", + [pos.getX(), pos.getY(), v])) + transform_layout.addWidget(zPos, 1, 3) - # 缩放属性 - if hasattr(gui_element, 'getScale'): - scale = gui_element.getScale() + # 缩放属性 + 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) + row_offset = 4 if gui_type in ["button", "label", "entry"] else 2 - # 颜色属性(针对2D GUI) + transform_layout.addWidget(QLabel("缩放"), row_offset, 0) + + 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)) + transform_layout.addWidget(scaleSpinBox, row_offset, 1) + + transform_group.setLayout(transform_layout) + self._propertyLayout.addWidget(transform_group) + + # 外观属性组 if gui_type in ["button", "label"]: + appearance_group = QGroupBox("外观属性") + appearance_layout = QGridLayout() + + appearance_layout.addWidget(QLabel("背景颜色:"), 0, 0) + colorButton = QPushButton("选择颜色") colorButton.clicked.connect(lambda: self.world.gui_manager.selectGUIColor(gui_element)) - self._propertyLayout.addRow("背景颜色:", colorButton) + appearance_layout.addWidget(colorButton, 0, 1) + + appearance_group.setLayout(appearance_layout) + self._propertyLayout.addWidget(appearance_group) def _updateScriptPropertyPanel(self, game_object): """更新脚本属性面板""" @@ -298,7 +435,7 @@ class PropertyPanelManager: enabled = script_component.enabled # 脚本名称和状态 - scriptLabel = QLabel(f"脚本 {i+1}:") + scriptLabel = QLabel(f"脚本 {i + 1}:") scriptInfo = QLabel(f"{script_name}") scriptInfo.setStyleSheet("color: green; font-weight: bold;" if enabled else "color: gray;") self._propertyLayout.addRow(scriptLabel, scriptInfo) @@ -339,197 +476,171 @@ class PropertyPanelManager: self.updatePropertyPanel(tree_widget.currentItem()) def updateLightPropertyPanel(self, model): - """更新模型属性面板""" - + """更新光源属性面板""" light_object = model.getPythonTag("rp_light_object") if light_object: + # 变换属性组 + transform_group = QGroupBox("变换 Transform") + transform_layout = QGridLayout() + + # 位置属性 current_pos = light_object.pos + transform_layout.addWidget(QLabel("位置"), 0, 0) + + # X, Y, Z 标签居中 + x_label = QLabel("X") + y_label = QLabel("Y") + z_label = QLabel("Z") + x_label.setAlignment(Qt.AlignCenter) + y_label.setAlignment(Qt.AlignCenter) + z_label.setAlignment(Qt.AlignCenter) + + transform_layout.addWidget(x_label, 0, 1) + transform_layout.addWidget(y_label, 0, 2) + transform_layout.addWidget(z_label, 0, 3) 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) + transform_layout.addWidget(xPos, 1, 1) yPos = QDoubleSpinBox() - yPos.setRange(-1000,1000) + yPos.setRange(-1000, 1000) yPos.setValue(current_pos.getY()) - yPos.valueChanged.connect(lambda v:self._updateLightPosition(light_object,model,'y',v)) - self._propertyLayout.addRow("相对位置 Y:",yPos) + yPos.valueChanged.connect(lambda v: self._updateLightPosition(light_object, model, 'y', v)) + transform_layout.addWidget(yPos, 1, 2) zPos = QDoubleSpinBox() - zPos.setRange(-1000,1000) + 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) + zPos.valueChanged.connect(lambda v: self._updateLightPosition(light_object, model, 'z', v)) + transform_layout.addWidget(zPos, 1, 3) - if hasattr(light_object,'direction'): + # 世界位置(只读) + worldPos = model.getPos(self.world.render) + transform_layout.addWidget(QLabel("世界位置"), 2, 0) + + worldXPos = QDoubleSpinBox() + worldXPos.setRange(-1000, 1000) + worldXPos.setValue(worldPos.getX()) + worldXPos.setReadOnly(True) + transform_layout.addWidget(worldXPos, 3, 1) + + worldYPos = QDoubleSpinBox() + worldYPos.setRange(-1000, 1000) + worldYPos.setValue(worldPos.getY()) + worldYPos.setReadOnly(True) + transform_layout.addWidget(worldYPos, 3, 2) + + worldZPos = QDoubleSpinBox() + worldZPos.setRange(-1000, 1000) + worldZPos.setValue(worldPos.getZ()) + worldZPos.setReadOnly(True) + transform_layout.addWidget(worldZPos, 3, 3) + + # 旋转属性(仅聚光灯) + if hasattr(light_object, 'direction'): current_hpr = model.getHpr() + transform_layout.addWidget(QLabel("旋转"), 4, 0) + + # H, P, R 标签居中 + h_label = QLabel("H") + p_label = QLabel("P") + r_label = QLabel("R") + h_label.setAlignment(Qt.AlignCenter) + p_label.setAlignment(Qt.AlignCenter) + r_label.setAlignment(Qt.AlignCenter) + + transform_layout.addWidget(h_label, 4, 1) + transform_layout.addWidget(p_label, 4, 2) + transform_layout.addWidget(r_label, 4, 3) hRot = QDoubleSpinBox() - hRot.setRange(-180,180) + 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) + hRot.valueChanged.connect(lambda v: self._updateLightRotation(light_object, model, 'h', v)) + transform_layout.addWidget(hRot, 5, 1) pRot = QDoubleSpinBox() - pRot.setRange(-180,180) + 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) + pRot.valueChanged.connect(lambda v: self._updateLightRotation(light_object, model, 'p', v)) + transform_layout.addWidget(pRot, 5, 2) rRot = QDoubleSpinBox() - rRot.setRange(-180,180) + 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) + rRot.valueChanged.connect(lambda v: self._updateLightRotation(light_object, model, 'r', v)) + transform_layout.addWidget(rRot, 5, 3) - 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) + # 缩放属性 + current_scale = model.getScale() + scale_row = 6 if hasattr(light_object, 'direction') else 4 + transform_layout.addWidget(QLabel("缩放"), scale_row, 0) - 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) + 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)) + transform_layout.addWidget(xScaleSpinBox, scale_row + 1, 1) - 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) + 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)) + transform_layout.addWidget(yScaleSpinBox, scale_row + 1, 2) - # shadowCheckBox = QCheckBox() - # shadowCheckBox.setChecked(light_object.casts_shadows) - # shadowCheckBox.stateChanged.connect(lambda state:self._updateLightCastsShadows(light_object,state==2)) - # self._propertyLayout.addRow("投射阴影:",shadowCheckBox) + 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)) + transform_layout.addWidget(zScaleSpinBox, scale_row + 1, 3) - current_scale = model.getScale() + transform_group.setLayout(transform_layout) + self._propertyLayout.addWidget(transform_group) - 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) + # 光源属性组 + light_group = QGroupBox("光源属性") + light_layout = QGridLayout() - 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) + # 能量 + light_layout.addWidget(QLabel("能量:"), 0, 0) + energySpinBox = QDoubleSpinBox() + energySpinBox.setRange(0, 10000) + energySpinBox.setValue(light_object.energy) + energySpinBox.valueChanged.connect(lambda v: self._updateLightEnergy(light_object, v)) + light_layout.addWidget(energySpinBox, 0, 1) - 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) + # 半径 + light_layout.addWidget(QLabel("半径:"), 1, 0) + radiusSpinBox = QDoubleSpinBox() + radiusSpinBox.setRange(1, 2000) + radiusSpinBox.setValue(light_object.radius) + radiusSpinBox.valueChanged.connect(lambda v: self._updateLightRadius(light_object, v)) + light_layout.addWidget(radiusSpinBox, 1, 1) + # 视野角度(仅聚光灯) + if hasattr(light_object, 'fov'): + light_layout.addWidget(QLabel("视野角度:"), 2, 0) + fovSpinBox = QDoubleSpinBox() + fovSpinBox.setRange(1, 180) + fovSpinBox.setValue(light_object.fov) + fovSpinBox.valueChanged.connect(lambda v: self._updateLightFOV(light_object, v)) + light_layout.addWidget(fovSpinBox, 2, 1) + light_group.setLayout(light_layout) + self._propertyLayout.addWidget(light_group) - - - # 获取父节点 - - #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): + def _updateLightPosition(self, light_object, node_path, axis, value): current_pos = light_object.pos - if axis=='x': - new_pos = Vec3(value,current_pos.getY(),current_pos.getZ()) + if axis == 'x': + new_pos = Vec3(value, current_pos.getY(), current_pos.getZ()) elif axis == 'y': new_pos = Vec3(current_pos.getX(), value, current_pos.getZ()) else: # z @@ -540,73 +651,73 @@ class PropertyPanelManager: # 同步更新场景节点位置(用于显示) node_path.setPos(new_pos) - def _updateLightRotation(self,light_object,node_path,axis,value): + def _updateLightRotation(self, light_object, node_path, axis, value): """更新光源旋转""" from panda3d.core import Vec3 current_hpr = node_path.getHpr() - if axis=='h': - new_hpr = Vec3(value,current_hpr.getY(),current_hpr.getZ()) - elif axis=='p': - new_hpr = Vec3(current_hpr.getX(),value,current_hpr.getZ()) + if axis == 'h': + new_hpr = Vec3(value, current_hpr.getY(), current_hpr.getZ()) + elif axis == 'p': + new_hpr = Vec3(current_hpr.getX(), value, current_hpr.getZ()) else: - new_hpr = Vec3(current_hpr.getX(),current_hpr.getY(),value) + new_hpr = Vec3(current_hpr.getX(), current_hpr.getY(), value) node_path.setHpr(new_hpr) - if hasattr(light_object,'direction'): + if hasattr(light_object, 'direction'): direction_mat = node_path.getMat() - new_direction = direction_mat.xformVec(Vec3(0,1,0)) + new_direction = direction_mat.xformVec(Vec3(0, 1, 0)) light_object.direction = new_direction print(f"光源旋转已更新:{axis}={value}") - def _updateLightEnergy(self,light_object,value): + def _updateLightEnergy(self, light_object, value): """更新光源强度""" light_object.energy = value - def _updateLightRadius(self,light_object,value): + def _updateLightRadius(self, light_object, value): """更新光源半径""" light_object.radius = value - def _updateLightFOV(self,light_Object,value): + def _updateLightFOV(self, light_Object, value): """更新聚光灯视野角度""" - if hasattr(light_Object,'fov'): + if hasattr(light_Object, 'fov'): light_Object.fov = value - def _updateLightTemperature(self,light_object,value): + def _updateLightTemperature(self, light_object, value): """更新光源色温""" light_object.set_color_from_temperature(value) - #保存色温值以便下次显示 - light_object._temperature=value + # 保存色温值以便下次显示 + light_object._temperature = value - def _updateLightInnerRadius(self,light_object,value): + def _updateLightInnerRadius(self, light_object, value): """更新点光源内半径""" - if hasattr(light_object,'inner_radius'): - light_object.inner_radius=value + if hasattr(light_object, 'inner_radius'): + light_object.inner_radius = value - def _updateLightShaowResolution(self,light_object,value): + def _updateLightShaowResolution(self, light_object, value): """更新阴影分辨率""" light_object.shadow_map_resolution = value - def _updateLightNearPlane(self,light_object,value): + def _updateLightNearPlane(self, light_object, value): """更新近平面距离""" light_object.near_plane = value - def _updateLightCastsShadows(self,light_object,casts_shadows): + def _updateLightCastsShadows(self, light_object, casts_shadows): """更新光源是否投射阴影""" light_object.casts_shadows = casts_shadows - def _updateLightScale(self,node_path,axis,value): + def _updateLightScale(self, node_path, axis, value): """更新光源节点缩放""" current_scale = node_path.getScale() - if axis=='x': - new_scale = Vec3(value,current_scale.getY(),current_scale.getZ()) - elif axis=='y': - new_scale = Vec3(current_scale.getX(),value,current_scale.getZ()) + if axis == 'x': + new_scale = Vec3(value, current_scale.getY(), current_scale.getZ()) + elif axis == 'y': + new_scale = Vec3(current_scale.getX(), value, current_scale.getZ()) else: - new_scale = Vec3(current_scale.getX(),current_scale.getY(),value) + new_scale = Vec3(current_scale.getX(), current_scale.getY(), value) node_path.setScale(new_scale) @@ -634,44 +745,48 @@ class PropertyPanelManager: return unique_names - - def _updateModelMaterialPanel(self,model): + def _updateModelMaterialPanel(self, model): """模型材质属性""" if model.is_empty(): print("警告: 无法在空的 NodePath 上查找材质") + no_material_group = QGroupBox("材质信息") + no_material_layout = QGridLayout() no_material_label = QLabel("无材质") no_material_label.setStyleSheet("color: gray;font-style:italic;") - self._propertyLayout.addRow("材质:", no_material_label) + no_material_layout.addWidget(no_material_label, 0, 0) + no_material_group.setLayout(no_material_layout) + self._propertyLayout.addWidget(no_material_group) return 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) + no_material_group = QGroupBox("材质信息") + no_material_layout = QGridLayout() + no_material_label = QLabel("无材质") + no_material_label.setStyleSheet("color: gray;font-style:italic;") + no_material_layout.addWidget(no_material_label, 0, 0) + no_material_group.setLayout(no_material_layout) + self._propertyLayout.addWidget(no_material_group) return - model_name=model.getName() or "未命名模型" - + model_name = model.getName() or "未命名模型" name_counter = {} # 创建材质到几何节点的映射字典 self._material_geom_mapping = {} self._material_display_names = {} - for i,material in enumerate(materials): + for i, material in enumerate(materials): # 查找使用该材质的几何节点,使用几何节点名称作为材质标题 geom_node = self._findSpecificGeomNodeWithMaterial(model, material) if geom_node: - # 使用几何节点名称作为材质标题 geom_node_name = geom_node.getName() unique_name = f"{geom_node_name}({model_name})" print(f"材质 {i}: 使用几何节点名称 '{geom_node_name}'") else: - # 回退到原有的材质名称逻辑 - material_name = material.get_name() if hasattr(material,'get_name') and material.get_name() else f"材质{i + 1}" + material_name = material.get_name() if hasattr(material, 'get_name') and material.get_name() else f"材质{i + 1}" unique_name = f"{material_name}({model_name})" print(f"材质 {i}: 未找到几何节点,使用材质名称 '{material_name}'") @@ -684,207 +799,198 @@ class PropertyPanelManager: display_name = unique_name # 存储材质和对应的几何节点信息到映射字典中 - material_id = id(material) # 使用材质对象的内存地址作为唯一标识 + material_id = id(material) self._material_geom_mapping[material_id] = geom_node self._material_display_names[material_id] = display_name - material_title = QLabel(display_name) - material_title.setStyleSheet("color:#00AAFF;font-weight:bold;font-size:12px") - self._propertyLayout.addRow(material_title) + # 材质信息组 + # material_group = QGroupBox(display_name) + material_group = QGroupBox("材质属性") + material_layout = QGridLayout() - # 检查材质类型并显示状态,但允许所有材质进行编辑 + material_layout.addWidget(QLabel("名称:"), 0, 0) + name_label = QLabel(display_name) + # name_label.setStyleSheet("color: #FF6B6B; font-weight: bold;") + material_layout.addWidget(name_label, 0, 1, 1, 3) + + # 检查材质类型并显示状态 material_status = self._getMaterialStatus(material) if material_status != "标准PBR材质": + material_layout.addWidget(QLabel("状态:"), 1, 0) status_label = QLabel(material_status) - status_label.setStyleSheet("color:#FFA500;font-style:italic;font-size:10px;") - self._propertyLayout.addRow("状态:", status_label) + # status_label.setStyleSheet("color:#FFA500;font-style:italic;font-size:10px;") + material_layout.addWidget(status_label, 1, 1, 1, 2) - # 移除了continue语句,让所有材质都可以编辑 - - # 基础颜色编辑(智能检查和创建) + # 基础颜色编辑 base_color = self._getOrCreateMaterialBaseColor(material) - if base_color is not None: print(f"材质基础颜色: {base_color}") - #R分量 + # 基础颜色标题 + color_row = 2 if material_status != "标准PBR材质" else 0 + material_layout.addWidget(QLabel("基础颜色"), color_row, 0) + + # R, G, B 标签 + r_label = QLabel("R") + g_label = QLabel("G") + b_label = QLabel("B") + r_label.setAlignment(Qt.AlignCenter) + g_label.setAlignment(Qt.AlignCenter) + b_label.setAlignment(Qt.AlignCenter) + + material_layout.addWidget(r_label, color_row, 1) + material_layout.addWidget(g_label, color_row, 2) + material_layout.addWidget(b_label, color_row, 3) + + # RGB 数值框 r_spinbox = QDoubleSpinBox() - r_spinbox.setRange(0.0,1.0) + 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) + r_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialBaseColor(mat, 'r', v)) + material_layout.addWidget(r_spinbox, color_row + 1, 1) - #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) + material_layout.addWidget(g_spinbox, color_row + 1, 2) - # 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) + material_layout.addWidget(b_spinbox, color_row + 1, 3) else: - # 如果无法获取或创建基础颜色,显示提示 no_base_color_label = QLabel("无法获取材质基础颜色") no_base_color_label.setStyleSheet("color:#888;font-style:italic;font-size:10px;") - self._propertyLayout.addRow("基础颜色:", no_base_color_label) + material_layout.addWidget(QLabel("基础颜色:"), 1, 0) + material_layout.addWidget(no_base_color_label, 1, 1, 1, 3) - # 粗糙度(安全检查) + # 材质属性行 + current_row = color_row + 2 if base_color is not None else 2 + + # 粗糙度 if hasattr(material, 'roughness') and material.roughness is not None: try: roughness_value = float(material.roughness) + material_layout.addWidget(QLabel("粗糙度:"), current_row, 0) roughness_spinbox = QDoubleSpinBox() roughness_spinbox.setRange(0.0, 1.0) roughness_spinbox.setSingleStep(0.01) roughness_spinbox.setValue(roughness_value) roughness_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialRoughness(mat, v)) - self._propertyLayout.addRow("粗糙度:", roughness_spinbox) + material_layout.addWidget(roughness_spinbox, current_row, 1) except (TypeError, ValueError) as e: print(f"粗糙度值无效: {material.roughness}, 错误: {e}") - no_roughness_label = QLabel("粗糙度值无效,无法编辑") + material_layout.addWidget(QLabel("粗糙度:"), current_row, 0) + no_roughness_label = QLabel("粗糙度值无效") no_roughness_label.setStyleSheet("color:#888;font-style:italic;font-size:10px;") - self._propertyLayout.addRow("粗糙度:", no_roughness_label) + material_layout.addWidget(no_roughness_label, current_row, 1) else: - no_roughness_label = QLabel("此材质不支持粗糙度编辑") + material_layout.addWidget(QLabel("粗糙度:"), current_row, 0) + no_roughness_label = QLabel("不支持") no_roughness_label.setStyleSheet("color:#888;font-style:italic;font-size:10px;") - self._propertyLayout.addRow("粗糙度:", no_roughness_label) + material_layout.addWidget(no_roughness_label, current_row, 1) + current_row += 1 - - # 金属性(安全检查) + # 金属性 if hasattr(material, 'metallic') and material.metallic is not None: try: metallic_value = float(material.metallic) + material_layout.addWidget(QLabel("金属性:"), current_row, 0) metallic_spinbox = QDoubleSpinBox() metallic_spinbox.setRange(0.0, 1.0) metallic_spinbox.setSingleStep(0.01) metallic_spinbox.setValue(metallic_value) metallic_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialMetallic(mat, v)) - self._propertyLayout.addRow("金属性:", metallic_spinbox) + material_layout.addWidget(metallic_spinbox, current_row, 1) except (TypeError, ValueError) as e: print(f"金属性值无效: {material.metallic}, 错误: {e}") - no_metallic_label = QLabel("金属性值无效,无法编辑") + material_layout.addWidget(QLabel("金属性:"), current_row, 0) + no_metallic_label = QLabel("值无效") no_metallic_label.setStyleSheet("color:#888;font-style:italic;font-size:10px;") - self._propertyLayout.addRow("金属性:", no_metallic_label) + material_layout.addWidget(no_metallic_label, current_row, 1) else: - no_metallic_label = QLabel("此材质不支持金属性编辑") + material_layout.addWidget(QLabel("金属性:"), current_row, 0) + no_metallic_label = QLabel("不支持") no_metallic_label.setStyleSheet("color:#888;font-style:italic;font-size:10px;") - self._propertyLayout.addRow("金属性:", no_metallic_label) + material_layout.addWidget(no_metallic_label, current_row, 1) - # 折射率(安全检查) + current_row += 1 + + # 折射率 if hasattr(material, 'refractive_index') and material.refractive_index is not None: try: ior_value = float(material.refractive_index) + material_layout.addWidget(QLabel("折射率:"), current_row, 0) ior_spinbox = QDoubleSpinBox() ior_spinbox.setRange(1.0, 3.0) ior_spinbox.setSingleStep(0.01) ior_spinbox.setValue(ior_value) ior_spinbox.valueChanged.connect(lambda v, mat=material: self._updateMaterialIOR(mat, v)) - self._propertyLayout.addRow("折射率:", ior_spinbox) + material_layout.addWidget(ior_spinbox, current_row, 1) except (TypeError, ValueError) as e: print(f"折射率值无效: {material.refractive_index}, 错误: {e}") - no_ior_label = QLabel("折射率值无效,无法编辑") + material_layout.addWidget(QLabel("折射率:"), current_row, 0) + no_ior_label = QLabel("折射率值无效") no_ior_label.setStyleSheet("color:#888;font-style:italic;font-size:10px;") - self._propertyLayout.addRow("折射率:", no_ior_label) + material_layout.addWidget(no_ior_label, current_row, 1) else: + material_layout.addWidget(QLabel("折射率:"), current_row, 0) no_ior_label = QLabel("此材质不支持折射率编辑") no_ior_label.setStyleSheet("color:#888;font-style:italic;font-size:10px;") - self._propertyLayout.addRow("折射率:", no_ior_label) + material_layout.addWidget(no_ior_label, current_row, 1) + current_row += 1 + + # 纹理贴图标题 texture_title = QLabel("纹理贴图") - texture_title.setStyleSheet("color: #4CAF50; font-weight:bold;font-size:11px;margin-top:5px;") - self._propertyLayout.addRow(texture_title) + texture_title.setStyleSheet("font-weight:bold;") + material_layout.addWidget(texture_title, current_row, 0, 1, 4) + current_row += 1 - #漫反射贴图 + # 纹理按钮 - 两列布局 diffuse_button = QPushButton("选择漫反射贴图") - diffuse_button.clicked.connect(lambda checked,title=unique_name:self._selectDiffuseTexture(title)) - self._propertyLayout.addRow("漫反射贴图:",diffuse_button) + diffuse_button.clicked.connect(lambda checked, title=unique_name: self._selectDiffuseTexture(title)) + material_layout.addWidget(diffuse_button, current_row, 0, 1, 2) - #法线贴图 normal_button = QPushButton("选择法线贴图") - normal_button.clicked.connect(lambda checked,mat=material:self._selectNormalTexture(mat)) - self._propertyLayout.addRow("法线贴图:",normal_button) + normal_button.clicked.connect(lambda checked, mat=material: self._selectNormalTexture(mat)) + material_layout.addWidget(normal_button, current_row, 2, 1, 2) + current_row += 1 - #粗糙度贴图 roughness_button = QPushButton("选择粗糙度贴图") - roughness_button.clicked.connect(lambda checked,mat=material:self._selectRoughnessTexture((mat))) - self._propertyLayout.addRow("粗糙度贴图:",roughness_button) + roughness_button.clicked.connect(lambda checked, mat=material: self._selectRoughnessTexture(mat)) + material_layout.addWidget(roughness_button, current_row, 0, 1, 2) - #金属性贴图 metallic_button = QPushButton("选择金属性贴图") - metallic_button.clicked.connect(lambda checked,mat=material:self._selectMetallicTexture(mat)) - self._propertyLayout.addRow("金属性贴图:",metallic_button) + metallic_button.clicked.connect(lambda checked, mat=material: self._selectMetallicTexture(mat)) + material_layout.addWidget(metallic_button, current_row, 2, 1, 2) - # #IOR贴图 - # ior_button = QPushButton("选择IOR贴图") - # ior_button.clicked.connect(lambda checked,mat = material:self._selectIORTexture(mat)) - # self._propertyLayout.addRow("IOR贴图",ior_button) + # 在纹理按钮后添加当前贴图信息显示 + current_row = self._displayCurrentTextures(material, material_layout, current_row) + current_row = self._addShadingModelPanel(material, material_layout, current_row) + current_row = self._addEmissionPanel(material, material_layout, current_row) + current_row = self._addMaterialPresetPanel(material, material_layout, current_row) + # # self._addColorSpacePanel(material) - # # 视差贴图 - # parallax_button = QPushButton("选择视差贴图") - # parallax_button.clicked.connect(lambda checked, mat=material: self._selectParallaxTexture(mat)) - # self._propertyLayout.addRow("视差贴图:", parallax_button) + material_group.setLayout(material_layout) + self._propertyLayout.addWidget(material_group) + + + # # 添加太阳方位角控制面板(只在第一个材质时添加,避免重复) + # # if i == 0: + # # self._addSunAzimuthPanel() # - # # 自发光贴图 - # emission_button = QPushButton("选择自发光贴图") - # emission_button.clicked.connect(lambda checked, mat=material: self._selectEmissionTexture(mat)) - # self._propertyLayout.addRow("自发光贴图:", emission_button) - # - # # 环境光遮蔽贴图 - # ao_button = QPushButton("选择AO贴图") - # ao_button.clicked.connect(lambda checked, mat=material: self._selectAOTexture(mat)) - # self._propertyLayout.addRow("AO贴图:", ao_button) - - # # 透明度贴图 - # alpha_button = QPushButton("选择透明度贴图") - # alpha_button.clicked.connect(lambda checked, mat=material: self._selectAlphaTexture(mat)) - # self._propertyLayout.addRow("透明度贴图:", alpha_button) - # - # # 细节贴图 - # detail_button = QPushButton("选择细节贴图") - # detail_button.clicked.connect(lambda checked, mat=material: self._selectDetailTexture(mat)) - # self._propertyLayout.addRow("细节贴图:", detail_button) - # - # # 光泽贴图 - # gloss_button = QPushButton("选择光泽贴图") - # gloss_button.clicked.connect(lambda checked, mat=material: self._selectGlossTexture(mat)) - # self._propertyLayout.addRow("光泽贴图:", gloss_button) - - - - # 显示当前贴图信息 - self._displayCurrentTextures(material) - - self._addShadingModelPanel(material) - self._addEmissionPanel(material) - self._addMaterialPresetPanel(material) - #self._addColorSpacePanel(material) - - # 添加太阳方位角控制面板(只在第一个材质时添加,避免重复) - # if i == 0: - # self._addSunAzimuthPanel() - - - # 分隔线 - if i < len(materials) - 1: - separator = QLabel("─" * 30) - separator.setStyleSheet("color: lightgray;") - self._propertyLayout.addRow(separator) + # # 分隔线 + # if i < len(materials) - 1: + # separator = QLabel("─" * 30) + # separator.setStyleSheet("color: lightgray;") + # self._propertyLayout.addRow(separator) def _updateMaterialBaseColor(self, material, component, value): """更新材质基础颜色(智能版本)""" @@ -907,7 +1013,7 @@ class PropertyPanelManager: # elif component == 'a': # Alpha分量处理 # self._updateMaterialTransparency(material, value) # return - #new_color = Vec4(current_color.x, current_color.y, current_color.z, value) + # new_color = Vec4(current_color.x, current_color.y, current_color.z, value) else: print(f"未知的颜色分量: {component}") return @@ -951,18 +1057,18 @@ class PropertyPanelManager: except Exception as e: print(f"更新材质基础颜色失败: {e}") - def _updateMaterialTransparency(self,material,alpha_value): + def _updateMaterialTransparency(self, material, alpha_value): try: from panda3d.core import Vec4 - if hasattr(material,'emission'): - material.emission = Vec4(3,0,0,0) + if hasattr(material, 'emission'): + material.emission = Vec4(3, 0, 0, 0) print("设置透明着色器模型") - if hasattr(material,'shading_model_param0'): + if hasattr(material, 'shading_model_param0'): material.shading_model_param0 = alpha_value print(f"设置透明度参数{alpha_value}") - if hasattr(material,'base_color'): + if hasattr(material, 'base_color'): current_color = material.base_color - material.base_color = Vec4(current_color.x,current_color.y,current_color.z,alpha_value) + material.base_color = Vec4(current_color.x, current_color.y, current_color.z, alpha_value) print(f"更新基础颜色透明度{alpha_value}") self._invalidateRenderState() print(f"材质透明度已更新:{alpha_value}") @@ -1036,8 +1142,6 @@ class PropertyPanelManager: # 现代渲染管线会自动检测纹理变化并更新 print("材质更改已应用,无需手动刷新渲染状态") - - def _getTextureModeString(self, mode): """获取纹理模式的字符串表示""" from panda3d.core import TextureStage @@ -1149,151 +1253,159 @@ class PropertyPanelManager: print(f"✗ 获取材质基础颜色失败: {e}") return None - def _selectDiffuseTexture(self,material_title): + def _selectDiffuseTexture(self, material_title): """漫反射贴图""" - from PyQt5.QtWidgets import QFileDialog + from PyQt5.QtWidgets import QFileDialog import os - file_dialog = QFileDialog(None,"选择漫反射贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") + file_dialog = QFileDialog(None, "选择漫反射贴图", "", "图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") if file_dialog.exec_(): filename = file_dialog.selectedFiles()[0] + cross = CrossPlatformPathHandler() + filename = cross.normalize_model_path(filename) if filename: - self._applyDiffuseTexture(material_title,filename) + self._applyDiffuseTexture(material_title, filename) print(f"已选择漫反射贴图:{filename}") - def _selectNormalTexture(self,material): + def _selectNormalTexture(self, material): """选择法线贴图""" - from PyQt5.QtWidgets import QFileDialog + from PyQt5.QtWidgets import QFileDialog - file_dialog = QFileDialog(None,"选择法线贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") + file_dialog = QFileDialog(None, "选择法线贴图", "", "图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") if file_dialog.exec_(): filename = file_dialog.selectedFiles()[0] + cross = CrossPlatformPathHandler() + filename = cross.normalize_model_path(filename) if filename: - self._applyNormalTexture(material,filename) + self._applyNormalTexture(material, filename) print(f"已选择法线贴图:{filename}") - def _selectRoughnessTexture(self,material): + def _selectRoughnessTexture(self, material): """选择粗糙度贴图""" from PyQt5.QtWidgets import QFileDialog - file_dialog = QFileDialog(None,"选择粗糙度贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") + file_dialog = QFileDialog(None, "选择粗糙度贴图", "", "图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") if file_dialog.exec_(): filename = file_dialog.selectedFiles()[0] + cross = CrossPlatformPathHandler() + filename = cross.normalize_model_path(filename) if filename: - self._applyRoughnessTexture_FINAL(material,filename) + self._applyRoughnessTexture_FINAL(material, filename) print(f"已选择粗糙度贴图: {filename}") - def _selectMetallicTexture(self,material): + def _selectMetallicTexture(self, material): """选择金属性贴图""" from PyQt5.QtWidgets import QFileDialog - file_dialog = QFileDialog(None,"选择金属性贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") + file_dialog = QFileDialog(None, "选择金属性贴图", "", "图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") if file_dialog.exec_(): filename = file_dialog.selectedFiles()[0] + cross = CrossPlatformPathHandler() + filename = cross.normalize_model_path(filename) if filename: - self._applyMetallicTexture_NEW(material,filename) + self._applyMetallicTexture_NEW(material, filename) print(f"已选择金属性贴图: {filename}") - #IOR贴图 - def _selectIORTexture(self,material): + # IOR贴图 + def _selectIORTexture(self, material): """选择IOR贴图""" from PyQt5.QtWidgets import QFileDialog - file_dialong = QFileDialog(None,"选择IOR贴图","","图像(*.png *.jpg *.jpeg *.tga *.bmp)") + file_dialong = QFileDialog(None, "选择IOR贴图", "", "图像(*.png *.jpg *.jpeg *.tga *.bmp)") if file_dialong.exec_(): filename = file_dialong.selectedFiles()[0] if filename: - self._applyIORTexture(material,filename) + self._applyIORTexture(material, filename) print(f"已选择IOR贴图:{filename}") - def _selectParallaxTexture(self,material): + def _selectParallaxTexture(self, material): """选择视差贴图""" from PyQt5.QtWidgets import QFileDialog - file_dialog = QFileDialog(None,"选择视差贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") + file_dialog = QFileDialog(None, "选择视差贴图", "", "图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") if file_dialog.exec_(): filename = file_dialog.selectedFiles()[0] if filename: - self._applyParallaxTexture(material,filename) + self._applyParallaxTexture(material, filename) print(f"已选择视差贴图:{filename}") - def _selectEmissionTexture(self,material): + def _selectEmissionTexture(self, material): """选择自发光贴图""" from PyQt5.QtWidgets import QFileDialog - file_dialog = QFileDialog(None,"选择自发光贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") + file_dialog = QFileDialog(None, "选择自发光贴图", "", "图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") if file_dialog.exec_(): filename = file_dialog.selectedFiles()[0] if filename: - self._applyEmissionTexture(material,filename) + self._applyEmissionTexture(material, filename) print(f"已选择自发光贴图:{filename}") - def _selectAOTexture(self,material): + def _selectAOTexture(self, material): """选择环境光遮蔽贴图""" from PyQt5.QtWidgets import QFileDialog - file_dialog = QFileDialog(None,"选择AO贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") + file_dialog = QFileDialog(None, "选择AO贴图", "", "图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") if file_dialog.exec_(): filename = file_dialog.selectedFiles()[0] if filename: - self._applyAOTexture(material,filename) + self._applyAOTexture(material, filename) print(f"已选择AO贴图:{filename}") - def _selectAlphaTexture(self,material): + def _selectAlphaTexture(self, material): """选择透明度贴图""" from PyQt5.QtWidgets import QFileDialog - file_dialog = QFileDialog(None,"选择透明度贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") + file_dialog = QFileDialog(None, "选择透明度贴图", "", "图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") if file_dialog.exec_(): filename = file_dialog.selectedFiles()[0] if filename: - self._applyAlphaTexture(material,filename) + self._applyAlphaTexture(material, filename) print(f"已选择透明度贴图:{filename}") - def _selectDetailTexture(self,material): + def _selectDetailTexture(self, material): """选择细节贴图""" from PyQt5.QtWidgets import QFileDialog - file_dialog = QFileDialog(None,"选择细节贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") + file_dialog = QFileDialog(None, "选择细节贴图", "", "图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") if file_dialog.exec_(): filename = file_dialog.selectedFiles()[0] if filename: - self._applyDetailTexture(material,filename) + self._applyDetailTexture(material, filename) print(f"已选择细节贴图:{filename}") - def _selectGlossTexture(self,material): + def _selectGlossTexture(self, material): """选择光泽贴图""" from PyQt5.QtWidgets import QFileDialog - file_dialog = QFileDialog(None,"选择光泽贴图","","图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") + file_dialog = QFileDialog(None, "选择光泽贴图", "", "图像文件(*.png *.jpg *.jpeg *.tga *.bmp)") if file_dialog.exec_(): filename = file_dialog.selectedFiles()[0] if filename: - self._applyGlossTexture(material,filename) + self._applyGlossTexture(material, filename) print(f"已选择光泽贴图:{filename}") - def _applyDiffuseTexture(self,material_title,texture_path): + def _applyDiffuseTexture(self, material_title, texture_path): """应用漫反射贴图""" try: from RenderPipelineFile.rpcore.loader import RPLoader from panda3d.core import TextureStage - #加载纹理 + # 加载纹理 texture = RPLoader.load_texture(texture_path) if texture: - #获取材质所属的节点 - material,node = self._findMaterialAndNodeByTitle(material_title) + # 获取材质所属的节点 + material, node = self._findMaterialAndNodeByTitle(material_title) if node and material: print(f"正在为节点 {node.getName()} 应用漫反射贴图") @@ -1316,8 +1428,7 @@ class PropertyPanelManager: node, effect_file, { - "normal_mapping": True, # 启用法线映射支持 - "render_gbuffer": True, + "normal_mapping": True, # 启用法线映射支持 "alpha_testing": needs_alpha, # 根据是否需要透明度决定 "parallax_mapping": False, "render_shadow": True, @@ -1354,7 +1465,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("==========================================") self._invalidateRenderState() @@ -1376,7 +1488,6 @@ class PropertyPanelManager: # # texture = RPLoader.load_texture(texture_path) # if texture: - # node = self._findNodeWithMaterial(material) # if node: # # 创建法线贴图纹理阶段 # normal_stage = TextureStage("normal") @@ -1405,7 +1516,6 @@ class PropertyPanelManager: # 查找使用该材质的具体几何节点 node = self._findSpecificGeomNodeForMaterial(material) if not node: - print("❌ 未找到材质对应的节点") return # 清理现有的法线贴图,避免冲突 @@ -1447,11 +1557,10 @@ class PropertyPanelManager: node, effect_file, { - "normal_mapping": True, # 强制启用法线映射 + "normal_mapping": True, # 强制启用法线映射 "render_gbuffer": True, "alpha_testing": needs_alpha, # 根据是否需要透明度决定 "parallax_mapping": False, - "render_shadow": True, "render_envmap": True }, 100 @@ -1466,7 +1575,6 @@ class PropertyPanelManager: # 创建法线贴图纹理阶段,对应p3d_Texture1 print("🎨 创建法线纹理阶段...") normal_stage = TextureStage("normal_map") - normal_stage.setSort(1) # 对应shader中的p3d_Texture1 normal_stage.setMode(TextureStage.MModulate) # 使用标准模式,不是MNormal normal_stage.setTexcoordName("texcoord") @@ -1508,7 +1616,6 @@ class PropertyPanelManager: import traceback traceback.print_exc() - def _applyRoughnessTexture_FINAL(self, material, texture_path): """应用粗糙度贴图 - 先编译后绑定策略""" try: @@ -1516,8 +1623,6 @@ class PropertyPanelManager: from panda3d.core import TextureStage import time - print(f"🎨 应用粗糙度贴图(先编译后绑定): {texture_path}") - # 1. 加载纹理 texture = RPLoader.load_texture(texture_path) if not texture: @@ -1542,14 +1647,12 @@ class PropertyPanelManager: if not has_normal: print("⚠️ 检测到材质没有法线贴图,先添加默认法线贴图...") - #self._applyDefaultNormalTexture(node) - self._applyNormalTexture(material,"RenderPipelineFile/Default_NRM_2K.png") + # self._applyDefaultNormalTexture(node) + self._applyNormalTexture(material, "RenderPipelineFile/Default_NRM_2K.png") print("✅ 默认法线贴图已添加") else: print("✅ 检测到材质已有法线贴图") - - # 5. 检查是否有金属性贴图和透明漫反射贴图,选择合适的PBR效果 print("🔧 步骤2:检查金属性贴图和透明度设置...") has_metallic = self._hasMetallicTexture(node) @@ -1569,7 +1672,6 @@ class PropertyPanelManager: effect_file, { "normal_mapping": True, # 始终启用法线映射 - "render_gbuffer": True, "alpha_testing": needs_alpha, # 根据是否需要透明度决定 "parallax_mapping": False, "render_shadow": True, @@ -1578,19 +1680,19 @@ class PropertyPanelManager: 100 ) print(f"✅ {effect_file} 效果已应用") - #print("✅ 着色器预编译完成") + # print("✅ 着色器预编译完成") # 5. 等待编译完成 - #time.sleep(0.2) # 200ms等待 - #print("⏱️ 等待着色器编译...") + # time.sleep(0.2) # 200ms等待 + # print("⏱️ 等待着色器编译...") # 6. 现在绑定纹理到已编译的着色器 - #print("🔧 步骤2:绑定纹理到编译完成的着色器...") + # print("🔧 步骤2:绑定纹理到编译完成的着色器...") # roughness_stage = TextureStage("roughness_map") # roughness_stage.setSort(3) # p3d_Texture3 # roughness_stage.setMode(TextureStage.MModulate) # node.setTexture(roughness_stage, texture) - #print("✅ 纹理已绑定到预编译着色器") + # print("✅ 纹理已绑定到预编译着色器") print("🧹 清理现有粗糙度贴图...") existing_stages = node.findAllTextureStages() @@ -1607,7 +1709,6 @@ class PropertyPanelManager: roughness_stage.setMode(TextureStage.MModulate) node.setTexture(roughness_stage, texture) - # 7. 验证效果 applied_texture = node.getTexture(roughness_stage) if applied_texture: @@ -1710,7 +1811,6 @@ class PropertyPanelManager: return False def _ensureEnhancedPBREffect(self, node): - """确保节点使用增强的PBR效果,支持金属性纹理""" try: print("🔧 应用金属性贴图支持的PBR效果...") self.world.render_pipeline.set_effect( @@ -1718,7 +1818,7 @@ class PropertyPanelManager: "effects/pbr_with_metallic.yaml", { "normal_mapping": False, # 关闭法线贴图避免干扰 - "render_gbuffer": True, # 必须启用gbuffer渲染 + "render_gbuffer": True, # 必须启用gbuffer渲染 "alpha_testing": False, "parallax_mapping": False, "render_shadow": True, @@ -1802,7 +1902,7 @@ class PropertyPanelManager: print(f"⚠️ 创建白色纹理失败: {e}") return False - def _applyMetallicTexture(self,material,texture_path): + def _applyMetallicTexture(self, material, texture_path): """应用金属性贴图 - Blender风格效果""" try: from RenderPipelineFile.rpcore.loader import RPLoader @@ -1882,7 +1982,7 @@ class PropertyPanelManager: "effects/metallic_only.yaml", { "normal_mapping": False, # 关闭法线贴图避免干扰 - "render_gbuffer": True, # 必须启用gbuffer渲染 + "render_gbuffer": True, # 必须启用gbuffer渲染 "alpha_testing": False, "parallax_mapping": False, "render_shadow": True, @@ -1984,7 +2084,7 @@ class PropertyPanelManager: import traceback traceback.print_exc() - def _applyIORTexture(self,material,texture_path): + def _applyIORTexture(self, material, texture_path): """应用IOR贴图到特定材质""" try: from RenderPipelineFile.rpcore.loader import RPLoader @@ -2015,7 +2115,7 @@ class PropertyPanelManager: ior_stage.setSort(2) # 对应p3d_Texture2 ior_stage.setMode(TextureStage.MModulate) - node.setTexture(ior_stage,texture) + node.setTexture(ior_stage, texture) print("IOR贴图已应用到p3d_Texture2槽") # 不再需要手动刷新渲染状态,避免闪烁 @@ -2027,7 +2127,7 @@ class PropertyPanelManager: import traceback traceback.print_exc() - def _applyParallaxTexture(self,material,texture_path): + def _applyParallaxTexture(self, material, texture_path): """应用视差贴图""" try: from RenderPipelineFile.rpcore.loader import RPLoader @@ -2057,7 +2157,7 @@ class PropertyPanelManager: parallax_stage.setSort(4) # 对应p3d_Texture4 parallax_stage.setMode(TextureStage.MHeight) # 高度贴图模式 - node.setTexture(parallax_stage,texture) + node.setTexture(parallax_stage, texture) print("视差贴图已应用到p3d_Texture4槽") self._invalidateRenderState() @@ -2069,16 +2169,16 @@ class PropertyPanelManager: import traceback traceback.print_exc() - def _ensureNormalMappingEnabled(self,model): + def _ensureNormalMappingEnabled(self, model): """确保模型启用了法线映射功能""" try: self.world.render_pipeline.set_effect( model, "effects/default.yaml", { - "normal_mapping":True, - "render_gbuffer":True, - "alpha_testing":True + "normal_mapping": True, + "render_gbuffer": True, + "alpha_testing": True }, 30 ) @@ -2267,7 +2367,7 @@ class PropertyPanelManager: { "normal_mapping": True, "render_gbuffer": False, # 透明物体不渲染到GBuffer - "render_forward": True, # 使用前向渲染 + "render_forward": True, # 使用前向渲染 "alpha_testing": True, "parallax_mapping": False, "render_shadow": True, @@ -2346,8 +2446,6 @@ class PropertyPanelManager: except: pass - - def _applyEmissionTexture(self, material, texture_path): """应用自发光贴图""" try: @@ -2767,17 +2865,35 @@ class PropertyPanelManager: print("未找到匹配的几何节点") return None - def _displayCurrentTextures(self, material): + def _displayCurrentTextures(self, material, material_layout, current_row): """显示当前材质的贴图信息""" node = self._findNodeWithMaterial(material) + + # 当前贴图信息标题 + current_row += 1 + texture_info_title = QLabel("当前贴图信息") + texture_info_title.setStyleSheet("color: #666; font-weight: bold; font-size: 10px; margin-top: 5px;") + material_layout.addWidget(texture_info_title, current_row, 0, 1, 4) + current_row += 1 + 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) + texture_info.setStyleSheet("color: #666; font-size: 9px;") + material_layout.addWidget(texture_info, current_row, 0, 1, 2) + else: + no_texture_info = QLabel("当前贴图: 无") + no_texture_info.setStyleSheet("color: #888; font-size: 9px; font-style: italic;") + material_layout.addWidget(no_texture_info, current_row, 0, 1, 2) + else: + no_node_info = QLabel("无法获取贴图信息") + no_node_info.setStyleSheet("color: #888; font-size: 9px; font-style: italic;") + material_layout.addWidget(no_node_info, current_row, 0, 1, 4) + + return current_row + 1 def _applyToAllMaterials(self, model, property_name, value): """将属性应用到模型的所有材质""" @@ -2793,7 +2909,7 @@ class PropertyPanelManager: material.set_refractive_index(value) self._invalidateRenderState() - def _addShadingModelPanel(self, material): + def _addShadingModelPanel(self, material, material_layout, current_row): """添加着色模型选择面板""" from PyQt5.QtWidgets import QComboBox @@ -2808,14 +2924,17 @@ class PropertyPanelManager: ] shading_title = QLabel("着色模型") - shading_title.setStyleSheet("color: #4CAF50; font-weight:bold;") - self._propertyLayout.addRow(shading_title) + shading_title.setStyleSheet("font-weight:bold;") + material_layout.addWidget(shading_title,current_row, 0, 1, 4) + current_row += 1 + + material_layout.addWidget(QLabel("着色模型:"), current_row, 0) 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: @@ -2828,11 +2947,17 @@ class PropertyPanelManager: shading_combo.currentIndexChanged.connect( lambda idx: self._onShadingModelChanged(material, idx) ) - self._propertyLayout.addRow("着色模型:", shading_combo) + material_layout.addWidget(shading_combo, current_row, 1, 1, 3) + current_row += 1 # 如果是透明着色模型,添加透明度控制 - if hasattr(material, 'emission') and material.emission is not None and int(material.emission.x) == 3: - self._addTransparencyPanel(material) + try: + if hasattr(material, 'emission') and material.emission is not None and int(material.emission.x) == 3: + current_row = self._addTransparencyPanel(material, material_layout, current_row) + except Exception as e: + print(f"添加透明度面板时出错: {e}") + + return current_row def _onShadingModelChanged(self, material, model_index): """处理着色模型变化""" @@ -2846,7 +2971,8 @@ class PropertyPanelManager: print("切换到透明模式,添加透明度面板...") # 延迟一点时间让UI更新完成 from PyQt5.QtCore import QTimer - QTimer.singleShot(100, lambda: self._addTransparencyPanel(material)) + QTimer.singleShot(100, lambda: self._refreshMaterialUI()) + # QTimer.singleShot(100, lambda: self._addTransparencyPanel(material)) def _updateShadingModel(self, material, model_index): """更新着色模型""" @@ -2875,7 +3001,7 @@ class PropertyPanelManager: self._updateMaterialAlphaForTransparency(material, default_opacity) # 应用透明渲染效果 - #self._applyTransparentRenderingEffect() + # self._applyTransparentRenderingEffect() print(f"透明着色模型设置完成") print(f" - emission.x = {model_index} (透明着色模型)") @@ -2891,15 +3017,18 @@ 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): + def _addTransparencyPanel(self, material, material_layout, current_row): """添加透明度控制面板""" transparency_title = QLabel("透明度属性") transparency_title.setStyleSheet("color: #00BFFF; font-weight:bold;") - self._propertyLayout.addRow(transparency_title) + material_layout.addWidget(transparency_title,current_row, 0, 1, 4) + current_row += 1 # 不透明度滑块(避免混淆,使用不透明度) + material_layout.addWidget(QLabel("不透明度:"), current_row, 0) opacity_spinbox = QDoubleSpinBox() opacity_spinbox.setRange(0.0, 1.0) # 最小值0.1,避免完全消失 opacity_spinbox.setSingleStep(0.01) @@ -2909,19 +3038,22 @@ class PropertyPanelManager: try: base_color = self._getOrCreateMaterialBaseColor(material) if base_color is not None: - current_opacity=base_color.w + current_opacity = base_color.w except Exception as e: print(f"获取当前透明度失败,使用默认值: {e}") opacity_spinbox.setValue(current_opacity) opacity_spinbox.valueChanged.connect(lambda v: self._updateTransparency(material, v)) - self._propertyLayout.addRow("不透明度:", opacity_spinbox) + material_layout.addWidget(opacity_spinbox, current_row, 1, 1, 3) + current_row += 1 + + return current_row def _updateTransparency(self, material, opacity_value): """更新不透明度值(同时更新emission.y和base_color.w)""" try: from panda3d.core import Vec4 - current_emission =material.emission or Vec4(0, 0, 0, 0) + current_emission = material.emission or Vec4(0, 0, 0, 0) new_emission = Vec4(current_emission.x, opacity_value, current_emission.z, current_emission.w) material.set_emission(new_emission) @@ -2973,7 +3105,7 @@ class PropertyPanelManager: model, "effects/default.yaml", { - "render_gbuffer":True, + "render_gbuffer": True, "alpha_testing": False, "normal_mapping": True, "render_shadow": True, @@ -2982,7 +3114,6 @@ class PropertyPanelManager: sort=100 ) - # 让RenderPipeline自动处理透明材质 # 当emission.x=3时,RenderPipeline会自动设置正确的渲染参数 self.world.render_pipeline.prepare_scene(model) @@ -2996,13 +3127,16 @@ class PropertyPanelManager: import traceback traceback.print_exc() - def _addEmissionPanel(self, material): + def _addEmissionPanel(self, material, material_layout, current_row): """添加自发光控制面板""" emission_title = QLabel("自发光属性") - emission_title.setStyleSheet("color: #FF6B6B; font-weight:bold;") - self._propertyLayout.addRow(emission_title) + emission_title.setStyleSheet("font-weight:bold;") + material_layout.addWidget(emission_title, current_row, 0, 1, 4) + current_row += 1 + + # 自发光强度标签和控件 + material_layout.addWidget(QLabel("发光强度:"), current_row, 0) - # 自发光强度 emission_spinbox = QDoubleSpinBox() emission_spinbox.setRange(0.0, 10.0) emission_spinbox.setSingleStep(0.1) @@ -3014,7 +3148,10 @@ class PropertyPanelManager: emission_spinbox.setValue(current_emission_z) emission_spinbox.valueChanged.connect(lambda v: self._updateEmissionStrength(material, v)) - self._propertyLayout.addRow("发光强度:", emission_spinbox) + material_layout.addWidget(emission_spinbox, current_row, 1, 1, 3) + current_row += 1 + + return current_row def _updateEmissionStrength(self, material, strength): """更新自发光强度""" @@ -3047,13 +3184,17 @@ class PropertyPanelManager: self._invalidateRenderState() print(f"自发光强度已更新为: {strength}") - def _addMaterialPresetPanel(self, material): + def _addMaterialPresetPanel(self, material, material_layout, current_row): """添加材质预设面板""" from PyQt5.QtWidgets import QComboBox preset_title = QLabel("材质预设") - preset_title.setStyleSheet("color: #9C27B0; font-weight:bold;") - self._propertyLayout.addRow(preset_title) + preset_title.setStyleSheet("font-weight:bold;") + material_layout.addWidget(preset_title, current_row, 0, 1, 4) + current_row += 1 + + # 选择预设标签和控件 + material_layout.addWidget(QLabel("选择预设:"), current_row, 0) preset_combo = QComboBox() preset_combo.addItems([ @@ -3075,7 +3216,10 @@ class PropertyPanelManager: preset_combo.currentTextChanged.connect( lambda preset: self._applyMaterialPreset(material, preset) ) - self._propertyLayout.addRow("选择预设:", preset_combo) + material_layout.addWidget(preset_combo, current_row, 1, 1, 3) + current_row += 1 + + return current_row def _detectCurrentPreset(self, material): """检测当前材质最接近的预设""" @@ -3098,9 +3242,9 @@ class PropertyPanelManager: if hasattr(material, 'base_color') and material.base_color is not None: try: base_color_match = ( - abs(material.base_color.x - preset_values["base_color"][0]) < tolerance and - abs(material.base_color.y - preset_values["base_color"][1]) < tolerance and - abs(material.base_color.z - preset_values["base_color"][2]) < tolerance + abs(material.base_color.x - preset_values["base_color"][0]) < tolerance and + abs(material.base_color.y - preset_values["base_color"][1]) < tolerance and + abs(material.base_color.z - preset_values["base_color"][2]) < tolerance ) except (AttributeError, TypeError): base_color_match = False @@ -3138,7 +3282,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}, @@ -3149,8 +3294,6 @@ class PropertyPanelManager: print(f"未知的材质预设: {preset_name}") return - - preset = presets[preset_name] if "shading_model" in preset: @@ -3163,25 +3306,23 @@ class PropertyPanelManager: print(f"材质emission值: {material.emission}") print(f"基础颜色alpha: {preset['base_color'].w}") - - 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) + emission = Vec4(float(preset["shading_model"]), 0, 0, 0) if "transparency" in preset: emission.y = preset["transparency"] material.set_emission(emission) - #关键:为透明材质应用正确的渲染效果 - if preset["shading_model"]==3: + # 关键:为透明材质应用正确的渲染效果 + if preset["shading_model"] == 3: self._apply_transparent_effect() self._invalidateRenderState() - #material._applied_preset = preset_name + # material._applied_preset = preset_name self._refreshMaterialUI() print(f"已应用材质预设: {preset_name}") @@ -3310,8 +3451,8 @@ class PropertyPanelManager: if not has_normal: print("⚠️ 检测到材质没有法线贴图,先添加默认法线贴图...") - #self._applyDefaultNormalTexture(node) - self._applyNormalTexture(material,"RenderPipelineFile/Default_NRM_2K.png") + # self._applyDefaultNormalTexture(node) + self._applyNormalTexture(material, "RenderPipelineFile/Default_NRM_2K.png") print("✅ 默认法线贴图已添加") else: print("✅ 检测到材质已有法线贴图") @@ -3340,7 +3481,6 @@ class PropertyPanelManager: { "normal_mapping": True, "render_gbuffer": True, - "alpha_testing": needs_alpha, # 根据是否需要透明度决定 "parallax_mapping": False, "render_shadow": True, "render_envmap": True @@ -3360,7 +3500,7 @@ class PropertyPanelManager: node, "effects/pbr_with_metallic.yaml", { - #"normal_mapping": has_normal, + # "normal_mapping": has_normal, "normal_mapping": True, "render_gbuffer": True, "alpha_testing": False, @@ -3380,7 +3520,7 @@ class PropertyPanelManager: # 多次重新绑定,模拟手动"应用两次"的效果 for i in range(3): - print(f" 第{i+1}次绑定...") + print(f" 第{i + 1}次绑定...") # 等待更长时间确保着色器编译完成 time.sleep(0.05) # 50ms延迟 @@ -3395,9 +3535,9 @@ class PropertyPanelManager: # 验证绑定 applied_texture = node.getTexture(metallic_stage) if applied_texture: - print(f" ✅ 第{i+1}次绑定成功") + print(f" ✅ 第{i + 1}次绑定成功") else: - print(f" ❌ 第{i+1}次绑定失败") + print(f" ❌ 第{i + 1}次绑定失败") # 最终验证 final_texture = node.getTexture(metallic_stage) @@ -3492,117 +3632,108 @@ class PropertyPanelManager: print(f"⚠️ 检查透明度需求失败: {e}") return True # 出错时默认启用透明度测试,更安全 + def _addAnimationPanel(self, model, model_ref): + """添加动画控制面板""" + # 动画控制组 + animation_group = QGroupBox("动画控制") + animation_layout = QGridLayout() + + # 检测动画 + animations = self._detectAnimations(model) + + if animations: + # 动画列表 + animation_layout.addWidget(QLabel("动画列表:"), 0, 0) + animationCombo = QComboBox() + animationCombo.addItems(animations) + animation_layout.addWidget(animationCombo, 0, 1, 1, 2) # 跨两列 + + # 播放控制按钮 + playBtn = QPushButton("播放") + stopBtn = QPushButton("停止") + pauseBtn = QPushButton("暂停") + + animation_layout.addWidget(playBtn, 1, 0) + animation_layout.addWidget(stopBtn, 1, 1) + animation_layout.addWidget(pauseBtn, 1, 2) + + # 播放速度 + animation_layout.addWidget(QLabel("播放速度:"), 2, 0) + speedSpinBox = QDoubleSpinBox() + speedSpinBox.setRange(0.1, 5.0) + speedSpinBox.setSingleStep(0.1) + speedSpinBox.setValue(1.0) + animation_layout.addWidget(speedSpinBox, 2, 1) + + # 循环播放 + loopCheckBox = QCheckBox("循环播放") + loopCheckBox.setChecked(True) + animation_layout.addWidget(loopCheckBox, 2, 2) + + # 连接事件 + playBtn.clicked.connect(lambda: self._playAnimation(model_ref, animationCombo.currentText(), + speedSpinBox.value(), loopCheckBox.isChecked())) + stopBtn.clicked.connect(lambda: self._stopAnimation(model_ref)) + pauseBtn.clicked.connect(lambda: self._pauseAnimation(model_ref)) + else: + # 无动画提示 + no_anim_label = QLabel("此模型没有动画") + no_anim_label.setStyleSheet("color: gray; font-style: italic;") + animation_layout.addWidget(no_anim_label, 0, 0, 1, 3) # 跨三列 + + animation_group.setLayout(animation_layout) + self._propertyLayout.addWidget(animation_group) + def _addSunAzimuthPanel(self): """添加太阳方位角控制面板""" - try: - from PyQt5.QtWidgets import QLabel, QSlider, QSpinBox, QHBoxLayout, QWidget - from PyQt5.QtCore import Qt + # 太阳控制组 + sun_group = QGroupBox("太阳控制") + sun_layout = QGridLayout() - # 添加分隔线 - separator = QLabel("─" * 30) - separator.setStyleSheet("color: lightgray;") - self._propertyLayout.addRow(separator) + # 太阳方位角 + sun_layout.addWidget(QLabel("方位角:"), 0, 0) + self.azimuthSpinBox = QDoubleSpinBox() + self.azimuthSpinBox.setRange(0, 360) + self.azimuthSpinBox.setSuffix("°") + self.azimuthSpinBox.setValue(180) + self.azimuthSpinBox.valueChanged.connect(self._applySunAzimuth_new) + sun_layout.addWidget(self.azimuthSpinBox, 0, 1) - # 标题 - title_label = QLabel("☀️ 太阳光照控制") - title_label.setStyleSheet("font-weight: bold; color: #FFA500; font-size: 12px;") - self._propertyLayout.addRow(title_label) + # 太阳高度角 + sun_layout.addWidget(QLabel("高度角:"), 1, 0) + self.altitudeSpinBox = QDoubleSpinBox() + self.altitudeSpinBox.setRange(0, 90) + self.altitudeSpinBox.setSuffix("°") + self.altitudeSpinBox.setValue(90) + self.altitudeSpinBox.valueChanged.connect(self._applySunAltitude) + sun_layout.addWidget(self.altitudeSpinBox, 1, 1) - # 太阳方位角控制 - azimuth_widget = QWidget() - azimuth_layout = QHBoxLayout(azimuth_widget) - azimuth_layout.setContentsMargins(0, 0, 0, 0) + # 太阳预设 + sun_layout.addWidget(QLabel("预设:"), 2, 0) + presetCombo = QComboBox() + presetCombo.addItems(["日出", "正午", "日落", "午夜"]) + sun_layout.addWidget(presetCombo, 2, 1) - # 滑块控制 (0-360度) - self.sun_azimuth_slider = QSlider(Qt.Horizontal) - self.sun_azimuth_slider.setMinimum(0) - self.sun_azimuth_slider.setMaximum(360) - self.sun_azimuth_slider.setValue(180) # 默认值180度 - self.sun_azimuth_slider.setTickPosition(QSlider.TicksBelow) - self.sun_azimuth_slider.setTickInterval(45) # 每45度一个刻度 + # 映射 + preset_map = { + "日出": "sunrise", + "正午": "noon", + "日落": "sunset", + "午夜": "midnight" + } - # 数值显示框 - self.sun_azimuth_spinbox = QSpinBox() - self.sun_azimuth_spinbox.setMinimum(0) - self.sun_azimuth_spinbox.setMaximum(360) - self.sun_azimuth_spinbox.setValue(180) - self.sun_azimuth_spinbox.setSuffix("°") - self.sun_azimuth_spinbox.setFixedWidth(80) + # 应用预设按钮 + applyPresetBtn = QPushButton("应用预设") + applyPresetBtn.clicked.connect(lambda: self._setSunPreset(preset_map[presetCombo.currentText()])) + sun_layout.addWidget(applyPresetBtn, 2, 2) - # 连接信号 - self.sun_azimuth_slider.valueChanged.connect(self._onSunAzimuthSliderChanged) - self.sun_azimuth_spinbox.valueChanged.connect(self._onSunAzimuthSpinboxChanged) + # 实时更新 + # realtimeCheckBox = QCheckBox("实时更新") + # realtimeCheckBox.setChecked(True) + # sun_layout.addWidget(realtimeCheckBox, 3, 0, 1, 3) # 跨三列 - azimuth_layout.addWidget(self.sun_azimuth_slider) - azimuth_layout.addWidget(self.sun_azimuth_spinbox) - - self._propertyLayout.addRow("太阳方位角:", azimuth_widget) - - # 太阳高度角控制 - altitude_widget = QWidget() - altitude_layout = QHBoxLayout(altitude_widget) - altitude_layout.setContentsMargins(0, 0, 0, 0) - - # 滑块控制 (0-90度) - self.sun_altitude_slider = QSlider(Qt.Horizontal) - self.sun_altitude_slider.setMinimum(0) - self.sun_altitude_slider.setMaximum(90) - self.sun_altitude_slider.setValue(45) # 默认值45度 - self.sun_altitude_slider.setTickPosition(QSlider.TicksBelow) - self.sun_altitude_slider.setTickInterval(15) # 每15度一个刻度 - - # 数值显示框 - self.sun_altitude_spinbox = QSpinBox() - self.sun_altitude_spinbox.setMinimum(0) - self.sun_altitude_spinbox.setMaximum(90) - self.sun_altitude_spinbox.setValue(45) - self.sun_altitude_spinbox.setSuffix("°") - self.sun_altitude_spinbox.setFixedWidth(80) - - # 连接信号 - self.sun_altitude_slider.valueChanged.connect(self._onSunAltitudeSliderChanged) - self.sun_altitude_spinbox.valueChanged.connect(self._onSunAltitudeSpinboxChanged) - - altitude_layout.addWidget(self.sun_altitude_slider) - altitude_layout.addWidget(self.sun_altitude_spinbox) - - self._propertyLayout.addRow("太阳高度角:", altitude_widget) - - # 添加预设按钮 - preset_widget = QWidget() - preset_layout = QHBoxLayout(preset_widget) - preset_layout.setContentsMargins(0, 0, 0, 0) - - # 预设按钮 - sunrise_btn = QPushButton("🌅 日出") - sunrise_btn.setFixedWidth(60) - sunrise_btn.clicked.connect(lambda: self._setSunPreset("sunrise")) - - noon_btn = QPushButton("☀️ 正午") - noon_btn.setFixedWidth(60) - noon_btn.clicked.connect(lambda: self._setSunPreset("noon")) - - sunset_btn = QPushButton("🌇 日落") - sunset_btn.setFixedWidth(60) - sunset_btn.clicked.connect(lambda: self._setSunPreset("sunset")) - - midnight_btn = QPushButton("🌙 午夜") - midnight_btn.setFixedWidth(60) - midnight_btn.clicked.connect(lambda: self._setSunPreset("midnight")) - - preset_layout.addWidget(sunrise_btn) - preset_layout.addWidget(noon_btn) - preset_layout.addWidget(sunset_btn) - preset_layout.addWidget(midnight_btn) - - self._propertyLayout.addRow("快速设置:", preset_widget) - - print("✅ 太阳控制面板已添加") - - except Exception as e: - print(f"❌ 添加太阳方位角控制面板失败: {e}") - import traceback - traceback.print_exc() + sun_group.setLayout(sun_layout) + self._propertyLayout.addWidget(sun_group) def _onSunAzimuthSliderChanged(self, value): """滑块值改变时的回调""" @@ -3664,30 +3795,24 @@ class PropertyPanelManager: """设置太阳预设位置""" try: presets = { - "sunrise": (90, 45), # 东方,低角度 - "noon": (180, 90), # 南方,天顶 - "sunset": (270, 45), # 西方,低角度 - "midnight": (0, 0) # 北方,地平线 + "sunrise": (90, 45), # 东方,低角度 + "noon": (180, 90), # 南方,天顶 + "sunset": (270, 45), # 西方,低角度 + "midnight": (0, 0) # 北方,地平线 } if preset_name in presets: azimuth, altitude = presets[preset_name] # 更新滑块和数值框 - self.sun_azimuth_slider.blockSignals(True) - self.sun_azimuth_spinbox.blockSignals(True) - self.sun_altitude_slider.blockSignals(True) - self.sun_altitude_spinbox.blockSignals(True) + self.azimuthSpinBox.blockSignals(True) + self.altitudeSpinBox.blockSignals(True) - self.sun_azimuth_slider.setValue(azimuth) - self.sun_azimuth_spinbox.setValue(azimuth) - self.sun_altitude_slider.setValue(altitude) - self.sun_altitude_spinbox.setValue(altitude) + self.azimuthSpinBox.setValue(azimuth) + self.altitudeSpinBox.setValue(altitude) - self.sun_azimuth_slider.blockSignals(False) - self.sun_azimuth_spinbox.blockSignals(False) - self.sun_altitude_slider.blockSignals(False) - self.sun_altitude_spinbox.blockSignals(False) + self.azimuthSpinBox.blockSignals(False) + self.altitudeSpinBox.blockSignals(False) # 应用设置 - 优先使用Day Time Editor azimuth_success = self._updateDayTimeEditorSetting("scattering", "sun_azimuth", azimuth) @@ -3818,7 +3943,6 @@ class PropertyPanelManager: if hasattr(setting_handle, 'curves') and setting_handle.curves: # 清除现有的控制点,设置单一值 setting_handle.curves[0].set_single_value(normalized_value) - print(f"✅ 更新Day Time设置: {plugin_name}.{setting_name} = {value} (归一化: {normalized_value:.3f})") # 保存设置到配置文件 try: @@ -3874,7 +3998,6 @@ class PropertyPanelManager: print("⚠️ 无法控制Day Time Editor中的Sun Azimuth节点") print(" 原因:Day Time Editor运行在独立进程中") print(" 建议:") - print(" 1. 直接在Day Time Editor窗口中调整Sun Azimuth") print(" 2. 或者实现进程间通信机制") except Exception as e: @@ -3912,62 +4035,6 @@ class PropertyPanelManager: print(f"⚠️ 文件通信失败: {e}") return False - def _addAnimationPanel(self,originmodel,filepath): - try: - model = Actor(filepath) - model.reparentTo(self.world.render) - - - model.setPos(0,0,0.3) - - model.hide() - animations = model.getAnimNames() - - if animations: - animation_title=QLabel("动画控制") - animation_title.setStyleSheet("color:#6B6BFF;font-weight:bold;font-size:14px;margin-top:10px;") - self._propertyLayout.addRow(animation_title) - - self.animation_combo = QComboBox() - self.animation_combo.addItems(animations) - self._propertyLayout.addRow("动画名称:", self.animation_combo) - - #播放控制按钮 - button_layout = QHBoxLayout() - - self.play_button = QPushButton("播放") - self.play_button.clicked.connect(lambda:self._playAnimation(model,originmodel)) - button_layout.addWidget(self.play_button) - - self.pause_button = QPushButton("暂停") - self.pause_button.clicked.connect(lambda:self._pauseAnimation(model,originmodel)) - button_layout.addWidget(self.pause_button) - - self.stop_button = QPushButton("停止") - self.stop_button.clicked.connect(lambda:self._stopAnimation(model,originmodel)) - button_layout.addWidget(self.stop_button) - - self.loop_button = QPushButton("循环") - self.loop_button.clicked.connect(lambda:self._loopAnimation(model,originmodel)) - button_layout.addWidget(self.loop_button) - - button_widget = QWidget() - button_widget.setLayout(button_layout) - self._propertyLayout.addRow("控制:", button_widget) - - self.speed_spinbox = QDoubleSpinBox() - self.speed_spinbox.setRange(0.1,5.0) - self.speed_spinbox.setValue(1.0) - self.speed_spinbox.setSingleStep(0.1) - self.speed_spinbox.valueChanged.connect(lambda v:self._setAnimationSpeed(model,v)) - self._propertyLayout.addRow("播放速度:", self.speed_spinbox) - else: - no_anim_label = QLabel("此模型无动画") - no_anim_label.setStyleSheet("color:#888888;font-style:italic;") - self._propertyLayout.addRow("动画:",no_anim_label) - except Exception as e: - print(f"添加动画面板失败: {e}") - def _detectAnimations(self, model): """检测模型中的动画""" try: @@ -3996,7 +4063,7 @@ class PropertyPanelManager: from direct.actor.Actor import Actor import os - model_path=model.filepath + model_path = model.filepath if model_path: actor = Actor(model_path) actor.reparentTo(self.world.render) @@ -4008,12 +4075,12 @@ class PropertyPanelManager: print(f"转换为Actor失败: {e}") return None - def _getModelPath(self,model): + def _getModelPath(self, model): import os model_root = model.find("**/+ModelRoot") if not model_root.isEmpty(): model_root_node = model_root.node() - if hasattr(model_root_node,'get_fullpath'): + if hasattr(model_root_node, 'get_fullpath'): fullpath = model_root_node.get_fullpath() if fullpath and not fullpath.empty(): return str(fullpath) @@ -4023,7 +4090,7 @@ class PropertyPanelManager: return None - def _playAnimation(self, model,originmodel): + def _playAnimation(self, model, originmodel): """播放动画""" try: print(f"=== 动画播放调试信息 ===") @@ -4036,7 +4103,7 @@ class PropertyPanelManager: if hasattr(self, 'animation_combo'): anim_name = self.animation_combo.currentText() print(f"播放动画: {anim_name}") - self._initActorModel(model,originmodel) + self._initActorModel(model, originmodel) # 使用Actor的标准方法播放动画 model.play(anim_name) @@ -4053,9 +4120,9 @@ class PropertyPanelManager: import traceback traceback.print_exc() - def _pauseAnimation(self,model,originmodel): + def _pauseAnimation(self, model, originmodel): try: - if hasattr(model,'stop'): + if hasattr(model, 'stop'): self._initActorModel(model, originmodel) originmodel.hide() model.show() @@ -4064,9 +4131,9 @@ class PropertyPanelManager: except Exception as e: print(f"暂停动画失败:{e}") - def _stopAnimation(self,model,originmodel): + def _stopAnimation(self, model, originmodel): try: - if hasattr(model,'stop'): + if hasattr(model, 'stop'): self._initActorModel(model, originmodel) originmodel.show() model.hide() @@ -4075,12 +4142,12 @@ class PropertyPanelManager: except Exception as e: print(f"停止动画失败:{e}") - def _loopAnimation(self,model,originmodel): + def _loopAnimation(self, model, originmodel): try: - if hasattr(self,'animation_combo'): + if hasattr(self, 'animation_combo'): self._initActorModel(model, originmodel) anim_name = self.animation_combo.currentText() - if anim_name and hasattr(model,'loop'): + if anim_name and hasattr(model, 'loop'): model.show() originmodel.hide() model.loop(anim_name) @@ -4088,18 +4155,30 @@ class PropertyPanelManager: except Exception as e: print(f"循环播放动画失败:{e}") - def _setAnimationSpeed(self,model,speed): + def _setAnimationSpeed(self, model, speed): try: - if hasattr(self,'animation_combo'): + if hasattr(self, 'animation_combo'): anim_name = self.animation_combo.currentText() - if anim_name and hasattr(model,'setPlayRate'): - model.setPlayRate(speed,anim_name) + if anim_name and hasattr(model, 'setPlayRate'): + model.setPlayRate(speed, anim_name) print(f"设置动画速度:{speed}") except Exception as e: print(f"设置动画速度失败:{e}") - def _initActorModel(self,model,originmodel): + def _initActorModel(self, model, originmodel): model.setPos(originmodel.getPos()) model.setScale(originmodel.getScale()) model.setHpr(originmodel.getHpr()) + + + + + + + + + + + + diff --git a/ui/widgets.py b/ui/widgets.py index 70347ec1..fdf62712 100644 --- a/ui/widgets.py +++ b/ui/widgets.py @@ -10,10 +10,10 @@ import os import re -from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QGroupBox, QHBoxLayout, - QLineEdit, QPushButton, QLabel, QDialogButtonBox, - QTreeView, QTreeWidget, QTreeWidgetItem, QWidget, - QFileDialog, QMessageBox) +from PyQt5.QtWidgets import (QDialog, QVBoxLayout, QGroupBox, QHBoxLayout, + QLineEdit, QPushButton, QLabel, QDialogButtonBox, + QTreeView, QTreeWidget, QTreeWidgetItem, QWidget, + QFileDialog, QMessageBox, QAbstractItemView) from PyQt5.QtCore import Qt, QUrl from PyQt5.QtGui import QDrag, QPainter, QPixmap from PyQt5.sip import wrapinstance @@ -310,10 +310,28 @@ class CustomTreeWidget(QTreeWidget): parent = wrapinstance(0, QWidget) super().__init__(parent) self.world = world + self.setupUI() # 初始化界面 + + self.setupDragDrop() # 设置拖拽功能 + + self.setDragDropMode(QTreeWidget.InternalMove) + + + def setupUI(self): + """初始化UI设置""" + self.setHeaderHidden(True) + # 启用多选和拖拽 + self.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) + self.setDropIndicatorShown(True) + + def setupDragDrop(self): + """设置拖拽功能""" + # 使用自定义拖拽模式 + self.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove) # 或者使用 DragDrop + self.setDefaultDropAction(Qt.DropAction.MoveAction) self.setDragEnabled(True) self.setAcceptDrops(True) - self.setDragDropMode(QTreeWidget.InternalMove) - + def dropEvent(self, event): """处理拖放事件""" # 获取拖动的项和目标项