动画播放bug修正,fbx模型自动转换glb格式

This commit is contained in:
Hector 2025-08-13 11:21:24 +08:00
parent 6a7e6e5657
commit ee0fe2741d
11 changed files with 1574 additions and 309 deletions

View File

@ -135,9 +135,9 @@ class Panda3DWorld(ShowBase):
#render_pipeline.set_camera(self.cam)
#添加渲染效果<E69588><E69E9C>
self.cam = self.render_pipeline._showbase.cam
self.camNode = self.cam.node()
self.camLens = self.camNode.get_lens()
#self.cam = self.render_pipeline._showbase.cam
#self.camNode = self.cam.node()
#self.camLens = self.camNode.get_lens()
self.render_pipeline._showbase.camera = self.render_pipeline._showbase.cam
#self.render_pipeline.daytime_mgr.update()

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,26 @@
# simple_transparent.yaml
vertex_shader: |
#version 330
uniform mat4 p3d_ModelViewProjectionMatrix;
in vec4 p3d_Vertex;
in vec2 p3d_MultiTexCoord0;
out vec2 texcoord;
void main() {
gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex;
texcoord = p3d_MultiTexCoord0;
}
fragment_shader: |
#version 330
uniform sampler2D p3d_Texture0;
uniform float material_opacity = 1.0;
in vec2 texcoord;
out vec4 o_color;
void main() {
vec4 c = texture(p3d_Texture0, texcoord);
o_color = vec4(c.rgb, c.a * material_opacity);
}
render_states:
TransparencyAttrib: M_alpha
DepthWriteAttrib: 0

View File

@ -398,7 +398,6 @@ class SelectionSystem:
material.setRoughness(1.0)
material.setMetallic(0.0)
axis_nodes[axis].setMaterial(material)
print("自发光材质设置完成")
except Exception as e:
print(f"自发光材质设置失败: {str(e)}")
@ -419,8 +418,6 @@ class SelectionSystem:
axis_node.setDepthWrite(False)
axis_node.setLightOff()
print("✓ 坐标轴渲染设置完成")
except Exception as e:
print(f"设置坐标轴渲染失败!!!!!!: {str(e)}")
@ -440,10 +437,8 @@ class SelectionSystem:
# 第三步:强制设置每个轴的独立渲染
self._forceAxisIndependentRendering()
print("✅ 激进 RenderPipeline 兼容坐标轴设置完成")
except Exception as e:
print(f"❌ 激进设置失败: {e}")
# 使用最后的备用方案
self._setupUltimateGizmoFallback()
@ -488,8 +483,6 @@ class SelectionSystem:
axis_node.setTag("unlit","1")
axis_node.setColorScale(2.0,2.0,2.0,1.0)
print(" ✓ 坐标轴完全隔离")
except Exception as e:
print(f" ❌ 隔离失败: {e}")
@ -514,8 +507,6 @@ class SelectionSystem:
)
self.gizmo.setState(minimal_state, 10000)
print(" ✓ 最简渲染设置完成")
except Exception as e:
print(f" ❌ 最简渲染设置失败: {e}")
@ -534,7 +525,6 @@ class SelectionSystem:
# 每个轴都完全独立设置
self._setupSingleAxisRendering(axis_node, name, color, 0)
print(" ✓ 独立轴渲染设置完成")
except Exception as e:
print(f" ❌ 独立轴渲染设置失败: {e}")
@ -575,8 +565,6 @@ class SelectionSystem:
def _setupUltimateGizmoFallback(self):
"""最后的备用方案 - 最简单的渲染"""
try:
print("🚨 使用最后备用方案...")
# 最简单的设置
self.gizmo.setLightOff()
self.gizmo.setFogOff()
@ -603,8 +591,6 @@ class SelectionSystem:
self.gizmoZAxis.setBin("gui-popup", 20003)
self.gizmoZAxis.setDepthTest(False)
print("✅ 最后备用方案设置完成")
except Exception as e:
print(f"❌ 最后备用方案也失败: {e}")
@ -672,7 +658,6 @@ class SelectionSystem:
self.gizmoTargetStartPos = None
self.gizmoStartPos = None
print("清除了坐标轴")
def setGizmoAxisColor(self, axis, color):
"""设置坐标轴颜色 - RenderPipeline 兼容版本"""
@ -772,7 +757,6 @@ class SelectionSystem:
axis_node.setTag("gizmo_axis", axis_name)
axis_node.setTag("pickable", "1")
print("✓ 坐标轴鼠标事件设置完成")
except Exception as e:
print(f"设置坐标轴鼠标事件失败: {e}")
@ -782,21 +766,15 @@ class SelectionSystem:
def checkGizmoClick(self, mouseX, mouseY):
"""使用屏幕空间检测是否点击了坐标轴"""
if not self.gizmo or not self.gizmoTarget:
print("坐标轴点击检测:坐标轴或目标不存在")
return None
# 基本参数验证
if not isinstance(mouseX, (int, float)) or not isinstance(mouseY, (int, float)):
print(f"坐标轴点击检测:无效的鼠标坐标 ({mouseX}, {mouseY})")
return None
try:
print(f"\n=== 坐标轴点击检测 ===")
print(f"鼠标位置: ({mouseX}, {mouseY})")
# 获取坐标轴中心的世界坐标
gizmo_world_pos = self.gizmo.getPos(self.world.render)
print(f"坐标轴世界位置: {gizmo_world_pos}")
# 计算各轴端点的世界坐标
x_end = gizmo_world_pos + Vec3(self.axis_length, 0, 0)
@ -833,7 +811,6 @@ class SelectionSystem:
# 如果无法获得屏幕坐标,使用备用方法
if not center_screen:
print("使用备用检测方法...")
return self.checkGizmoClickFallback(mouseX, mouseY)
# 计算点击阈值
@ -872,7 +849,6 @@ class SelectionSystem:
print(f"✓ 点击了{axis_label}")
return axis_name
print("× 没有点击任何轴")
return None
except Exception as e:
@ -883,7 +859,6 @@ class SelectionSystem:
def checkGizmoClickFallback(self, mouseX, mouseY):
"""备用检测方法:使用固定的屏幕区域"""
print("使用备用点击检测...")
# 获取准确的窗口尺寸
win_width, win_height = self.world.getWindowSize()

