1
0
forked from Rowland/EG

Merge remote-tracking branch 'origin/addRender' into main_ch_eg

# Conflicts:
#	.gitignore
#	.idea/AugmentWebviewStateStore.xml
#	RenderPipelineFile/config/daytime.yaml
#	ui/property_panel.py
This commit is contained in:
陈横 2025-08-21 11:31:31 +08:00
commit bb76795478
12 changed files with 248 additions and 175 deletions

BIN
.gitignore vendored

Binary file not shown.

3
.idea/.gitignore generated vendored
View File

@ -1,3 +0,0 @@
# 默认忽略的文件
/shelf/
/workspace.xml

File diff suppressed because one or more lines are too long

2
.idea/EG.iml generated
View File

@ -4,7 +4,7 @@
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.10 (EG)" jdkType="Python SDK" />
<orderEntry type="jdk" jdkName="Python 3.12 (PythonProject)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">

View File

@ -1,15 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ourVersions">
<value>
<list size="2">
<item index="0" class="java.lang.String" itemvalue="2.7" />
<item index="1" class="java.lang.String" itemvalue="3.14" />
</list>
</value>
</option>
</inspection_tool>
</profile>
</component>

View File

@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

2
.idea/misc.xml generated
View File

@ -3,5 +3,5 @@
<component name="Black">
<option name="sdkName" value="Python 3.12 (PythonProject)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (EG)" project-jdk-type="Python SDK" />
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (PythonProject)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated
View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/EG.iml" filepath="$PROJECT_DIR$/.idea/EG.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated
View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

File diff suppressed because one or more lines are too long

View File

