Merge remote-tracking branch 'origin/main_ch_eg' into main_ch_eg
# Conflicts: # RenderPipelineFile/config/daytime.yaml # ui/property_panel.py
This commit is contained in:
commit
0319b13f4a
File diff suppressed because one or more lines are too long
BIN
core/TranslateArrowHandle.fbx
Executable file
BIN
core/TranslateArrowHandle.fbx
Executable file
Binary file not shown.
@ -11,7 +11,7 @@ from PIL.ImageChops import lighter
|
||||
from panda3d.core import (Vec3, Point3, Point2, LineSegs, ColorAttrib, RenderState,
|
||||
DepthTestAttrib, CollisionTraverser, CollisionHandlerQueue,
|
||||
CollisionNode, CollisionRay, GeomNode, BitMask32, Material, LColor, DepthWriteAttrib,
|
||||
TransparencyAttrib)
|
||||
TransparencyAttrib, Vec4)
|
||||
from direct.task.TaskManagerGlobal import taskMgr
|
||||
import math
|
||||
|
||||
@ -50,14 +50,14 @@ class SelectionSystem:
|
||||
# 高亮相关
|
||||
self.gizmoHighlightAxis = None
|
||||
self.gizmo_colors = {
|
||||
"x": (1*10, 0, 0, 1), # 红色
|
||||
"y": (0, 1*10, 0, 1), # 绿色
|
||||
"z": (0, 0, 1*10, 1) # 蓝色
|
||||
"x": (1, 0, 0, 0), # 红色
|
||||
"y": (0, 1, 0, 0), # 绿色
|
||||
"z": (0, 0, 1, 0) # 蓝色
|
||||
}
|
||||
self.gizmo_highlight_colors = {
|
||||
"x": (1.5*20, 1.5*20, 0, 1), # 黄色高亮
|
||||
"y": (1.5*20, 1.5*20, 0, 1), # 黄色高亮
|
||||
"z": (1.5*20, 1.5*20, 0, 1) # 黄色高亮
|
||||
"x": (1, 1, 0, 0), # 黄色高亮
|
||||
"y": (1, 1, 0, 0), # 黄色高亮
|
||||
"z": (1, 1, 0, 0) # 黄色高亮
|
||||
}
|
||||
|
||||
print("✓ 选择和变换系统初始化完成")
|
||||
@ -217,6 +217,9 @@ class SelectionSystem:
|
||||
return task.cont
|
||||
self._last_selection_box_update = current_time
|
||||
|
||||
#检查目标节点是否已被删除
|
||||
self.checkAndClearIfTargetDeleted()
|
||||
|
||||
if not self.selectionBox or not self.selectionBoxTarget:
|
||||
return task.done # 结束任务
|
||||
|
||||
@ -300,11 +303,18 @@ class SelectionSystem:
|
||||
# 只调用一次几何体创建
|
||||
self.createGizmoGeometry()
|
||||
|
||||
# 只调用一次颜色设置
|
||||
#只调用一次颜色设置
|
||||
self.setGizmoAxisColor("x", self.gizmo_colors["x"])
|
||||
self.setGizmoAxisColor("y", self.gizmo_colors["y"])
|
||||
self.setGizmoAxisColor("z", self.gizmo_colors["z"])
|
||||
|
||||
self._updateGizmoScreenSize()
|
||||
|
||||
self._setupGizmoRendering()
|
||||
|
||||
# 现在才显示坐标轴
|
||||
self.gizmo.show()
|
||||
|
||||
# 只启动一次更新任务
|
||||
taskMgr.add(self.updateGizmoTask, "updateGizmo")
|
||||
|
||||
@ -319,321 +329,100 @@ class SelectionSystem:
|
||||
if not self.gizmo:
|
||||
return
|
||||
|
||||
# 创建X轴(红色)
|
||||
x_lines = LineSegs("x_axis")
|
||||
x_lines.setThickness(6.0)
|
||||
x_lines.moveTo(0, 0, 0)
|
||||
x_lines.drawTo(self.axis_length, 0, 0)
|
||||
# 创建X轴箭头
|
||||
x_lines.moveTo(self.axis_length - 0.5, -0.2, 0)
|
||||
x_lines.drawTo(self.axis_length, 0, 0)
|
||||
x_lines.drawTo(self.axis_length - 0.5, 0.2, 0)
|
||||
x_geom = x_lines.create()
|
||||
self.gizmoXAxis = self.gizmo.attachNewNode(x_geom)
|
||||
self.gizmoXAxis.setName("gizmo_x_axis")
|
||||
#self.gizmoXAxis.setLightOff()
|
||||
# 创建Y轴(绿色)
|
||||
y_lines = LineSegs("y_axis")
|
||||
y_lines.setThickness(6.0)
|
||||
y_lines.moveTo(0, 0, 0)
|
||||
y_lines.drawTo(0, self.axis_length, 0)
|
||||
# 创建Y轴箭头
|
||||
y_lines.moveTo(-0.2, self.axis_length - 0.5, 0)
|
||||
y_lines.drawTo(0, self.axis_length, 0)
|
||||
y_lines.drawTo(0.2, self.axis_length - 0.5, 0)
|
||||
y_geom = y_lines.create()
|
||||
self.gizmoYAxis = self.gizmo.attachNewNode(y_geom)
|
||||
self.gizmoYAxis.setName("gizmo_y_axis")
|
||||
#self.gizmoYAxis.setLightOff()
|
||||
model_paths = [
|
||||
"core/TranslateArrowHandle.fbx",
|
||||
"EG/core/TranslateArrowHandle.fbx",
|
||||
]
|
||||
arrow_model = None
|
||||
for path in model_paths:
|
||||
try:
|
||||
arrow_model = self.world.loader.loadModel(path)
|
||||
if arrow_model:
|
||||
print(f"成功加载模型: {path}")
|
||||
break
|
||||
except:
|
||||
continue
|
||||
self.gizmoXAxis = self.gizmo.attachNewNode("gizmo_x_axis")
|
||||
x_arrow = arrow_model.copyTo(self.gizmoXAxis)
|
||||
x_arrow.setName("x_arrow")
|
||||
x_arrow.setHpr(0,-90,0)
|
||||
x_arrow.setScale(0.1,0.05,0.05)
|
||||
x_arrow.setPos(0,0,0)
|
||||
|
||||
self.gizmoYAxis = self.gizmo.attachNewNode("gizmo_y_axis")
|
||||
y_arrow = arrow_model.copyTo(self.gizmoYAxis)
|
||||
y_arrow.setName("y_arrow")
|
||||
y_arrow.setHpr(90,0,0)
|
||||
y_arrow.setScale(0.1,0.05,0.05)
|
||||
y_arrow.setPos(0,0,0)
|
||||
|
||||
# 创建Z轴(蓝色)
|
||||
z_lines = LineSegs("z_axis")
|
||||
z_lines.setThickness(6.0)
|
||||
z_lines.moveTo(0, 0, 0)
|
||||
z_lines.drawTo(0, 0, self.axis_length)
|
||||
# 创建Z轴箭头
|
||||
z_lines.moveTo(-0.2, 0, self.axis_length - 0.5)
|
||||
z_lines.drawTo(0, 0, self.axis_length)
|
||||
z_lines.drawTo(0.2, 0, self.axis_length - 0.5)
|
||||
z_geom = z_lines.create()
|
||||
self.gizmoZAxis = self.gizmo.attachNewNode(z_geom)
|
||||
self.gizmoZAxis.setName("gizmo_z_axis")
|
||||
#self.gizmoZAxis.setLightOff()
|
||||
self.gizmoZAxis = self.gizmo.attachNewNode("gizmo_z_axis")
|
||||
z_arrow = arrow_model.copyTo(self.gizmoZAxis)
|
||||
z_arrow.setName("z_arrow")
|
||||
# 旋转箭头使其指向Z轴正方向
|
||||
z_arrow.setHpr(0, 0, -90) # 根据需要调整旋转
|
||||
z_arrow.setScale(0.1,0.05,0.05)
|
||||
z_arrow.setPos(0, 0, 0)
|
||||
|
||||
# 确保坐标轴不被光照影响
|
||||
#self.gizmo.setLightOff()
|
||||
|
||||
# 使用最强的渲染设置,确保坐标轴绝对不会被遮挡
|
||||
self.gizmo.setBin("gui-popup", 0) # 使用最高的GUI渲染层
|
||||
self.gizmo.setDepthTest(False) # 完全禁用深度测试
|
||||
self.gizmo.setDepthWrite(False) # 禁用深度写入
|
||||
self.gizmo.setTwoSided(True) # 双面渲染
|
||||
|
||||
# 创建强制前景渲染状态
|
||||
from panda3d.core import RenderModeAttrib, TransparencyAttrib
|
||||
foreground_state = RenderState.make(
|
||||
DepthTestAttrib.make(DepthTestAttrib.MNone), # 完全不进行深度测试
|
||||
TransparencyAttrib.make(TransparencyAttrib.MAlpha) # 启用透明度混合
|
||||
)
|
||||
#self.gizmo.setState(foreground_state)
|
||||
|
||||
# 对每个坐标轴设置独立的最高渲染优先级
|
||||
self.gizmoXAxis.setBin("gui-popup", 10)
|
||||
self.gizmoXAxis.setDepthTest(False)
|
||||
self.gizmoXAxis.setDepthWrite(False)
|
||||
self.gizmoXAxis.setLightOff()
|
||||
self.gizmoXAxis.setState(foreground_state)
|
||||
|
||||
self.gizmoYAxis.setBin("gui-popup", 20)
|
||||
self.gizmoYAxis.setDepthTest(False)
|
||||
self.gizmoYAxis.setDepthWrite(False)
|
||||
self.gizmoYAxis.setLightOff()
|
||||
self.gizmoYAxis.setState(foreground_state)
|
||||
|
||||
self.gizmoZAxis.setBin("gui-popup", 30)
|
||||
self.gizmoZAxis.setDepthTest(False)
|
||||
self.gizmoZAxis.setDepthWrite(False)
|
||||
self.gizmoZAxis.setLightOff()
|
||||
self.gizmoZAxis.setState(foreground_state)
|
||||
|
||||
|
||||
|
||||
# 初始化高亮状态
|
||||
self.gizmoHighlightAxis = None
|
||||
|
||||
# 立即设置初始颜色,确保创建时就有正确的颜色
|
||||
self.setGizmoAxisColor("z", self.gizmo_colors["z"])
|
||||
# 设置初始颜色
|
||||
self.setGizmoAxisColor("x", self.gizmo_colors["x"])
|
||||
self.setGizmoAxisColor("y", self.gizmo_colors["y"])
|
||||
self.setGizmoAxisColor("z", self.gizmo_colors["z"])
|
||||
|
||||
|
||||
print(f"✓ 坐标轴几何体创建完成,长度={self.axis_length}")
|
||||
|
||||
# 为 RenderPipeline 环境设置正确的渲染状态
|
||||
self._setupRenderPipelineCompatibleGizmo()
|
||||
|
||||
self._setupEmissiveMaterials()
|
||||
#设置渲染属性,解决模型遮挡和阴影问题
|
||||
self._setupGizmoRendering()
|
||||
|
||||
except Exception as e:
|
||||
print(f"创建坐标轴几何体失败: {str(e)}")
|
||||
|
||||
def _setupEmissiveMaterials(self):
|
||||
try:
|
||||
from panda3d.core import Material,Vec4
|
||||
materials ={
|
||||
"x":(Vec4(1,0,0,1),Vec4(2.0,0,0,1)),
|
||||
"y":(Vec4(0,1,0,1),Vec4(0,2.0,0,1)),
|
||||
"z":(Vec4(0,0,1,1),Vec4(0,0,2.0,1))
|
||||
}
|
||||
axis_nodes ={
|
||||
"x":self.gizmoXAxis,
|
||||
"y":self.gizmoYAxis,
|
||||
"z":self.gizmoZAxis
|
||||
}
|
||||
for axis,(base_color,emission_color) in materials.items():
|
||||
if axis_nodes[axis]:
|
||||
material=Material(f"gizmo_{axis}_material")
|
||||
material.setBaseColor(base_color)
|
||||
material.setEmission(emission_color)
|
||||
material.setRoughness(1.0)
|
||||
material.setMetallic(0.0)
|
||||
axis_nodes[axis].setMaterial(material)
|
||||
except Exception as e:
|
||||
print(f"自发光材质设置失败: {str(e)}")
|
||||
|
||||
def _setupGizmoRendering(self):
|
||||
"""设置坐标轴渲染属性"""
|
||||
try:
|
||||
# 设置渲染优先级,确保在最前面显示
|
||||
self.gizmo.setBin("gui-popup", 1000)
|
||||
self.gizmo.setDepthTest(False)
|
||||
self.gizmo.setDepthWrite(False)
|
||||
self.gizmo.setLightOff()
|
||||
axis_nodes = [self.gizmoXAxis,self.gizmoYAxis,self.gizmoZAxis]
|
||||
|
||||
# 为每个轴设置独立的渲染属性
|
||||
for i, axis_node in enumerate([self.gizmoXAxis, self.gizmoYAxis, self.gizmoZAxis]):
|
||||
for axis_node in axis_nodes:
|
||||
if axis_node:
|
||||
axis_node.setBin("gui-popup", 1001 + i)
|
||||
axis_node.setDepthTest(False)
|
||||
axis_node.setDepthWrite(False)
|
||||
axis_node.setLightOff()
|
||||
|
||||
except Exception as e:
|
||||
print(f"设置坐标轴渲染失败!!!!!!: {str(e)}")
|
||||
|
||||
def _setupRenderPipelineCompatibleGizmo(self):
|
||||
"""为 RenderPipeline 环境设置兼容的坐标轴渲染 - 激进修复版本"""
|
||||
try:
|
||||
from panda3d.core import (ShaderAttrib, MaterialAttrib, RenderModeAttrib,
|
||||
AntialiasAttrib, TransparencyAttrib, CullFaceAttrib,
|
||||
RescaleNormalAttrib, TextureAttrib)
|
||||
|
||||
# 第一步:完全隔离坐标轴,避免被任何系统处理
|
||||
self._isolateGizmoFromRenderPipeline()
|
||||
|
||||
# 第二步:使用最简单的渲染方式
|
||||
self._setupMinimalGizmoRendering()
|
||||
|
||||
# 第三步:强制设置每个轴的独立渲染
|
||||
self._forceAxisIndependentRendering()
|
||||
|
||||
|
||||
except Exception as e:
|
||||
# 使用最后的备用方案
|
||||
self._setupUltimateGizmoFallback()
|
||||
|
||||
def _isolateGizmoFromRenderPipeline(self):
|
||||
"""完全隔离坐标轴,避免被 RenderPipeline 处理"""
|
||||
try:
|
||||
# 设置所有可能的隔离标签
|
||||
isolation_tags = [
|
||||
("no_shadow", "1"),
|
||||
("no_lighting", "1"),
|
||||
("no_material", "1"),
|
||||
("no_shader", "1"),
|
||||
("no_fog", "1"),
|
||||
("no_texture", "1"),
|
||||
("gui_element", "1"),
|
||||
("bypass_rp", "1"),
|
||||
("fixed_pipeline", "1"),
|
||||
("ignore_all", "1")
|
||||
]
|
||||
|
||||
for tag, value in isolation_tags:
|
||||
self.gizmo.setTag(tag, value)
|
||||
|
||||
# 完全禁用所有高级功能,使用最高优先级
|
||||
self.gizmo.setShaderOff(10000)
|
||||
self.gizmo.setLightOff(10000)
|
||||
self.gizmo.setFogOff(10000)
|
||||
self.gizmo.setMaterialOff(10000)
|
||||
self.gizmo.setTextureOff(10000)
|
||||
|
||||
# 禁用所有可能的渲染特性
|
||||
from panda3d.core import RenderModeAttrib, CullFaceAttrib
|
||||
# self.gizmo.setRenderModeWireframe()
|
||||
# self.gizmo.setTwoSided(True)
|
||||
self.gizmo.setRenderMode(RenderModeAttrib.MFilled)
|
||||
self.gizmo.setTwoSided(True)
|
||||
|
||||
self.gizmo.setColorScale(2.0,2.0,2.0,1.0)
|
||||
for axis_node in [self.gizmoXAxis,self.gizmoYAxis,self.gizmoZAxis]:
|
||||
if axis_node:
|
||||
axis_node.setTag("emissive","1")
|
||||
axis_node.setTag("unlit","1")
|
||||
axis_node.setColorScale(2.0,2.0,2.0,1.0)
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 隔离失败: {e}")
|
||||
|
||||
def _setupMinimalGizmoRendering(self):
|
||||
"""设置最简单的渲染方式"""
|
||||
try:
|
||||
from panda3d.core import (ShaderAttrib, MaterialAttrib, TextureAttrib,
|
||||
CullFaceAttrib, RescaleNormalAttrib)
|
||||
|
||||
# 使用最高优先级的 GUI 渲染 bin
|
||||
self.gizmo.setBin("gui-popup", 10000)
|
||||
self.gizmo.setDepthTest(False)
|
||||
self.gizmo.setDepthWrite(False)
|
||||
|
||||
# 创建最简单的渲染状态
|
||||
minimal_state = RenderState.make(
|
||||
ShaderAttrib.makeOff(10000), # 完全禁用着色器
|
||||
MaterialAttrib.makeOff(), # 禁用材质
|
||||
TextureAttrib.makeOff(), # 禁用纹理
|
||||
CullFaceAttrib.make(CullFaceAttrib.MCullNone), # 禁用面剔除
|
||||
RescaleNormalAttrib.makeOff() # 禁用法线重缩放
|
||||
)
|
||||
self.gizmo.setState(minimal_state, 10000)
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 最简渲染设置失败: {e}")
|
||||
|
||||
def _forceAxisIndependentRendering(self):
|
||||
"""强制设置每个轴的独立渲染"""
|
||||
try:
|
||||
# 轴配置
|
||||
axis_configs = [
|
||||
(self.gizmoXAxis, "X轴", (1.0, 0.0, 0.0, 1.0), 0),
|
||||
(self.gizmoYAxis, "Y轴", (0.0, 1.0, 0.0, 1.0), 0),
|
||||
(self.gizmoZAxis, "Z轴", (0.0, 0.0, 1.0, 1.0), 0)
|
||||
]
|
||||
|
||||
for axis_node, name, color, priority in axis_configs:
|
||||
if axis_node:
|
||||
# 每个轴都完全独立设置
|
||||
self._setupSingleAxisRendering(axis_node, name, color, 0)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 独立轴渲染设置失败: {e}")
|
||||
|
||||
def _setupSingleAxisRendering(self, axis_node, name, color, priority):
|
||||
"""为单个轴设置完全独立的渲染"""
|
||||
try:
|
||||
from panda3d.core import (LVecBase4f, RenderState, ColorAttrib,
|
||||
TransparencyAttrib, LColor, AntialiasAttrib,
|
||||
RenderModeAttrib, CullFaceAttrib, AuxBitplaneAttrib,
|
||||
LightRampAttrib)
|
||||
|
||||
# 转换颜色为LColor并增加亮度
|
||||
base_color = LColor(*color)
|
||||
emissive_color = LColor(base_color[0], base_color[1], base_color[2], 1.0)
|
||||
|
||||
# 完全禁用所有高级渲染功能
|
||||
axis_node.clearShader()
|
||||
axis_node.clearTexture()
|
||||
axis_node.clearMaterial()
|
||||
#禁用光照和阴影
|
||||
axis_node.setLightOff()
|
||||
axis_node.setShaderOff()
|
||||
axis_node.setFogOff()
|
||||
axis_node.setAttrib(RenderModeAttrib.make(RenderModeAttrib.MWireframe, 6.0))
|
||||
axis_node.setAttrib(AntialiasAttrib.make(AntialiasAttrib.MLine))
|
||||
axis_node.setBin("gui-popup", 0)
|
||||
axis_node.setDepthTest(False)
|
||||
#设置渲染层级,确保大多数对象之前渲染
|
||||
axis_node.setBin("fixed",30)
|
||||
axis_node.setDepthWrite(False)
|
||||
axis_node.setTwoSided(True)
|
||||
|
||||
# 强制设置自发光颜色
|
||||
axis_node.setColor(*color)
|
||||
axis_node.setColorScale(1.0, 1.0, 1.0, 1.0) # 增加整体亮度
|
||||
|
||||
except Exception as e:
|
||||
print(" ❌ {} 轴渲染设置失败: {}".format(name, str(e)))
|
||||
raise e
|
||||
|
||||
def _setupUltimateGizmoFallback(self):
|
||||
"""最后的备用方案 - 最简单的渲染"""
|
||||
try:
|
||||
# 最简单的设置
|
||||
self.gizmo.setLightOff()
|
||||
self.gizmo.setFogOff()
|
||||
self.gizmo.setBin("gui-popup", 20000)
|
||||
self.gizmo.setDepthTest(False)
|
||||
self.gizmo.setDepthWrite(False)
|
||||
|
||||
# 直接设置颜色,不使用复杂的渲染状态
|
||||
axis_node.setDepthTest(False)
|
||||
arrow_nodes = []
|
||||
if self.gizmoXAxis:
|
||||
self.gizmoXAxis.setColor(1, 0, 0, 1)
|
||||
self.gizmoXAxis.setLightOff()
|
||||
self.gizmoXAxis.setBin("gui-popup", 20001)
|
||||
self.gizmoXAxis.setDepthTest(False)
|
||||
|
||||
x_arrow = self.gizmoXAxis.find("x_arrow")
|
||||
if x_arrow:
|
||||
arrow_nodes.append(x_arrow)
|
||||
if self.gizmoYAxis:
|
||||
self.gizmoYAxis.setColor(0, 1, 0, 1)
|
||||
self.gizmoYAxis.setLightOff()
|
||||
self.gizmoYAxis.setBin("gui-popup", 20002)
|
||||
self.gizmoYAxis.setDepthTest(False)
|
||||
|
||||
y_arrow = self.gizmoYAxis.find("y_arrow")
|
||||
if y_arrow:
|
||||
arrow_nodes.append(y_arrow)
|
||||
if self.gizmoZAxis:
|
||||
self.gizmoZAxis.setColor(0, 0, 1, 1)
|
||||
self.gizmoZAxis.setLightOff()
|
||||
self.gizmoZAxis.setBin("gui-popup", 20003)
|
||||
self.gizmoZAxis.setDepthTest(False)
|
||||
z_arrow = self.gizmoZAxis.find("z_arrow")
|
||||
if z_arrow:
|
||||
arrow_nodes.append(z_arrow)
|
||||
|
||||
for arrow_node in arrow_nodes:
|
||||
if arrow_node:
|
||||
arrow_node.setLightOff()
|
||||
arrow_node.setShaderOff()
|
||||
arrow_node.setFogOff()
|
||||
arrow_node.setBin("fixed",31)
|
||||
arrow_node.setDepthWrite(False)
|
||||
arrow_node.setDepthTest(False)
|
||||
#启用透明度S
|
||||
arrow_node.setTransparency(TransparencyAttrib.MAlpha)
|
||||
if self.gizmo:
|
||||
self.gizmo.setLightOff()
|
||||
self.gizmo.setShaderOff()
|
||||
self.gizmo.setFogOff()
|
||||
self.gizmo.setBin("fixed",29)
|
||||
self.gizmo.setDepthWrite(False)
|
||||
self.gizmo.setDepthTest(False)
|
||||
except Exception as e:
|
||||
print(f"❌ 最后备用方案也失败: {e}")
|
||||
print(f"设置坐标轴渲染属性失败: {str(e)}")
|
||||
|
||||
def updateGizmoTask(self, task):
|
||||
"""坐标轴更新任务 - 包含固定大小功能"""
|
||||
@ -648,6 +437,9 @@ class SelectionSystem:
|
||||
return task.cont
|
||||
self._last_gizmo_update = current_time
|
||||
|
||||
#检查目标节点是否已被删除
|
||||
self.checkAndClearIfTargetDeleted()
|
||||
|
||||
if not self.gizmo or not self.gizmoTarget:
|
||||
return task.done
|
||||
|
||||
@ -665,25 +457,6 @@ class SelectionSystem:
|
||||
else:
|
||||
# 只在必要时更新位置和朝向
|
||||
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()
|
||||
@ -753,8 +526,8 @@ class SelectionSystem:
|
||||
self.gizmo.setScale(scale_factor)
|
||||
|
||||
# 限制缩放范围,避免过大或过小
|
||||
min_scale = 0.1
|
||||
max_scale = 10.0
|
||||
min_scale = 0.08
|
||||
max_scale = 100.0
|
||||
final_scale = max(min_scale, min(max_scale, scale_factor))
|
||||
|
||||
if final_scale != scale_factor:
|
||||
@ -785,11 +558,114 @@ class SelectionSystem:
|
||||
self.gizmoStartPos = None
|
||||
|
||||
|
||||
def setGizmoAxisColor(self, axis, color):
|
||||
"""设置坐标轴颜色 - RenderPipeline 兼容版本"""
|
||||
try:
|
||||
from panda3d.core import AntialiasAttrib,TransparencyAttrib
|
||||
# def setGizmoAxisColor(self, axis, color):
|
||||
# """设置坐标轴颜色 - RenderPipeline 兼容版本"""
|
||||
# try:
|
||||
# from panda3d.core import AntialiasAttrib,TransparencyAttrib
|
||||
#
|
||||
# axis_nodes = {
|
||||
# "x": self.gizmoXAxis,
|
||||
# "y": self.gizmoYAxis,
|
||||
# "z": self.gizmoZAxis
|
||||
# }
|
||||
#
|
||||
# if axis in axis_nodes and axis_nodes[axis]:
|
||||
# axis_node = axis_nodes[axis]
|
||||
#
|
||||
# axis_node.setColor(color[0]*20.0,color[1]*20.0,color[2]*20.0,color[3])
|
||||
# axis_node.setColorScale(color[0]*10.0,color[1]*10.0,color[2]*10.0,color[3])
|
||||
# axis_node.setShaderOff(10000)
|
||||
# axis_node.setLightOff()
|
||||
# axis_node.setMaterialOff()
|
||||
# axis_node.setTextureOff()
|
||||
# axis_node.setFogOff()
|
||||
#
|
||||
# except Exception as e:
|
||||
# print(f"设置坐标轴颜色失败: {str(e)}")
|
||||
# # 回退到简单的颜色设置
|
||||
# try:
|
||||
# if axis in axis_nodes and axis_nodes[axis]:
|
||||
# axis_nodes[axis].setColor(*color)
|
||||
# except:
|
||||
# pass
|
||||
|
||||
def setGizmoAxisColor(self, axis, color):
|
||||
"""使用材质设置坐标轴颜色 - RenderPipeline兼容版本"""
|
||||
try:
|
||||
from panda3d.core import Material, Vec4,ColorWriteAttrib,DepthWriteAttrib,DepthTestAttrib,TransparencyAttrib
|
||||
|
||||
# 获取对应的轴节点
|
||||
axis_nodes = {
|
||||
"x": self.gizmoXAxis,
|
||||
"y": self.gizmoYAxis,
|
||||
"z": self.gizmoZAxis
|
||||
}
|
||||
|
||||
if axis not in axis_nodes or not axis_nodes[axis]:
|
||||
return
|
||||
|
||||
axis_node = axis_nodes[axis]
|
||||
|
||||
# 查找箭头模型节点
|
||||
arrow_node = None
|
||||
if axis == "x":
|
||||
arrow_node = axis_node.find("x_arrow")
|
||||
elif axis == "y":
|
||||
arrow_node = axis_node.find("y_arrow")
|
||||
elif axis == "z":
|
||||
arrow_node = axis_node.find("z_arrow")
|
||||
|
||||
if not arrow_node:
|
||||
print(f"未找到{axis}轴的箭头模型")
|
||||
return
|
||||
|
||||
# 创建或获取材质
|
||||
mat = Material()
|
||||
|
||||
# 设置材质属性 - 使用自发光确保在RenderPipeline下可见
|
||||
mat.setBaseColor(Vec4(color[0], color[1], color[2], color[3]))
|
||||
mat.setDiffuse(Vec4(0, 0, 0, 1))
|
||||
#mat.setEmission(Vec4(color[0], color[1], color[2], 1.0)) # 自发光
|
||||
mat.setEmission(Vec4(1,1,1,1.0)) # 自发光
|
||||
mat.set_roughness(1)
|
||||
|
||||
# 应用材质
|
||||
arrow_node.setMaterial(mat, 1)
|
||||
|
||||
|
||||
# 设置透明度
|
||||
if color[3] < 1.0:
|
||||
arrow_node.setTransparency(TransparencyAttrib.MAlpha)
|
||||
else:
|
||||
arrow_node.setTransparency(TransparencyAttrib.MNone)
|
||||
|
||||
arrow_node.setLightOff() # 禁用光照影响
|
||||
arrow_node.setShaderOff() # 禁用着色器
|
||||
arrow_node.setFogOff() # 禁用雾效果
|
||||
|
||||
arrow_node.setBin("fixed",31)
|
||||
#arrow_node.setDepthWrite(False)
|
||||
#arrow_node.setDepthTest(True)
|
||||
|
||||
# 保存材质引用以便后续修改
|
||||
if axis == "x":
|
||||
self.xMat = mat
|
||||
elif axis == "y":
|
||||
self.yMat = mat
|
||||
elif axis == "z":
|
||||
self.zMat = mat
|
||||
|
||||
axis_node.setLightOff()
|
||||
axis_node.setShaderOff()
|
||||
axis_node.setFogOff()
|
||||
axis_node.setBin("fixed", 30)
|
||||
#axis_node.setDepthWrite(False)
|
||||
#axis_node.setDepthTest(True)
|
||||
|
||||
except Exception as e:
|
||||
print(f"设置坐标轴颜色失败: {str(e)}")
|
||||
# 回退到简单颜色设置
|
||||
try:
|
||||
axis_nodes = {
|
||||
"x": self.gizmoXAxis,
|
||||
"y": self.gizmoYAxis,
|
||||
@ -797,22 +673,7 @@ class SelectionSystem:
|
||||
}
|
||||
|
||||
if axis in axis_nodes and axis_nodes[axis]:
|
||||
axis_node = axis_nodes[axis]
|
||||
|
||||
axis_node.setColor(color[0]*20.0,color[1]*20.0,color[2]*20.0,color[3])
|
||||
axis_node.setColorScale(color[0]*20.0,color[1]*20.0,color[2]*20.0,color[3])
|
||||
axis_node.setShaderOff(10000)
|
||||
axis_node.setLightOff(10000)
|
||||
axis_node.setMaterialOff(10000)
|
||||
axis_node.setTextureOff(1000)
|
||||
axis_node.setFogOff(10000)
|
||||
|
||||
except Exception as e:
|
||||
print(f"设置坐标轴颜色失败: {str(e)}")
|
||||
# 回退到简单的颜色设置
|
||||
try:
|
||||
if axis in axis_nodes and axis_nodes[axis]:
|
||||
axis_nodes[axis].setColor(*color)
|
||||
axis_nodes[axis].setColor(color[0], color[1], color[2], color[3])
|
||||
except:
|
||||
pass
|
||||
|
||||
@ -1050,38 +911,10 @@ class SelectionSystem:
|
||||
|
||||
# ==================== 高亮和交互 ====================
|
||||
|
||||
def updateGizmoHighlight(self, mouseX, mouseY):
|
||||
"""更新坐标轴高亮状态"""
|
||||
if not self.gizmo or self.isDraggingGizmo:
|
||||
return
|
||||
|
||||
import time
|
||||
current_time = time.time()
|
||||
if not hasattr(self,'_last_highlight_time'):
|
||||
self._last_highlight_time = 0
|
||||
|
||||
if current_time - self._last_highlight_time<0.05:
|
||||
return
|
||||
self._last_highlight_time = current_time
|
||||
|
||||
hoveredAxis = self._detectHoveredAxis(mouseX, mouseY)
|
||||
|
||||
if not hasattr(self,'_hover_stability_counter'):
|
||||
self._hover_stability_counter = {}
|
||||
self._last_detected_axis = None
|
||||
|
||||
if hoveredAxis !=self._last_detected_axis:
|
||||
self._hover_stability_counter[hoveredAxis]=1
|
||||
self._last_detected_axis = hoveredAxis
|
||||
else:
|
||||
self._hover_stability_counter[hoveredAxis] = self._hover_stability_counter.get(hoveredAxis,0)+1
|
||||
|
||||
if self._hover_stability_counter.get(hoveredAxis,0)>=2:
|
||||
if hoveredAxis != self.gizmoHighlightAxis:
|
||||
self._updateAxisHighlight(hoveredAxis)
|
||||
|
||||
# 检测鼠标悬停的轴(使用相同的检测逻辑但不输出调试信息)
|
||||
hoveredAxis = None
|
||||
def detectGizmoAxisAtMouse(self, mouseX, mouseY):
|
||||
"""统一的坐标轴检测方法 - 同时用于高亮和点击检测"""
|
||||
if not self.gizmo or not self.gizmoTarget:
|
||||
return None
|
||||
|
||||
try:
|
||||
# 获取坐标轴中心的世界坐标
|
||||
@ -1124,60 +957,78 @@ class SelectionSystem:
|
||||
y_screen = worldToScreen(y_end)
|
||||
z_screen = worldToScreen(z_end)
|
||||
|
||||
# 只要坐标轴中心在屏幕内,就进行检测
|
||||
if gizmo_screen:
|
||||
click_threshold = 25
|
||||
# 如果坐标轴中心不在屏幕内,返回None
|
||||
if not gizmo_screen:
|
||||
return None
|
||||
|
||||
def isNearLine(mousePos, start, end, threshold):
|
||||
# 设置检测阈值
|
||||
click_threshold = 35 # 统一使用25像素的检测阈值
|
||||
|
||||
# 更准确的点到线段距离计算方法
|
||||
def distanceToLineSegment(mousePos, start, end):
|
||||
import math
|
||||
A = mousePos[1] - start[1]
|
||||
B = start[0] - mousePos[0]
|
||||
C = (end[1] - start[1]) * mousePos[0] + (start[0] - end[0]) * mousePos[1] + end[0] * start[1] - start[0] * end[1]
|
||||
mx, my = mousePos
|
||||
x1, y1 = start
|
||||
x2, y2 = end
|
||||
|
||||
length = math.sqrt((end[0] - start[0])**2 + (end[1] - start[1])**2)
|
||||
if length == 0:
|
||||
return False
|
||||
# 线段向量
|
||||
dx = x2 - x1
|
||||
dy = y2 - y1
|
||||
|
||||
distance = abs(C) / length
|
||||
t = ((mousePos[0] - start[0]) * (end[0] - start[0]) +
|
||||
(mousePos[1] - start[1]) * (end[1] - start[1])) / (length * length)
|
||||
# 线段长度平方
|
||||
length_sq = dx * dx + dy * dy
|
||||
|
||||
return distance < threshold and 0 <= t <= 1
|
||||
if length_sq == 0:
|
||||
# 线段退化为点
|
||||
return math.sqrt((mx - x1) ** 2 + (my - y1) ** 2)
|
||||
|
||||
# 投影参数
|
||||
t = max(0, min(1, ((mx - x1) * dx + (my - y1) * dy) / length_sq))
|
||||
|
||||
# 投影点坐标
|
||||
proj_x = x1 + t * dx
|
||||
proj_y = y1 + t * dy
|
||||
|
||||
# 返回点到投影点的距离
|
||||
return math.sqrt((mx - proj_x) ** 2 + (my - proj_y) ** 2)
|
||||
|
||||
mouse_pos = (mouseX, mouseY)
|
||||
|
||||
# 分别检测每个轴,为在屏幕外的轴端点提供替代方案
|
||||
# 按优先级检测轴(Z > X > Y)
|
||||
# 检测各个轴 - 按优先级检测(Z > X > Y)
|
||||
axes_to_check = [
|
||||
("z", z_screen),
|
||||
("x", x_screen),
|
||||
("y", y_screen)
|
||||
]
|
||||
|
||||
# 对于轴端点在屏幕外的情况,使用较短的轴段进行检测
|
||||
def getAxisScreenPoint(axis_name, axis_screen_end):
|
||||
if axis_screen_end:
|
||||
return axis_screen_end
|
||||
# 如果端点在屏幕外,使用轴长度的一半作为检测点
|
||||
if axis_name == "x":
|
||||
half_end = gizmo_world_pos + Vec3(self.axis_length * 0.5, 0, 0)
|
||||
elif axis_name == "y":
|
||||
half_end = gizmo_world_pos + Vec3(0, self.axis_length * 0.5, 0)
|
||||
elif axis_name == "z":
|
||||
half_end = gizmo_world_pos + Vec3(0, 0, self.axis_length * 0.5)
|
||||
return worldToScreen(half_end)
|
||||
for axis_name, axis_end in axes_to_check:
|
||||
if axis_end:
|
||||
distance = distanceToLineSegment(mouse_pos, gizmo_screen, axis_end)
|
||||
if distance < click_threshold:
|
||||
return axis_name
|
||||
|
||||
# 获取有效的检测点(优先使用完整轴,备用使用半轴)
|
||||
z_detect_point = getAxisScreenPoint("z", z_screen)
|
||||
x_detect_point = getAxisScreenPoint("x", x_screen)
|
||||
y_detect_point = getAxisScreenPoint("y", y_screen)
|
||||
|
||||
if z_detect_point and isNearLine(mouse_pos, gizmo_screen, z_detect_point, click_threshold):
|
||||
hoveredAxis = "z"
|
||||
elif x_detect_point and isNearLine(mouse_pos, gizmo_screen, x_detect_point, click_threshold):
|
||||
hoveredAxis = "x"
|
||||
elif y_detect_point and isNearLine(mouse_pos, gizmo_screen, y_detect_point, click_threshold):
|
||||
hoveredAxis = "y"
|
||||
# 如果没有检测到,返回None
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
pass # 静默处理错误,避免频繁输出
|
||||
# 静默处理错误,避免频繁输出
|
||||
return None
|
||||
|
||||
# 如果高亮状态发生变化
|
||||
def updateGizmoHighlight(self, mouseX, mouseY):
|
||||
"""更新坐标轴高亮状态"""
|
||||
if not self.gizmo or self.isDraggingGizmo:
|
||||
return
|
||||
|
||||
# 使用统一的检测方法
|
||||
hoveredAxis = self.detectGizmoAxisAtMouse(mouseX, mouseY)
|
||||
|
||||
# 简化稳定性检测逻辑
|
||||
if not hasattr(self, '_last_detected_axis'):
|
||||
self._last_detected_axis = None
|
||||
|
||||
# 如果检测结果发生变化,立即更新高亮
|
||||
if hoveredAxis != self._last_detected_axis:
|
||||
# 更新轴的高亮状态
|
||||
if hoveredAxis != self.gizmoHighlightAxis:
|
||||
# 恢复之前高亮的轴
|
||||
if self.gizmoHighlightAxis:
|
||||
@ -1189,6 +1040,8 @@ class SelectionSystem:
|
||||
|
||||
self.gizmoHighlightAxis = hoveredAxis
|
||||
|
||||
self._last_detected_axis = hoveredAxis
|
||||
|
||||
def _detectHoveredAxis(self, mouseX, mouseY):
|
||||
"""检测鼠标悬停的轴 - 提取为独立方法"""
|
||||
# 将原来 updateGizmoHighlight 中的检测逻辑移到这里
|
||||
@ -1221,6 +1074,10 @@ class SelectionSystem:
|
||||
return
|
||||
|
||||
self.isDraggingGizmo = True
|
||||
# 使用当前高亮的轴,如果有的话
|
||||
if self.gizmoHighlightAxis:
|
||||
self.dragGizmoAxis = self.gizmoHighlightAxis
|
||||
else:
|
||||
self.dragGizmoAxis = axis
|
||||
self.dragStartMousePos = (mouseX, mouseY)
|
||||
|
||||
@ -1228,7 +1085,25 @@ class SelectionSystem:
|
||||
self.gizmoTargetStartPos = self.gizmoTarget.getPos()
|
||||
self.gizmoStartPos = self.gizmo.getPos(self.world.render) # 坐标轴的世界位置
|
||||
|
||||
print(f"开始拖拽 {axis} 轴 - 目标起始位置: {self.gizmoTargetStartPos}, 坐标轴位置: {self.gizmoStartPos}, 鼠标: ({mouseX}, {mouseY})")
|
||||
# 确保正在拖动的轴保持高亮状态
|
||||
if self.dragGizmoAxis and self.dragGizmoAxis in self.gizmo_colors:
|
||||
# 先将所有轴恢复为正常颜色
|
||||
for axis_name in self.gizmo_colors.keys():
|
||||
if axis_name != self.dragGizmoAxis:
|
||||
self.setGizmoAxisColor(axis_name, self.gizmo_colors[axis_name])
|
||||
|
||||
# 然后将当前拖动的轴设置为高亮颜色
|
||||
self.setGizmoAxisColor(self.dragGizmoAxis, self.gizmo_highlight_colors[self.dragGizmoAxis])
|
||||
self.gizmoHighlightAxis = self.dragGizmoAxis
|
||||
elif axis and axis in self.gizmo_colors:
|
||||
for axis_name in self.gizmo_colors.keys():
|
||||
self.setGizmoAxisColor(axis_name, self.gizmo_colors[axis_name])
|
||||
|
||||
self.setGizmoAxisColor(axis, self.gizmo_highlight_colors[axis])
|
||||
self.gizmoHighlightAxis = axis
|
||||
|
||||
print(
|
||||
f"开始拖拽 {self.dragGizmoAxis} 轴 - 目标起始位置: {self.gizmoTargetStartPos}, 坐标轴位置: {self.gizmoStartPos}, 鼠标: ({mouseX}, {mouseY})")
|
||||
|
||||
except Exception as e:
|
||||
print(f"开始坐标轴拖拽失败: {str(e)}")
|
||||
@ -1454,6 +1329,10 @@ class SelectionSystem:
|
||||
def stopGizmoDrag(self):
|
||||
"""停止坐标轴拖拽"""
|
||||
print(f"停止坐标轴拖拽 - 轴: {self.dragGizmoAxis}")
|
||||
if self.dragGizmoAxis and self.dragGizmoAxis in self.gizmo_colors:
|
||||
self.setGizmoAxisColor(self.dragGizmoAxis, self.gizmo_colors[self.dragGizmoAxis])
|
||||
# 不要将 gizmoHighlightAxis 设置为 None,保持当前高亮轴的状态
|
||||
# self.gizmoHighlightAxis = None
|
||||
|
||||
self.isDraggingGizmo = False
|
||||
self.dragGizmoAxis = None
|
||||
@ -1507,3 +1386,10 @@ class SelectionSystem:
|
||||
def hasSelection(self):
|
||||
"""检查是否有选中的节点"""
|
||||
return self.selectedNode is not None
|
||||
|
||||
def checkAndClearIfTargetDeleted(self):
|
||||
if self.gizmoTarget and self.gizmoTarget.isEmpty():
|
||||
self.clearGizmo()
|
||||
|
||||
if self.selectionBoxTarget and self.selectionBoxTarget.isEmpty():
|
||||
self.clearSelectionBox()
|
||||
|
||||
6
main.py
6
main.py
@ -370,9 +370,6 @@ class MyWorld(CoreWorld):
|
||||
"""更新属性面板显示 - 代理到property_panel"""
|
||||
return self.property_panel.updatePropertyPanel(item)
|
||||
|
||||
# def addAnimationPanel(self,originmodel,filepath):
|
||||
# return self.property_panel._addAnimationPanel(originmodel,filepath)
|
||||
|
||||
def updateGUIPropertyPanel(self, gui_element):
|
||||
"""更新GUI元素属性面板 - 代理到property_panel"""
|
||||
return self.property_panel.updateGUIPropertyPanel(gui_element)
|
||||
@ -380,6 +377,9 @@ class MyWorld(CoreWorld):
|
||||
def removeActorForModel(self,model):
|
||||
return self.property_panel.removeActorForModel( model)
|
||||
|
||||
def updateNodeVisibilityAfterDrag(self,item):
|
||||
return self.property_panel.updateNodeVisibilityAfterDrag(item)
|
||||
|
||||
# ==================== 工具管理代理 ====================
|
||||
|
||||
def setCurrentTool(self, tool):
|
||||
|
||||
@ -631,6 +631,7 @@ class SceneManager:
|
||||
|
||||
# 将碰撞节点附加到模型上
|
||||
cNodePath = model.attachNewNode(cNode)
|
||||
#cNodePath.hide()
|
||||
# cNodePath.show() # 取消注释可以显示碰撞体,用于调试
|
||||
|
||||
# ==================== 场景树管理 ====================
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QMenu
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.sip import delete
|
||||
from panda3d.core import GeomNode, ModelRoot
|
||||
|
||||
|
||||
@ -26,8 +27,11 @@ class InterfaceManager:
|
||||
def onTreeItemClicked(self, item, column):
|
||||
"""处理树形控件项目点击事件"""
|
||||
if not item:
|
||||
self.world.selection.updateSelection(None)
|
||||
return
|
||||
|
||||
self.world.property_panel.updatePropertyPanel(item)
|
||||
|
||||
# 获取节点对象
|
||||
nodePath = item.data(0, Qt.UserRole)
|
||||
if nodePath:
|
||||
@ -36,7 +40,7 @@ class InterfaceManager:
|
||||
self.world.selection.updateSelection(nodePath)
|
||||
|
||||
# 更新属性面板
|
||||
self.world.property_panel.updatePropertyPanel(item)
|
||||
#self.world.property_panel.updatePropertyPanel(item)
|
||||
|
||||
print(f"树形控件点击: {item.text(0)}")
|
||||
else:
|
||||
@ -77,6 +81,9 @@ class InterfaceManager:
|
||||
if self.isModelOrChild(item):
|
||||
deleteAction = menu.addAction("删除")
|
||||
deleteAction.triggered.connect(lambda: self.deleteNode(nodePath, item))
|
||||
else:
|
||||
deleteAction = menu.addAction("删除")
|
||||
deleteAction.triggered.connect(lambda: self.deleteNode(nodePath, item))
|
||||
|
||||
# 显示菜单
|
||||
menu.exec_(self.treeWidget.viewport().mapToGlobal(position))
|
||||
@ -94,10 +101,31 @@ class InterfaceManager:
|
||||
try:
|
||||
# 从场景中移除
|
||||
self.world.property_panel.removeActorForModel(nodePath)
|
||||
|
||||
if hasattr(nodePath,'getPythonTag'):
|
||||
light_object = nodePath.getPythonTag('rp_light_object')
|
||||
if light_object and hasattr(self.world,'render_pipeline'):
|
||||
self.world.render_pipeline.remove_light(light_object)
|
||||
|
||||
if hasattr(self.world,'selection'):
|
||||
if self.world.selection.selectedNode == nodePath:
|
||||
self.world.selection.clearSelectionBox()
|
||||
self.world.selection.clearGizmo()
|
||||
self.world.selection.selectedNode = None
|
||||
self.world.selection.selectedObject = None
|
||||
|
||||
if nodePath in self.world.Spotlight:
|
||||
self.world.Spotlight.remove(nodePath)
|
||||
if nodePath in self.world.Pointlight:
|
||||
self.world.Pointlight.remove(nodePath)
|
||||
|
||||
nodePath.removeNode()
|
||||
|
||||
if hasattr(self.world,'selection'):
|
||||
self.world.selection.checkAndClearIfTargetDeleted()
|
||||
|
||||
# 如果是模型根节点,从模型列表中移除
|
||||
if item.parent().text(0) == "模型":
|
||||
#if item.parent().text(0) == "模型":
|
||||
if nodePath in self.world.models:
|
||||
self.world.models.remove(nodePath)
|
||||
|
||||
@ -240,3 +268,34 @@ class InterfaceManager:
|
||||
|
||||
for i in range(item.childCount()):
|
||||
self._restore_expanded(item.child(i),path)
|
||||
|
||||
|
||||
def syncVisibilityDownward(self):
|
||||
from collections import deque
|
||||
|
||||
if not self.world.models:
|
||||
return
|
||||
q = deque()
|
||||
|
||||
for root in self.world.models:
|
||||
q.append(root)
|
||||
|
||||
while q:
|
||||
node = q.popleft()
|
||||
|
||||
visible = node.getPythonTag("visible")
|
||||
if visible is None:
|
||||
visible = True
|
||||
if not visible:
|
||||
stack = [node]
|
||||
while stack:
|
||||
cur = stack.pop()
|
||||
cur.hide()
|
||||
|
||||
for child in cur.getChildren():
|
||||
stack.append(child)
|
||||
continue
|
||||
node.show()
|
||||
|
||||
for child in node.getChildren():
|
||||
q.append(child)
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
from collections import deque
|
||||
from traceback import print_exc
|
||||
from types import new_class
|
||||
from typing import Hashable
|
||||
@ -21,6 +22,25 @@ 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("开始设置属性布局")
|
||||
@ -50,6 +70,20 @@ class PropertyPanelManager:
|
||||
if item.widget():
|
||||
item.widget().deleteLater()
|
||||
|
||||
def updateNodeVisibilityAfterDrag(self, item):
|
||||
"""拖拽结束后更新节点的可见性状态"""
|
||||
node = item.data(0, Qt.UserRole)
|
||||
if not node:
|
||||
return
|
||||
|
||||
# 当节点被拖拽后,需要根据新父节点的状态来更新可见性
|
||||
self._syncEffectiveVisibility(node)
|
||||
self._syncSceneVisibility()
|
||||
def _syncSceneVisibility(self):
|
||||
scene_root = self.world.render
|
||||
self._syncEffectiveVisibility(scene_root)
|
||||
|
||||
|
||||
def updatePropertyPanel(self, item):
|
||||
"""更新属性面板显示"""
|
||||
if not self._propertyLayout or not self._propertyLayout.parent():
|
||||
@ -58,6 +92,10 @@ class PropertyPanelManager:
|
||||
|
||||
self.clearPropertyPanel()
|
||||
|
||||
# 应用紧凑样式到属性面板容器
|
||||
if self._propertyLayout.parent():
|
||||
self._propertyLayout.parent().setStyleSheet(self.compact_style)
|
||||
|
||||
itemText = item.text(0)
|
||||
|
||||
# 如果点击的是场景根节点,显示提示信息
|
||||
@ -68,15 +106,34 @@ class PropertyPanelManager:
|
||||
self._propertyLayout.addWidget(tipLabel)
|
||||
return
|
||||
|
||||
model = item.data(0, Qt.UserRole)
|
||||
|
||||
user_visible = True
|
||||
if model:
|
||||
user_visible = model.getPythonTag("user_visible")
|
||||
if user_visible is None:
|
||||
user_visible = True
|
||||
model.setPythonTag("user_visible", True)
|
||||
|
||||
self.name_group = QGroupBox("物体名称")
|
||||
name_layout = QHBoxLayout()
|
||||
self.active_check = QCheckBox()
|
||||
self.active_check.setChecked(True) # 默认激活
|
||||
# 根据模型的实际可见性状态设置复选框
|
||||
self.active_check.setChecked(user_visible)
|
||||
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)
|
||||
|
||||
if model:
|
||||
try:
|
||||
self.active_check.stateChanged.disconnect()
|
||||
except TypeError:
|
||||
pass
|
||||
self.active_check.stateChanged.connect(
|
||||
lambda state,m = model:self._setUserVisible(m,state == Qt.Checked)
|
||||
)
|
||||
# nameLabel = QLabel("名称:")
|
||||
# nameEdit = QLineEdit(itemText)
|
||||
# self._propertyLayout.addRow(nameLabel, nameEdit)
|
||||
@ -107,6 +164,59 @@ class PropertyPanelManager:
|
||||
if propertyWidget:
|
||||
propertyWidget.update()
|
||||
|
||||
def _setUserVisible(self,node,visible):
|
||||
node.setPythonTag("user_visible",visible)
|
||||
self._syncEffectiveVisibility(node)
|
||||
|
||||
def _syncEffectiveVisibility(self, start_node):
|
||||
"""广度优先,确保父隐藏则子一定隐藏"""
|
||||
# 获取起始节点的父节点
|
||||
parent_node = start_node.getParent()
|
||||
|
||||
# 确定父节点的有效可见性
|
||||
parent_effective_visible = True
|
||||
if parent_node:
|
||||
parent_effective_visible = parent_node.getPythonTag("effective_visible")
|
||||
if parent_effective_visible is None:
|
||||
parent_effective_visible = True
|
||||
|
||||
q = deque([(start_node, parent_effective_visible)]) # (node, parent_effective_visible)
|
||||
|
||||
while q:
|
||||
node, parent_eff = q.popleft()
|
||||
user = node.getPythonTag("user_visible")
|
||||
if user is None:
|
||||
user = True
|
||||
eff = parent_eff and user
|
||||
node.setPythonTag("effective_visible", eff)
|
||||
|
||||
# 特殊处理:检查是否为碰撞体节点
|
||||
|
||||
if node.getName().startswith("modelCollision_"):
|
||||
node.hide()
|
||||
else:
|
||||
if eff:
|
||||
node.show()
|
||||
else:
|
||||
node.hide()
|
||||
|
||||
for child in node.getChildren():
|
||||
q.append((child, eff))
|
||||
|
||||
def _toggleModelVisibility(self, model, state):
|
||||
"""切换模型可见性状态"""
|
||||
try:
|
||||
# 用我们自己维护的可见性接口,而不是直接 show/hide
|
||||
visible = (state == Qt.Checked)
|
||||
self._setUserVisible(model, visible)
|
||||
|
||||
collision_nodes = model.findAllMatches("**/modelCollision_*")
|
||||
for collision_node in collision_nodes:
|
||||
collision_node.hide()
|
||||
|
||||
except Exception as e:
|
||||
print(f"切换模型可见性失败: {str(e)}")
|
||||
|
||||
def refreshModelValues(self, nodePath):
|
||||
"""实时刷新模型所有属性数值(相对/世界位置、旋转、缩放)"""
|
||||
if not nodePath or self._propertyLayout is None:
|
||||
@ -185,7 +295,7 @@ class PropertyPanelManager:
|
||||
|
||||
# 设置位置控件属性
|
||||
for pos_widget in [self.pos_x, self.pos_y, self.pos_z]:
|
||||
pos_widget.setRange(-1000, 1000)
|
||||
pos_widget.setRange(-1000000.0, 1000000.0)
|
||||
|
||||
self.pos_x.setValue(relativePos.getX())
|
||||
self.pos_y.setValue(relativePos.getY())
|
||||
@ -234,7 +344,7 @@ class PropertyPanelManager:
|
||||
|
||||
# 设置世界位置控件属性
|
||||
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.setRange(-1000000.0, 1000000.0)
|
||||
world_pos_widget.setReadOnly(True)
|
||||
|
||||
self.world_pos_x.setValue(worldPos.getX())
|
||||
@ -253,7 +363,7 @@ class PropertyPanelManager:
|
||||
|
||||
# 设置旋转控件属性
|
||||
for rot_widget in [self.rot_h, self.rot_p, self.rot_r]:
|
||||
rot_widget.setRange(-180, 180)
|
||||
rot_widget.setRange(-360, 360)
|
||||
|
||||
self.rot_h.setValue(model.getH())
|
||||
self.rot_p.setValue(model.getP())
|
||||
@ -4182,14 +4292,6 @@ class PropertyPanelManager:
|
||||
# 忽略 Actor 加载错误,很多模型都不是角色动画
|
||||
print(f"[信息] 该模型不包含骨骼动画: {actor_error}")
|
||||
|
||||
# 只有在没有骨骼动画时才检测非骨骼动画
|
||||
if not has_skeletal_anim:
|
||||
non_skeletal_anims = self._detectNonSkeletalAnimations(origin_model)
|
||||
if non_skeletal_anims and self._validateNonSkeletalAnimations(origin_model, non_skeletal_anims):
|
||||
self._buildNonSkeletalUI(origin_model, non_skeletal_anims, animation_layout)
|
||||
has_animation = True
|
||||
print(f"[信息] 检测到非骨骼动画: {list(non_skeletal_anims.keys())}")
|
||||
|
||||
# 如果都没有动画
|
||||
if not has_animation:
|
||||
no_anim_label = QLabel("此模型无动画")
|
||||
@ -4861,434 +4963,7 @@ except Exception as e:
|
||||
origin_model.setPythonTag("anim_speed",speed)
|
||||
print(f"[动画] 速度设为: {speed} ({display_name})")
|
||||
|
||||
def _detectNonSkeletalAnimations(self, origin_model):
|
||||
"""检测模型中的非骨骼动画"""
|
||||
animations = {}
|
||||
|
||||
try:
|
||||
print(f"[调试] 开始检测非骨骼动画: {origin_model}")
|
||||
|
||||
# 1. 精确检测 AnimBundle (非骨骼动画)
|
||||
bundle_nodes = origin_model.findAllMatches("**/+AnimBundleNode")
|
||||
print(f"[调试] 找到 AnimBundleNode 数量: {bundle_nodes.getNumPaths()}")
|
||||
|
||||
if not bundle_nodes.isEmpty():
|
||||
for i, bundle_node in enumerate(bundle_nodes):
|
||||
try:
|
||||
bundle = bundle_node.node().getBundle()
|
||||
print(f"[调试] AnimBundle #{i}: {bundle}")
|
||||
|
||||
if bundle and hasattr(bundle, 'getNumFrames'):
|
||||
num_frames = bundle.getNumFrames()
|
||||
print(f"[调试] Bundle 帧数: {num_frames}")
|
||||
|
||||
# 只有真正有多帧的才认为是动画
|
||||
if num_frames > 1:
|
||||
anim_names = []
|
||||
|
||||
# 尝试获取动画名称
|
||||
try:
|
||||
if hasattr(bundle, 'getName') and bundle.getName():
|
||||
anim_names.append(bundle.getName())
|
||||
else:
|
||||
anim_names.append(f"Animation_{i}")
|
||||
print(f"[调试] 检测到有效帧动画: {anim_names}, {num_frames} 帧")
|
||||
except Exception as name_error:
|
||||
anim_names.append(f"Animation_{i}")
|
||||
print(f"[调试] 使用默认动画名: Animation_{i}")
|
||||
|
||||
animations['transform'] = {
|
||||
'bundle': bundle,
|
||||
'names': anim_names,
|
||||
'node': bundle_node,
|
||||
'frames': num_frames
|
||||
}
|
||||
else:
|
||||
print(f"[调试] Bundle 只有 {num_frames} 帧,不认为是动画")
|
||||
|
||||
except Exception as bundle_error:
|
||||
print(f"[调试] 处理 bundle 失败: {bundle_error}")
|
||||
|
||||
# 2. 仅对 GLB 文件进行特殊检测
|
||||
filepath = origin_model.getTag("model_path")
|
||||
if filepath and filepath.lower().endswith('.glb') and not animations:
|
||||
print(f"[调试] GLB 文件特殊检测: {filepath}")
|
||||
|
||||
# 检查是否有任何动画相关的节点名称
|
||||
all_nodes = origin_model.findAllMatches("**")
|
||||
anim_indicators = []
|
||||
|
||||
for node in all_nodes:
|
||||
node_name = node.getName().lower()
|
||||
# 查找典型的动画节点命名模式
|
||||
if any(keyword in node_name for keyword in ['anim', 'key', 'frame', 'action', 'timeline']):
|
||||
anim_indicators.append(node.getName())
|
||||
print(f"[调试] 发现可能的动画节点: {node.getName()}")
|
||||
|
||||
# 只有在明确找到动画指示器时才创建动画条目
|
||||
if anim_indicators:
|
||||
animations['glb_keyframe'] = {
|
||||
'bundle': None,
|
||||
'names': ['GLB_Animation'],
|
||||
'node': origin_model,
|
||||
'type': 'glb_manual',
|
||||
'indicators': anim_indicators
|
||||
}
|
||||
print(f"[调试] 创建 GLB 动画条目,基于指示器: {anim_indicators}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"检测非骨骼动画失败: {e}")
|
||||
|
||||
print(f"[调试] 最终检测结果: {animations}")
|
||||
return animations if animations else None
|
||||
|
||||
def _validateNonSkeletalAnimations(self, origin_model, animations):
|
||||
"""验证检测到的非骨骼动画是否真的可以播放"""
|
||||
try:
|
||||
print(f"[验证] 开始验证非骨骼动画: {list(animations.keys())}")
|
||||
|
||||
for anim_type, anim_data in animations.items():
|
||||
if anim_type == 'transform':
|
||||
# 验证变换动画
|
||||
bundle = anim_data.get('bundle')
|
||||
if bundle:
|
||||
# 检查是否真的有可播放的动画数据
|
||||
if hasattr(bundle, 'getNumFrames'):
|
||||
frames = bundle.getNumFrames()
|
||||
if frames <= 1:
|
||||
print(f"[验证] 变换动画帧数不足: {frames}")
|
||||
return False
|
||||
|
||||
# 检查是否有有效的动画通道
|
||||
try:
|
||||
if hasattr(bundle, 'getNumChannels'):
|
||||
channels = bundle.getNumChannels()
|
||||
if channels == 0:
|
||||
print(f"[验证] 无有效动画通道")
|
||||
return False
|
||||
except:
|
||||
pass
|
||||
|
||||
elif anim_type == 'glb_keyframe':
|
||||
# 验证 GLB 动画指示器
|
||||
indicators = anim_data.get('indicators', [])
|
||||
if not indicators:
|
||||
print(f"[验证] GLB 动画无有效指示器")
|
||||
return False
|
||||
|
||||
print(f"[验证] 动画验证通过")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"[验证] 动画验证失败: {e}")
|
||||
return False
|
||||
|
||||
def _buildNonSkeletalUI(self, origin_model, animations, layout):
|
||||
"""构建非骨骼动画控制UI"""
|
||||
from PyQt5.QtWidgets import QLabel, QComboBox, QHBoxLayout, QWidget, QPushButton, QDoubleSpinBox, QTabWidget
|
||||
|
||||
# 动画信息
|
||||
info_text = f"非骨骼动画数量: {len(animations)}"
|
||||
info_label = QLabel(info_text)
|
||||
info_label.setStyleSheet("color:#888;font-size:10px;")
|
||||
layout.addWidget(QLabel("信息:"), 0, 0)
|
||||
layout.addWidget(info_label, 0, 1, 1, 3)
|
||||
|
||||
# 如果有多种类型的动画,使用标签页
|
||||
if len(animations) > 1:
|
||||
tab_widget = QTabWidget()
|
||||
|
||||
for anim_type, anim_data in animations.items():
|
||||
tab = QWidget()
|
||||
tab_layout = QVBoxLayout(tab)
|
||||
self._buildAnimationTypeUI(tab_layout, origin_model, anim_type, anim_data)
|
||||
tab_widget.addTab(tab, self._getAnimTypeDisplayName(anim_type))
|
||||
|
||||
self._propertyLayout.addRow("动画类型:", tab_widget)
|
||||
else:
|
||||
# 只有一种类型,直接显示
|
||||
anim_type, anim_data = next(iter(animations.items()))
|
||||
self._buildAnimationTypeUI(self._propertyLayout, origin_model, anim_type, anim_data)
|
||||
|
||||
# 存储动画信息供控制使用
|
||||
if not hasattr(self, '_non_skeletal_cache'):
|
||||
self._non_skeletal_cache = {}
|
||||
self._non_skeletal_cache[origin_model] = animations
|
||||
|
||||
def _buildAnimationTypeUI(self, layout, origin_model, anim_type, anim_data):
|
||||
"""为特定动画类型构建UI"""
|
||||
from PyQt5.QtWidgets import QLabel, QComboBox, QHBoxLayout, QWidget, QPushButton, QDoubleSpinBox
|
||||
|
||||
if anim_type == 'transform':
|
||||
# 变换动画控制
|
||||
self.ns_transform_combo = QComboBox()
|
||||
self.ns_transform_combo.addItems(anim_data['names'])
|
||||
layout.addRow("变换动画:", self.ns_transform_combo)
|
||||
|
||||
elif anim_type == 'texture':
|
||||
# 纹理动画控制
|
||||
self.ns_texture_combo = QComboBox()
|
||||
self.ns_texture_combo.addItems(anim_data['stages'])
|
||||
layout.addRow("纹理动画:", self.ns_texture_combo)
|
||||
|
||||
elif anim_type == 'material':
|
||||
# 材质动画控制
|
||||
self.ns_material_combo = QComboBox()
|
||||
self.ns_material_combo.addItems(anim_data['properties'])
|
||||
layout.addRow("材质动画:", self.ns_material_combo)
|
||||
|
||||
elif anim_type == 'lerp':
|
||||
# Lerp动画控制
|
||||
self.ns_lerp_combo = QComboBox()
|
||||
self.ns_lerp_combo.addItems(anim_data['intervals'])
|
||||
layout.addRow("Lerp动画:", self.ns_lerp_combo)
|
||||
|
||||
# 通用控制按钮
|
||||
btn_box = QWidget()
|
||||
btn_lay = QHBoxLayout(btn_box)
|
||||
for txt, cmd in (("播放", "play"), ("暂停", "pause"), ("停止", "stop"), ("循环", "loop")):
|
||||
btn = QPushButton(txt)
|
||||
btn.clicked.connect(lambda _, c=cmd, t=anim_type: self._controlNonSkeletalAnimation(origin_model, t, c))
|
||||
btn_lay.addWidget(btn)
|
||||
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.valueChanged.connect(lambda v, t=anim_type: self._setNonSkeletalAnimationSpeed(origin_model, t, v))
|
||||
layout.addRow("播放速度:", speed_spinbox)
|
||||
|
||||
def _getAnimTypeDisplayName(self, anim_type):
|
||||
"""获取动画类型的显示名称"""
|
||||
names = {
|
||||
'transform': '变换动画',
|
||||
'glb_keyframe': 'GLB关键帧动画',
|
||||
'texture': '纹理动画',
|
||||
'material': '材质动画',
|
||||
'lerp': 'Lerp动画'
|
||||
}
|
||||
return names.get(anim_type, anim_type)
|
||||
|
||||
def _controlNonSkeletalAnimation(self, origin_model, anim_type, command):
|
||||
"""控制非骨骼动画播放"""
|
||||
try:
|
||||
if not hasattr(self, '_non_skeletal_cache') or origin_model not in self._non_skeletal_cache:
|
||||
return
|
||||
|
||||
animations = self._non_skeletal_cache[origin_model]
|
||||
if anim_type not in animations:
|
||||
return
|
||||
|
||||
anim_data = animations[anim_type]
|
||||
|
||||
if anim_type == 'transform':
|
||||
self._controlTransformAnimation(origin_model, anim_data, command)
|
||||
elif anim_type == 'glb_keyframe':
|
||||
self._controlGLBKeyframeAnimation(origin_model, anim_data, command)
|
||||
elif anim_type == 'texture':
|
||||
self._controlTextureAnimation(origin_model, anim_data, command)
|
||||
elif anim_type == 'material':
|
||||
self._controlMaterialAnimation(origin_model, anim_data, command)
|
||||
elif anim_type == 'lerp':
|
||||
self._controlLerpAnimation(origin_model, anim_data, command)
|
||||
|
||||
print(f"[非骨骼动画] {anim_type} {command}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"控制非骨骼动画失败: {e}")
|
||||
|
||||
def _controlTransformAnimation(self, origin_model, anim_data, command):
|
||||
"""控制变换动画"""
|
||||
try:
|
||||
bundle = anim_data['bundle']
|
||||
bundle_node = anim_data['node']
|
||||
|
||||
print(f"[调试] 控制变换动画: {command}, Bundle: {bundle}")
|
||||
|
||||
if command == 'play':
|
||||
# 方法1: 通过 AnimBundle 直接播放
|
||||
if hasattr(bundle, 'play'):
|
||||
bundle.play()
|
||||
print(f"[动画] 通过 bundle.play() 播放")
|
||||
# 方法2: 通过 AnimControl 播放
|
||||
elif hasattr(bundle_node.node(), 'getAnimControl'):
|
||||
controls = bundle_node.node().getAnimControls()
|
||||
if controls:
|
||||
controls[0].play()
|
||||
print(f"[动画] 通过 AnimControl 播放")
|
||||
# 方法3: 通过启用动画节点
|
||||
else:
|
||||
bundle_node.node().setPlayRate(1.0)
|
||||
print(f"[动画] 设置播放速率为 1.0")
|
||||
|
||||
elif command == 'pause':
|
||||
if hasattr(bundle, 'pause'):
|
||||
bundle.pause()
|
||||
elif hasattr(bundle_node.node(), 'getAnimControl'):
|
||||
controls = bundle_node.node().getAnimControls()
|
||||
if controls:
|
||||
controls[0].pause()
|
||||
else:
|
||||
bundle_node.node().setPlayRate(0.0)
|
||||
|
||||
elif command == 'stop':
|
||||
if hasattr(bundle, 'stop'):
|
||||
bundle.stop()
|
||||
elif hasattr(bundle_node.node(), 'getAnimControl'):
|
||||
controls = bundle_node.node().getAnimControls()
|
||||
if controls:
|
||||
controls[0].stop()
|
||||
else:
|
||||
bundle_node.node().setPlayRate(0.0)
|
||||
# 重置到第一帧
|
||||
if hasattr(bundle, 'setFrame'):
|
||||
bundle.setFrame(0)
|
||||
|
||||
elif command == 'loop':
|
||||
if hasattr(bundle, 'loop'):
|
||||
bundle.loop()
|
||||
elif hasattr(bundle_node.node(), 'getAnimControl'):
|
||||
controls = bundle_node.node().getAnimControls()
|
||||
if controls:
|
||||
controls[0].loop(True)
|
||||
controls[0].play()
|
||||
else:
|
||||
bundle_node.node().setPlayRate(1.0)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[错误] 控制变换动画失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def _controlGLBKeyframeAnimation(self, origin_model, anim_data, command):
|
||||
"""控制 GLB 关键帧动画"""
|
||||
try:
|
||||
print(f"[调试] 控制 GLB 动画: {command}")
|
||||
|
||||
# 尝试通过 AnimControlCollection 控制
|
||||
from panda3d.core import AnimControlCollection
|
||||
|
||||
# 方法1: 查找 AnimControlCollection
|
||||
anim_collection = AnimControlCollection()
|
||||
origin_model.getAnimControls(anim_collection)
|
||||
|
||||
if anim_collection.getNumAnims() > 0:
|
||||
for i in range(anim_collection.getNumAnims()):
|
||||
anim_control = anim_collection.getAnim(i)
|
||||
print(f"[调试] 找到动画控制: {anim_control.getName()}")
|
||||
|
||||
if command == 'play':
|
||||
anim_control.setPlayRate(1.0)
|
||||
print(f"[GLB动画] 播放: {anim_control.getName()}")
|
||||
elif command == 'pause':
|
||||
anim_control.setPlayRate(0.0)
|
||||
print(f"[GLB动画] 暂停: {anim_control.getName()}")
|
||||
elif command == 'stop':
|
||||
anim_control.setPlayRate(0.0)
|
||||
anim_control.setFrame(0)
|
||||
print(f"[GLB动画] 停止: {anim_control.getName()}")
|
||||
elif command == 'loop':
|
||||
anim_control.setPlayRate(1.0)
|
||||
anim_control.loop(True)
|
||||
print(f"[GLB动画] 循环: {anim_control.getName()}")
|
||||
return
|
||||
|
||||
# 方法2: 通过自动播放任务
|
||||
if command == 'play':
|
||||
origin_model.setPlayRate(1.0)
|
||||
print(f"[GLB动画] 设置播放速率为 1.0")
|
||||
elif command == 'pause':
|
||||
origin_model.setPlayRate(0.0)
|
||||
print(f"[GLB动画] 暂停播放")
|
||||
elif command == 'stop':
|
||||
origin_model.setPlayRate(0.0)
|
||||
print(f"[GLB动画] 停止播放")
|
||||
elif command == 'loop':
|
||||
origin_model.setPlayRate(1.0)
|
||||
print(f"[GLB动画] 循环播放")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[错误] 控制 GLB 动画失败: {e}")
|
||||
|
||||
def _controlTextureAnimation(self, origin_model, anim_data, command):
|
||||
"""控制纹理动画"""
|
||||
# 纹理动画通常通过 LerpInterval 实现
|
||||
print(f"[纹理动画] {command} - 功能待实现")
|
||||
|
||||
def _controlMaterialAnimation(self, origin_model, anim_data, command):
|
||||
"""控制材质动画"""
|
||||
# 材质动画通常通过修改材质属性实现
|
||||
print(f"[材质动画] {command} - 功能待实现")
|
||||
|
||||
def _controlLerpAnimation(self, origin_model, anim_data, command):
|
||||
"""控制Lerp动画"""
|
||||
# 通过 LerpInterval 控制
|
||||
print(f"[Lerp动画] {command} - 功能待实现")
|
||||
|
||||
def _setNonSkeletalAnimationSpeed(self, origin_model, anim_type, speed):
|
||||
"""设置非骨骼动画播放速度"""
|
||||
try:
|
||||
if not hasattr(self, '_non_skeletal_cache') or origin_model not in self._non_skeletal_cache:
|
||||
return
|
||||
|
||||
animations = self._non_skeletal_cache[origin_model]
|
||||
if anim_type not in animations:
|
||||
return
|
||||
|
||||
anim_data = animations[anim_type]
|
||||
|
||||
if anim_type == 'transform':
|
||||
bundle = anim_data['bundle']
|
||||
if hasattr(bundle, 'setPlayRate'):
|
||||
bundle.setPlayRate(speed)
|
||||
|
||||
print(f"[非骨骼动画] {anim_type} 速度设为: {speed}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"设置非骨骼动画速度失败: {e}")
|
||||
|
||||
def _detectPlayer(self,origin_model):
|
||||
filepath = origin_model.getTag("model_path")
|
||||
if filepath:
|
||||
try:
|
||||
actor = Actor(filepath)
|
||||
if actor.getAnimNames():
|
||||
actor.cleanup();actor.removeNode()
|
||||
return ("actor",None)
|
||||
except:
|
||||
pass
|
||||
bundle_np = origin_model.find("**/+AnimBundleNode")
|
||||
if not bundle_np.isEmpty():
|
||||
ctrl = bundle_np.node().getBundle().bind(origin_model.node(),PartGroup.PART_Whole)
|
||||
return ("bundle",ctrl)
|
||||
return None
|
||||
|
||||
def _buildBundleUI(self,origin_model,ctrl):
|
||||
from PyQt5.QtWidgets import QLabel,QPushButton,QHBoxLayout,QWidget,QSlider
|
||||
|
||||
title = QLabel("骨骼动画控制")
|
||||
title.setStyleSheet("color:#FF8C00;font-weight:bold;font-size:14px;margin-top:10px;")
|
||||
self._propertyLayout.addRow(title)
|
||||
|
||||
btn_box = QWidget()
|
||||
lay = QHBoxLayout(btn_box)
|
||||
for txt , fn in (("播放",ctrl.play),
|
||||
("暂停",ctrl.stop),
|
||||
("重置",lambda:ctrl.pose(0))):
|
||||
btn = QPushButton(txt)
|
||||
btn.clicked.connect(fn)
|
||||
lay.addWidget(btn)
|
||||
self._propertyLayout.addRow("控制:",btn_box)
|
||||
|
||||
slider = QSlider()
|
||||
slider.setOrientation(1)
|
||||
slider.setRange(0,int(ctrl.getNumFrames()))
|
||||
slider.valueChanged.connect(ctrl.pose)
|
||||
self._propertyLayout.addRow("帧:",slider)
|
||||
|
||||
self._actor_cache[origin_model] = ("bundle",ctrl)
|
||||
|
||||
def _dispatchAnimCommand(self,origin_model,cmd):
|
||||
cache = self._actor_cache.get(origin_model)
|
||||
@ -5323,20 +4998,6 @@ except Exception as e:
|
||||
elif isinstance(cmd,tuple) and cmd[0] == "speed":
|
||||
actor.setPlayRate(cmd[1], anim_name)
|
||||
|
||||
elif kind == "bundle":
|
||||
ctrl = player
|
||||
if cmd == "play":
|
||||
ctrl.play()
|
||||
elif cmd == "pause":
|
||||
ctrl.stop()
|
||||
elif cmd == "stop":
|
||||
ctrl.stop()
|
||||
ctrl.pose(0)
|
||||
elif cmd == "loop":
|
||||
ctrl.loop(True)
|
||||
elif isinstance(cmd,tuple) and cmd[0] =="speed":
|
||||
ctrl.setPlayRate(cmd[1])
|
||||
|
||||
def removeActorForModel(self, model):
|
||||
"""删除 model 对应的 Actor(如果存在)"""
|
||||
actor = self._actor_cache.pop(model, None)
|
||||
|
||||
@ -348,6 +348,24 @@ class CustomTreeWidget(QTreeWidget):
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
# # 检查是否是有效的父子关系
|
||||
# if self.isValidParentChild(dragged_item, target_item):
|
||||
# # 保存当前的世界坐标
|
||||
# world_pos = dragged_node.getPos(self.world.render)
|
||||
#
|
||||
# # 更新场景图中的父子关系
|
||||
# dragged_node.wrtReparentTo(target_node)
|
||||
#
|
||||
# # 接受拖放事件,更新树形控件
|
||||
# super().dropEvent(event)
|
||||
#
|
||||
# #self.world.property_panel.updateNodeVisibilityAfterDrag(dragged_item)
|
||||
# # 更新属性面板
|
||||
# self.world.updatePropertyPanel(dragged_item)
|
||||
# self.world.property_panel._syncEffectiveVisibility(dragged_node)
|
||||
|
||||
|
||||
|
||||
print(f"dragged_node: {dragged_node}, target_node: {target_node}")
|
||||
|
||||
# 记录拖拽前的父节点
|
||||
@ -394,7 +412,7 @@ class CustomTreeWidget(QTreeWidget):
|
||||
|
||||
# 事后验证:确保节点仍在"场景"根节点下
|
||||
self._ensureUnderSceneRoot(dragged_item)
|
||||
|
||||
self.world.property_panel._syncEffectiveVisibility(dragged_node)
|
||||
event.accept()
|
||||
|
||||
# try:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user