View File

@ -37,27 +37,29 @@ class CoreWorld(Panda3DWorld):
self._setupLighting()
self._setupGround()
self._loadFont()
#self.load_and_play_fbx_model()
#self.load_and_play_glb_model()
def load_and_play_fbx_model(self):
def load_and_play_glb_model(self):
"""加载 glTF 模型并播放动画"""
try:
from direct.actor.Actor import Actor
# 使用 Actor 类加载 glTF 模型
self.model = Actor("/home/tiger/Women.glb")
self.model = Actor("/home/tiger/cube.glb")
print("模型加载成功!")
self.model.reparentTo(self.render)
self.model.setPos(0, 10, 0)
self.model.setScale(10)
# 列出所有可用动画
anims = self.model.getAnimNames()
print("可用动画:", anims)
# 播放特定动画
if anims:
self.model.loop('Armature|mixamo.com|Layer0')
# 找出所有 AnimBundleNode
print(f"开始寻找动画AnimationBundleNode...")
for np in self.model.findAllMatches("**/+AnimBundleNode"):
print(f"找到AnimBundleNode: {np.getName()}")
bundle = np.node().getBundle()
for i in range(bundle.getNumAnimations()):
anim_name = bundle.getAnimation(i).getName()
print("动画名:", anim_name)
# 这里不能直接 play需要手动把 AnimControl 绑定到节点
except Exception as e:

38
install_fbx2gltf.sh Executable file
View File