@ -105,6 +105,37 @@ class SelectionSystem:
if not self.selectionBox or not self.selectionBoxTarget:
return
# 检查是否需要重新计算边界框
if not hasattr(self, '_bounds_cache'):
self._bounds_cache = {}
node_name = self.selectionBoxTarget.getName()
import time
current_time = time.time()
# 如果缓存存在且未过期,则使用缓存
if (node_name in self._bounds_cache and
current_time - self._bounds_cache[node_name]['time'] < 0.1):
minPoint, maxPoint = self._bounds_cache[node_name]['bounds']
else:
# 计算新的边界框并缓存
minPoint = Point3()
maxPoint = Point3()
if not self.selectionBoxTarget.calcTightBounds(minPoint, maxPoint, self.world.render):
return
# 缓存结果
self._bounds_cache[node_name] = {
'bounds': (minPoint, maxPoint),
'time': current_time
}
# 清理旧缓存
expired_keys = [k for k, v in self._bounds_cache.items()
if current_time - v['time'] > 1.0]
for key in expired_keys:
del self._bounds_cache[key]
# 清除现有的几何体
self.selectionBox.removeNode()
self.selectionBox = self.world.render.attachNewNode("selectionBox")
@ -177,6 +208,15 @@ class SelectionSystem:
def updateSelectionBoxTask(self, task):
"""选择框更新任务"""
try:
if not hasattr(self,'_last_selection_box_update'):
self._last_selection_box_update = 0
import time
current_time = time.time()
if current_time - self._last_selection_box_update < 0.1:
return task.cont
self._last_selection_box_update = current_time
if not self.selectionBox or not self.selectionBoxTarget:
return task.done # 结束任务
@ -598,6 +638,16 @@ class SelectionSystem:
def updateGizmoTask(self, task):
"""坐标轴更新任务 - 包含固定大小功能"""
try:
# 限制更新频率
if not hasattr(self, '_last_gizmo_update'):
self._last_gizmo_update = 0
import time
current_time = time.time()
if current_time - self._last_gizmo_update < 0.05: # 每0.05秒更新一次
return task.cont
self._last_gizmo_update = current_time
if not self.gizmo or not self.gizmoTarget:
return task.done
@ -613,25 +663,27 @@ class SelectionSystem:
self.gizmoTarget.setPos(light_pos)
else:
# 更新坐标轴位置,始终在目标节点中心
minPoint = Point3()
maxPoint = Point3()
if self.gizmoTarget.calcTightBounds(minPoint, maxPoint, self.world.render):
# 计算中心点
center = Point3((minPoint.x + maxPoint.x) * 0.5,
(minPoint.y + maxPoint.y) * 0.5,
(minPoint.z + maxPoint.z) * 0.5)
self.gizmo.setPos(center)
# 【关键修复】:更新坐标轴朝向以跟踪父节点的变化
parent_node = self.gizmoTarget.getParent()
if parent_node and parent_node != self.world.render:
# 子节点:坐标轴朝向跟随父节点
parent_hpr = parent_node.getHpr()
self.gizmo.setHpr(parent_hpr)
else:
# 顶级模型:使用世界坐标系朝向
self.gizmo.setHpr(0, 0, 0)
# 只在必要时更新位置和朝向
self._updateGizmoPositionAndOrientation()
# # 更新坐标轴位置,始终在目标节点中心
# minPoint = Point3()
# maxPoint = Point3()
# if self.gizmoTarget.calcTightBounds(minPoint, maxPoint, self.world.render):
# # 计算中心点
# center = Point3((minPoint.x + maxPoint.x) * 0.5,
# (minPoint.y + maxPoint.y) * 0.5,
# (minPoint.z + maxPoint.z) * 0.5)
# self.gizmo.setPos(center)
#
# # 【关键修复】:更新坐标轴朝向以跟踪父节点的变化
# parent_node = self.gizmoTarget.getParent()
# if parent_node and parent_node != self.world.render:
# # 子节点:坐标轴朝向跟随父节点
# parent_hpr = parent_node.getHpr()
# self.gizmo.setHpr(parent_hpr)
# else:
# # 顶级模型:使用世界坐标系朝向
# self.gizmo.setHpr(0, 0, 0)
# 【新功能】:动态调整坐标轴大小,保持固定的屏幕大小
self._updateGizmoScreenSize()
@ -642,6 +694,33 @@ class SelectionSystem:
print(f"坐标轴更新任务出错: {str(e)}")
return task.done
def _updateGizmoPositionAndOrientation(self):
"""优化的Gizmo位置和朝向更新"""
# 只在必要时重新计算边界框
if not hasattr(self, '_last_gizmo_bounds_update'):
self._last_gizmo_bounds_update = 0
import time
current_time = time.time()
if current_time - self._last_gizmo_bounds_update > 0.2: # 每0.2秒计算一次边界框
minPoint = Point3()
maxPoint = Point3()
if self.gizmoTarget.calcTightBounds(minPoint, maxPoint, self.world.render):
# 计算中心点
center = Point3((minPoint.x + maxPoint.x) * 0.5,
(minPoint.y + maxPoint.y) * 0.5,
(minPoint.z + maxPoint.z) * 0.5)
self.gizmo.setPos(center)
self._last_gizmo_bounds_update = current_time
# 更新朝向
parent_node = self.gizmoTarget.getParent()
if parent_node and parent_node != self.world.render:
parent_hpr = parent_node.getHpr()
self.gizmo.setHpr(parent_hpr)
else:
self.gizmo.setHpr(0, 0, 0)
def _updateGizmoScreenSize(self):
"""动态调整坐标轴大小,保持固定的屏幕大小"""
try:
@ -1203,33 +1282,23 @@ class SelectionSystem:
# 确定轴向量的变换上下文
if parent_node and parent_node != self.world.render:
# 子节点:使用父节点的局部坐标系
# print(f"子节点拖拽 - 父节点: {parent_node.getName()}, 父节点旋转: {parent_node.getHpr()}")
# transform_context = parent_node
transform_mat = parent_node.getMat(self.world.render)
world_axis_vector = transform_mat.xformVec(local_axis_vector)
else:
world_axis_vector = local_axis_vector
# 顶级模型:使用世界坐标系
# print(f"顶级模型拖拽 - 使用世界坐标系")
# transform_context = self.world.render
axis_end = gizmo_world_pos + world_axis_vector
#axis_end = gizmo_world_pos + world_axis_vector
# 投影到屏幕空间
def worldToScreen(worldPos):
try:
# 先转换为相机坐标系
camPos = self.world.cam.getRelativePoint(self.world.render, worldPos)
# 检查是否在相机前方
if camPos.getY() <= 0:
return None
screenPos = Point2()
if self.world.cam.node().getLens().project(camPos, screenPos):
# 获取准确的窗口尺寸
winWidth, winHeight = self.world.getWindowSize()
winX = (screenPos.x + 1) * 0.5 * winWidth
winY = (1 - screenPos.y) * 0.5 * winHeight
return (winX, winY)
@ -1237,28 +1306,45 @@ class SelectionSystem:
except Exception as e:
print(f"世界坐标转屏幕坐标失败: {e}")
return None
axis_start_screen = worldToScreen(gizmo_world_pos)
axis_end_world = gizmo_world_pos + world_axis_vector
axis_end_screen = worldToScreen(axis_end_world)
#gizmo_screen = worldToScreen(gizmo_world_pos)
#axis_screen = worldToScreen(axis_end)
gizmo_screen = worldToScreen(gizmo_world_pos)
axis_screen = worldToScreen(axis_end)
# if not gizmo_screen:
# print("拖拽更新失败: 坐标轴中心不在屏幕内")
# return
# if not axis_screen:
# print("拖拽更新失败: 坐标轴端点不在屏幕内")
# return
#
# # 计算轴在屏幕空间的方向向量
# screen_axis_dir = (
# axis_screen[0] - gizmo_screen[0],
# axis_screen[1] - gizmo_screen[1]
# )
if not gizmo_screen:
print("拖拽更新失败: 坐标轴中心不在屏幕内")
return
if not axis_screen:
print("拖拽更新失败: 坐标轴端点不在屏幕内")
if not axis_start_screen or not axis_end_screen:
print("拖拽更新失败: 无法获取轴线屏幕坐标")
return
# 计算轴在屏幕空间的方向向量
screen_axis_dir = (
axis_screen[0] - gizmo_screen[0],
axis_screen[1] - gizmo_screen[1]
axis_end_screen[0] - axis_start_screen[0],
axis_end_screen[1] - axis_start_screen[1]
)
# 归一化屏幕轴方向
import math
length = math.sqrt(screen_axis_dir[0]**2 + screen_axis_dir[1]**2)
if length > 0:
screen_axis_dir = (screen_axis_dir[0] / length, screen_axis_dir[1] / length)
#screen_axis_dir = (screen_axis_dir[0] / length, screen_axis_dir[1] / length)
screen_axis_dir = (
screen_axis_dir[0] / length,
screen_axis_dir[1] / length
)
else:
print("拖拽更新失败: 屏幕轴方向长度为0")
return
@ -1267,29 +1353,54 @@ class SelectionSystem:
projected_distance = (mouseDeltaX * screen_axis_dir[0] +
mouseDeltaY * screen_axis_dir[1])
scale_adjustment = 1.0
if parent_node and parent_node!= self.world.render:
current_node = parent_node
total_scale = 1.0
while current_node and current_node != self.world.render:
node_scale = current_node.getScale()
avg_scale = (node_scale.x+node_scale.y + node_scale.z)/3.0
total_scale *= avg_scale
current_node = current_node.getParent()
if total_scale>0:
scale_adjustment = 1.0 / total_scale
# parent_scale = parent_node.getScale()
# avg_scale = (parent_scale.x+parent_scale.y+parent_scale.z)/3.0
# if avg_scale>0:
# scale_adjustment = 1.0 / avg_scale
cam_pos = self.world.cam.getPos(self.world.render)
distance_to_object = (cam_pos - gizmo_world_pos).length()
lens = self.world.cam.node().getLens()
fov = lens.getFov()[0]
winWidth,winHeight = self.world.getWindowSize()
fixed_pixel_to_world_ratio = 0.01 # 1像素 = 0.01世界单位
scale_factor = fixed_pixel_to_world_ratio * scale_adjustment
pixels_to_world_units = (2*distance_to_object*math.tan(math.radians(fov/2)))/winWidth
movement_distance = projected_distance * pixels_to_world_units
total_scale_factor = 1.0
current_node = self.gizmoTarget.getParent()
while current_node and current_node != self.world.render:
node_scale = current_node.getScale()
avg_scale = (node_scale.x+node_scale.y + node_scale.z) / 3.0
total_scale_factor *= avg_scale
current_node = current_node.getParent()
if total_scale_factor > 0:
movement_distance = movement_distance / total_scale_factor
movement_distance = projected_distance * scale_factor
# 获取当前位置并只修改选中轴的坐标
currentPos = self.gizmoTargetStartPos
# scale_adjustment = 1.0
# if parent_node and parent_node!= self.world.render:
# current_node = parent_node
# total_scale = 1.0
# while current_node and current_node != self.world.render:
# node_scale = current_node.getScale()
# avg_scale = (node_scale.x+node_scale.y + node_scale.z)/3.0
# total_scale *= avg_scale
# current_node = current_node.getParent()
# if total_scale>0:
# scale_adjustment = 1.0 / total_scale
# # parent_scale = parent_node.getScale()
# # avg_scale = (parent_scale.x+parent_scale.y+parent_scale.z)/3.0
# # if avg_scale>0:
# # scale_adjustment = 1.0 / avg_scale
#
#
# fixed_pixel_to_world_ratio = 0.01 # 1像素 = 0.01世界单位
# scale_factor = fixed_pixel_to_world_ratio * scale_adjustment
#
# movement_distance = projected_distance * scale_factor
# # 获取当前位置并只修改选中轴的坐标
# currentPos = self.gizmoTargetStartPos
# 根据拖拽的轴,只修改对应的坐标分量
if self.dragGizmoAxis == "x":
@ -1332,7 +1443,7 @@ class SelectionSystem:
import time
current_time = time.time()
if current_time - self._last_drag_debug_time > 0.1: # 每0.1秒最多输出一次
print(f"拖拽更新成功 - 轴:{self.dragGizmoAxis}, 比例:{scale_factor:.6f}, 投影:{projected_distance:.2f}")
#print(f"拖拽更新成功 - 轴:{self.dragGizmoAxis}, 比例:{scale_factor:.6f}, 投影:{projected_distance:.2f}")
self._last_drag_debug_time = current_time
except Exception as e:

View File

@ -21,25 +21,6 @@ class PropertyPanelManager:
self._propertyLayout = None
self._actor_cache={}
# 定义紧凑样式
self.compact_style = """
QDoubleSpinBox {
min-width: 45px;
}
QPushButton {
min-width: 10px;
}
QComboBox {
min-width: 60px;
}
QLineEdit {
min-width: 60px;
}
QCheckBox {
min-width: 20px;
}
"""
def setPropertyLayout(self, layout):
"""设置属性面板布局引用"""
print("开始设置属性布局")
@ -77,10 +58,6 @@ class PropertyPanelManager:
self.clearPropertyPanel()
# 应用紧凑样式到属性面板容器
if self._propertyLayout.parent():
self._propertyLayout.parent().setStyleSheet(self.compact_style)
itemText = item.text(0)
# 如果点击的是场景根节点,显示提示信息
@ -215,9 +192,24 @@ class PropertyPanelManager:
self.pos_z.setValue(relativePos.getZ())
# 连接位置变化事件
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))
# 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))
def updateXPosition(value):
model.setX(value)
self.refreshModelValues(model)
self.pos_x.valueChanged.connect(updateXPosition)
def updateYPosition(value):
model.setY(value)
self.refreshModelValues(model)
self.pos_y.valueChanged.connect(updateYPosition)
def updateZPosition(value):
model.setZ(value)
self.refreshModelValues(model)
self.pos_z.valueChanged.connect(updateZPosition)
# 创建并设置 X, Y, Z 标签居中
x_label1 = QLabel("X")
@ -332,6 +324,50 @@ class PropertyPanelManager:
# 材质属性组
self._updateModelMaterialPanel(model)
def refreshModelValues(self,nodePath):
if not nodePath or self._propertyLayout is None:
return
parent = nodePath.getParent()
render = self.world.render
relPos = nodePath.getPos(parent) if parent else nodePath.getPos()
if hasattr(self,'pos_x') and self.pos_x:
self.pos_x.blockSignals(True)
self.pos_x.setValue(relPos.getX())
self.pos_x.blockSignals(False)
if hasattr(self,'pos_y') and self.pos_y:
self.pos_y.blockSignals(True)
self.pos_y.setValue(relPos.getY())
self.pos_y.blockSignals(False)
if hasattr(self,'pos_z') and self.pos_z:
self.pos_z.blockSignals(True)
self.pos_z.setValue(relPos.getZ())
self.pos_z.blockSignals(False)
worldPos = nodePath.getPos(render)
for axis,attr in zip(('x','y','z'),('world_pos_x','world_pos_y','world_pos_z')):
spin = getattr(self,attr,None)
if spin:
spin.blockSignals(True)
spin.setValue(getattr(worldPos,axis))
spin.blockSignals(False)
hpr = nodePath.getHpr()
for idx,(attr,val) in enumerate(zip(('rot_h','rot_p','rot_r'),hpr)):
spin = getattr(self,attr,None)
if spin:
spin.blockSignals(True)
spin.setValue(val)
spin.blockSignals(False)
scale = nodePath.getScale()
for axis,attr in zip(('x','y','z'),('scale_x','scale_y','scale_z')):
spin = getattr(self,attr,None)
if spin:
spin.blockSignals(True)
spin.setValue(getattr(scale,axis))
spin.blockSignals(False)
def updateGUIPropertyPanel(self, gui_element):
"""更新GUI元素属性面板"""
@ -3892,7 +3928,6 @@ class PropertyPanelManager:
if preset_name in presets:
azimuth, altitude = presets[preset_name]
# 更新滑块和数值框
# 更新滑块和数值框
self.azimuthSpinBox.blockSignals(True)
self.altitudeSpinBox.blockSignals(True)
@ -4962,20 +4997,18 @@ except Exception as e:
# 如果有多种类型的动画,使用标签页
if len(animations) > 1:
tab_widget = QTabWidget()
tab_widget.setMinimumWidth(200) # 设置最小宽度
for anim_type, anim_data in animations.items():
tab = QWidget()
tab_layout = QGridLayout(tab) # 改为QGridLayout保持一致
tab_layout = QVBoxLayout(tab)
self._buildAnimationTypeUI(tab_layout, origin_model, anim_type, anim_data)
tab_widget.addTab(tab, self._getAnimTypeDisplayName(anim_type))
layout.addWidget(QLabel("动画类型:"), 1, 0)
layout.addWidget(tab_widget, 1, 1, 1, 3)
self._propertyLayout.addRow("动画类型:", tab_widget)
else:
# 只有一种类型,直接显示
anim_type, anim_data = next(iter(animations.items()))
self._buildAnimationTypeUI(layout, origin_model, anim_type, anim_data)
self._buildAnimationTypeUI(self._propertyLayout, origin_model, anim_type, anim_data)
# 存储动画信息供控制使用
if not hasattr(self, '_non_skeletal_cache'):
@ -4986,69 +5019,46 @@ except Exception as e:
"""为特定动画类型构建UI"""
from PyQt5.QtWidgets import QLabel, QComboBox, QHBoxLayout, QWidget, QPushButton, QDoubleSpinBox
current_row = layout.rowCount()
if anim_type == 'transform':
# 变换动画控制
self.ns_transform_combo = QComboBox()
self.ns_transform_combo.addItems(anim_data['names'])
self.ns_transform_combo.setMinimumWidth(80)
layout.addWidget(QLabel("变换动画:"), current_row, 0)
layout.addWidget(self.ns_transform_combo, current_row, 1, 1, 3)
current_row += 1
layout.addRow("变换动画:", self.ns_transform_combo)
elif anim_type == 'texture':
# 纹理动画控制
self.ns_texture_combo = QComboBox()
self.ns_texture_combo.addItems(anim_data['stages'])
self.ns_texture_combo.setMinimumWidth(80)
layout.addWidget(QLabel("纹理动画:"), current_row, 0)
layout.addWidget(self.ns_texture_combo, current_row, 1, 1, 3)
current_row += 1
layout.addRow("纹理动画:", self.ns_texture_combo)
elif anim_type == 'material':
# 材质动画控制
self.ns_material_combo = QComboBox()
self.ns_material_combo.addItems(anim_data['properties'])
self.ns_material_combo.setMinimumWidth(80)
layout.addWidget(QLabel("材质动画:"), current_row, 0)
layout.addWidget(self.ns_material_combo, current_row, 1, 1, 3)
current_row += 1
layout.addRow("材质动画:", self.ns_material_combo)
elif anim_type == 'lerp':
# Lerp动画控制
self.ns_lerp_combo = QComboBox()
self.ns_lerp_combo.addItems(anim_data['intervals'])
self.ns_lerp_combo.setMinimumWidth(80)
layout.addWidget(QLabel("Lerp动画"), current_row, 0)
layout.addWidget(self.ns_lerp_combo, current_row, 1, 1, 3)
current_row += 1
layout.addRow("Lerp动画", self.ns_lerp_combo)
# 通用控制按钮
btn_box = QWidget()
btn_lay = QHBoxLayout(btn_box)
btn_lay.setContentsMargins(0, 0, 0, 0)
for txt, cmd in (("播放", "play"), ("暂停", "pause"), ("停止", "stop"), ("循环", "loop")):
btn = QPushButton(txt)
btn.setMinimumWidth(35) # 设置按钮最小宽度
btn.setMaximumWidth(50) # 限制按钮最大宽度
btn.clicked.connect(lambda _, c=cmd, t=anim_type: self._controlNonSkeletalAnimation(origin_model, t, c))
btn_lay.addWidget(btn)
layout.addWidget(QLabel("控制:"), current_row, 0)
layout.addWidget(btn_box, current_row, 1, 1, 3)
current_row += 1
layout.addRow("控制:", btn_box)
# 播放速度
speed_spinbox = QDoubleSpinBox()
speed_spinbox.setRange(0.1, 5.0)
speed_spinbox.setSingleStep(0.1)
speed_spinbox.setValue(1.0)
speed_spinbox.setMinimumWidth(60)
speed_spinbox.setMaximumWidth(80)
speed_spinbox.valueChanged.connect(
lambda v, t=anim_type: self._setNonSkeletalAnimationSpeed(origin_model, t, v))
layout.addWidget(QLabel("播放速度:"), current_row, 0)
layout.addWidget(speed_spinbox, current_row, 1, 1, 3)
speed_spinbox.valueChanged.connect(lambda v, t=anim_type: self._setNonSkeletalAnimationSpeed(origin_model, t, v))
layout.addRow("播放速度:", speed_spinbox)
def _getAnimTypeDisplayName(self, anim_type):
"""获取动画类型的显示名称"""