@ -0,0 +1,38 @@
#!/bin/bash
# FBX2glTF 安装脚本
echo "开始安装 FBX2glTF..."
# 创建工具目录
mkdir -p ~/tools
# 方案1: 尝试下载 Godot 维护的版本
echo "尝试下载 Godot 维护的 FBX2glTF..."
if wget -O ~/tools/FBX2glTF https://github.com/godotengine/FBX2glTF/releases/download/v0.13.1/FBX2glTF-linux-x64 2>/dev/null; then
chmod +x ~/tools/FBX2glTF
echo "✓ FBX2glTF 下载成功"
else
echo "× Godot版本下载失败尝试原版..."
# 方案2: 尝试下载 Facebook 原版
if wget -O ~/tools/FBX2glTF https://github.com/facebookincubator/FBX2glTF/releases/download/v0.9.7/FBX2glTF-linux-x64 2>/dev/null; then
chmod +x ~/tools/FBX2glTF
echo "✓ FBX2glTF 原版下载成功"
else
echo "× 下载失败,请手动安装"
echo " 1. 访问: https://github.com/godotengine/FBX2glTF/releases"
echo " 2. 下载 FBX2glTF-linux-x64"
echo " 3. 移动到 ~/tools/FBX2glTF"
echo " 4. 运行: chmod +x ~/tools/FBX2glTF"
exit 1
fi
fi
# 添加到 PATH
if ! grep -q "~/tools" ~/.bashrc; then
echo 'export PATH="$HOME/tools:$PATH"' >> ~/.bashrc
echo "✓ 已添加到 PATH"
fi
echo "✓ FBX2glTF 安装完成"
echo "请运行: source ~/.bashrc 或重启终端"

View File

@ -370,13 +370,16 @@ class MyWorld(CoreWorld):
"""更新属性面板显示 - 代理到property_panel"""
return self.property_panel.updatePropertyPanel(item)
def addAnimationPanel(self,originmodel,filepath):
return self.property_panel._addAnimationPanel(originmodel,filepath)
# 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)
def removeActorForModel(self,model):
return self.property_panel.removeActorForModel( model)
# ==================== 工具管理代理 ====================
def setCurrentTool(self, tool):

View File

@ -38,19 +38,40 @@ class SceneManager:
# ==================== 模型导入和处理 ====================
def importModel(self, filepath, apply_unit_conversion=False, normalize_scales=True):
def importModel(self, filepath, apply_unit_conversion=False, normalize_scales=True, auto_convert_to_glb=True):
"""导入模型到场景
Args:
filepath: 模型文件路径
apply_unit_conversion: 是否应用单位转换主要针对FBX文件
normalize_scales: 是否标准化子节点缩放推荐开启
auto_convert_to_glb: 是否自动将非GLB格式转换为GLB以获得更好的动画支持
"""
try:
print(f"\n=== 开始导入模型: {filepath} ===")
print(f"单位转换: {'开启' if apply_unit_conversion else '关闭'}")
print(f"自动转换GLB: {'开启' if auto_convert_to_glb else '关闭'}")
filepath = util.normalize_model_path(filepath)
original_filepath = filepath
# 检查是否需要转换为GLB以获得更好的动画支持
if auto_convert_to_glb and self._shouldConvertToGLB(filepath):
print(f"🔄 检测到需要转换的格式尝试转换为GLB...")
converted_path = self._convertToGLBWithProgress(filepath)
if converted_path:
print(f"✅ 转换成功: {converted_path}")
filepath = converted_path
# 显示成功消息
try:
from PyQt5.QtWidgets import QMessageBox
original_ext = os.path.splitext(original_filepath)[1].upper()
QMessageBox.information(None, "转换成功",
f"已将 {original_ext} 格式自动转换为 GLB 格式\n以获得更好的动画支持!")
except:
pass
else:
print(f"⚠️ 转换失败,使用原始文件")
# 总是重新加载模型以确保材质信息完整
# 不使用ModelPool缓存避免材质信息丢失问题
@ -66,6 +87,12 @@ class SceneManager:
# 将模型添加到场景
model.reparentTo(self.world.render)
# 保存原始路径和转换后的路径
model.setTag("model_path", filepath)
model.setTag("original_path", original_filepath)
if filepath != original_filepath:
model.setTag("converted_from", os.path.splitext(original_filepath)[1])
model.setTag("converted_to_glb", "true")
# 可选的单位转换主要针对FBX
if apply_unit_conversion and filepath.lower().endswith('.fbx'):
@ -952,4 +979,265 @@ class SceneManager:
self.world.updateSceneTree()
return light,light_np
# ==================== GLB 转换方法 ====================
def _shouldConvertToGLB(self, filepath):
"""判断是否应该转换为GLB格式"""
ext = os.path.splitext(filepath)[1].lower()
# 需要转换的格式FBX, OBJ, DAE等但不转换已经是GLB/GLTF的
convert_formats = ['.fbx', '.obj', '.dae', '.3ds', '.blend']
return ext in convert_formats
def _convertToGLBWithProgress(self, filepath):
"""带进度显示的GLB转换"""
try:
from PyQt5.QtWidgets import QProgressDialog, QApplication
from PyQt5.QtCore import Qt
# 创建进度对话框
progress = QProgressDialog("正在转换模型格式以获得更好的动画支持...", "取消", 0, 100)
progress.setWindowTitle("模型格式转换")
progress.setWindowModality(Qt.WindowModal)
progress.show()
QApplication.processEvents()
try:
result = self._convertToGLB(filepath, progress)
progress.hide()
return result
except Exception as e:
progress.hide()
print(f"转换过程出错: {e}")
return None
except ImportError:
# 如果没有 PyQt5直接转换
return self._convertToGLB(filepath)
def _convertToGLB(self, filepath, progress=None):
"""将模型文件转换为GLB格式"""
try:
print(f"[GLB转换] 开始转换: {filepath}")
if progress:
progress.setValue(10)
progress.setLabelText("准备转换...")
from PyQt5.QtWidgets import QApplication
QApplication.processEvents()
# 准备输出路径
base_name = os.path.splitext(os.path.basename(filepath))[0]
output_dir = os.path.dirname(filepath)
glb_path = os.path.join(output_dir, f"{base_name}_auto_converted.glb")
# 如果已经存在转换后的文件,直接使用
if os.path.exists(glb_path):
# 检查文件时间,如果原文件更新则重新转换
original_time = os.path.getmtime(filepath)
converted_time = os.path.getmtime(glb_path)
if converted_time > original_time:
print(f"[GLB转换] 使用现有转换文件: {glb_path}")
if progress:
progress.setValue(100)
return glb_path
if progress:
progress.setValue(20)
progress.setLabelText("尝试 Blender 转换...")
QApplication.processEvents()
# 方法1: 使用 Blender 进行转换
if self._convertWithBlender(filepath, glb_path, progress):
return glb_path
if progress:
progress.setValue(60)
progress.setLabelText("尝试 FBX2glTF 转换...")
QApplication.processEvents()
# 方法2: 使用 FBX2glTF (如果可用)
if self._convertWithFBX2glTF(filepath, glb_path, progress):
return glb_path
if progress:
progress.setValue(80)
progress.setLabelText("尝试 Assimp 转换...")
QApplication.processEvents()
# 方法3: 使用 Assimp
if self._convertWithAssimp(filepath, glb_path, progress):
return glb_path
print(f"[GLB转换] 所有转换方法都失败")
return None
except Exception as e:
print(f"[GLB转换] 转换过程出错: {e}")
return None
def _convertWithBlender(self, input_path, output_path, progress=None):
"""使用 Blender 进行转换"""
try:
import subprocess
import tempfile
print(f"[Blender转换] {input_path}{output_path}")
# 创建 Blender 脚本
script_content = f'''
import bpy
import sys
import os
# 清理默认场景
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)
print("开始导入文件...")
# 根据文件类型选择导入方法
input_file = "{input_path}"
output_file = "{output_path}"
try:
ext = os.path.splitext(input_file)[1].lower()
if ext == '.fbx':
bpy.ops.import_scene.fbx(filepath=input_file)
elif ext == '.obj':
bpy.ops.import_scene.obj(filepath=input_file)
elif ext == '.dae':
bpy.ops.wm.collada_import(filepath=input_file)
elif ext == '.blend':
bpy.ops.wm.open_mainfile(filepath=input_file)
else:
print(f"不支持的格式: {{ext}}")
sys.exit(1)
print("导入成功开始导出GLB...")
# 导出为 GLB保留动画
bpy.ops.export_scene.gltf(
filepath=output_file,
export_format='GLB',
export_animations=True,
export_force_sampling=True,
export_frame_range=True,
export_current_frame=False,
export_skins=True,
export_morph=True,
export_lights=True,
export_cameras=False
)
print("GLB导出成功")
except Exception as e:
print(f"转换失败: {{e}}")
sys.exit(1)
'''
# 写入临时脚本文件
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as temp_file:
temp_file.write(script_content)
script_path = temp_file.name
try:
# 执行 Blender 转换
result = subprocess.run([
'blender', '--background', '--python', script_path
], capture_output=True, text=True, timeout=180)
# 清理临时文件
os.unlink(script_path)
if result.returncode == 0 and os.path.exists(output_path):
print(f"[Blender转换] 转换成功")
return True
else:
print(f"[Blender转换] 转换失败: {result.stderr}")
return False
except subprocess.TimeoutExpired:
print(f"[Blender转换] 转换超时")
return False
except FileNotFoundError:
print(f"[Blender转换] Blender 未安装")
return False
except Exception as e:
print(f"[Blender转换] 转换过程出错: {e}")
return False
def _convertWithFBX2glTF(self, input_path, output_path, progress=None):
"""使用 FBX2glTF 进行转换仅支持FBX"""
try:
import subprocess
if not input_path.lower().endswith('.fbx'):
return False
print(f"[FBX2glTF转换] {input_path}{output_path}")
# 使用 FBX2glTF 转换
result = subprocess.run([
'FBX2glTF', input_path, '--output', output_path, '--binary'
], capture_output=True, text=True, timeout=120)
if result.returncode == 0 and os.path.exists(output_path):
print(f"[FBX2glTF转换] 转换成功")
return True
else:
print(f"[FBX2glTF转换] 转换失败: {result.stderr}")
return False
except subprocess.TimeoutExpired:
print(f"[FBX2glTF转换] 转换超时")
return False
except FileNotFoundError:
print(f"[FBX2glTF转换] FBX2glTF 未安装")
return False
except Exception as e:
print(f"[FBX2glTF转换] 转换过程出错: {e}")
return False
def _convertWithAssimp(self, input_path, output_path, progress=None):
"""使用 PyAssimp 进行转换"""
try:
import pyassimp
print(f"[PyAssimp转换] {input_path}{output_path}")
# 加载模型
scene = pyassimp.load(input_path)
if not scene:
print(f"[PyAssimp转换] 加载模型失败")
return False
if progress:
progress.setValue(30)
# 导出为GLB格式
pyassimp.export(scene, output_path, "glb2")
if progress:
progress.setValue(80)
# 释放资源
pyassimp.release(scene)
if os.path.exists(output_path):
print(f"[PyAssimp转换] 转换成功")
return True
else:
print(f"[PyAssimp转换] 转换失败: 输出文件未生成")
return False
except ImportError:
print(f"[PyAssimp转换] PyAssimp 未安装")
return False
except Exception as e:
print(f"[PyAssimp转换] 转换过程出错: {e}")
return False

View File

@ -91,6 +91,7 @@ class InterfaceManager:
"""删除节点"""
try:
# 从场景中移除
self.world.property_panel.removeActorForModel(nodePath)
nodePath.removeNode()
# 如果是模型根节点,从模型列表中移除

File diff suppressed because it is too large Load Diff

View File

@ -150,7 +150,7 @@ class CustomPanda3DWidget(QPanda3DWidget):
filepath = url.toLocalFile()
if filepath.lower().endswith(('.egg', '.bam', '.obj', '.fbx', '.gltf', '.glb')):
self.world.importModel(filepath)
self.world.addAnimationPanel(None,filepath)
#self.world.addAnimationPanel(None,filepath)
event.acceptProposedAction()
else:
event.ignore()