1
0
forked from Rowland/EG
EG/demo/fbx_import_test.py
2025-07-10 09:19:51 +08:00

362 lines
13 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
FBX模型导入测试 - 演示新的缩放处理选项
修复内容:
1. 默认保持模型原有缩放结构
2. 提供可选的单位转换功能
3. 智能缩放标准化(处理子节点大缩放值)
4. 避免缩放层级混乱问题
使用说明:
1. 运行脚本启动3D编辑器
2. 通过文件菜单或拖拽导入FBX模型
3. 观察模型的缩放层级结构
4. 按U键切换单位转换模式
5. 按N键切换缩放标准化模式
"""
import sys
import os
# 添加主目录到Python路径
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
from main import MyWorld
from ui.main_window import setup_main_window
from PyQt5.QtCore import Qt
def setup_fbx_import_demo():
"""设置FBX导入演示"""
# 创建世界对象
world = MyWorld()
# 使用新的UI模块创建主窗口
app, main_window = setup_main_window(world)
# 设置窗口标题
main_window.setWindowTitle("FBX导入测试 - 缩放层级修复")
# 设置焦点策略
main_window.setFocusPolicy(Qt.StrongFocus)
main_window.setFocus()
# 导入选项
unit_conversion_enabled = False
scale_normalization_enabled = True # 默认开启缩放标准化
def keyPressEvent(event):
nonlocal unit_conversion_enabled, scale_normalization_enabled
key = event.key()
if key == Qt.Key_U: # U键切换单位转换模式
unit_conversion_enabled = not unit_conversion_enabled
status = "开启" if unit_conversion_enabled else "关闭"
print(f"\n=== 单位转换模式已{status} ===")
print("下次导入FBX文件时将使用此设置")
event.accept()
return
elif key == Qt.Key_N: # N键切换缩放标准化模式
scale_normalization_enabled = not scale_normalization_enabled
status = "开启" if scale_normalization_enabled else "关闭"
print(f"\n=== 缩放标准化模式已{status} ===")
print("下次导入FBX文件时将使用此设置")
event.accept()
return
elif key == Qt.Key_I: # I键显示导入信息
print_import_info(world)
event.accept()
return
elif key == Qt.Key_R: # R键切换射线显示
state = world.toggleRayDisplay()
status = "开启" if state else "关闭"
print(f"\n=== 射线显示已{status} ===")
event.accept()
return
elif key == Qt.Key_S: # S键显示当前设置
print_current_settings(unit_conversion_enabled, scale_normalization_enabled)
event.accept()
return
# 调用原始键盘事件处理
if hasattr(main_window, '_original_keyPressEvent'):
main_window._original_keyPressEvent(event)
else:
event.ignore()
# 覆盖importModel方法以使用当前的导入设置
original_import = world.scene_manager.importModel
def enhanced_import(filepath):
"""增强的导入方法,使用当前导入设置"""
print(f"\n" + "="*60)
print(f"导入模型: {os.path.basename(filepath)}")
print(f"单位转换: {'开启' if unit_conversion_enabled else '关闭'}")
print(f"缩放标准化: {'开启' if scale_normalization_enabled else '关闭'}")
print("="*60)
result = original_import(
filepath,
apply_unit_conversion=unit_conversion_enabled,
normalize_scales=scale_normalization_enabled
)
if result:
print("\n导入后的模型结构:")
print_model_structure(result, max_depth=3, world=world) # 限制显示深度
return result
world.scene_manager.importModel = enhanced_import
# 保存原始键盘事件处理器
if hasattr(main_window, 'keyPressEvent'):
main_window._original_keyPressEvent = main_window.keyPressEvent
main_window.keyPressEvent = keyPressEvent
# 添加自定义菜单
add_custom_menus(main_window, world, unit_conversion_enabled, scale_normalization_enabled)
# 输出使用说明
print_usage_instructions()
return app, main_window, world
def add_custom_menus(main_window, world, unit_conversion_enabled, scale_normalization_enabled):
"""添加自定义菜单选项"""
# 添加FBX测试菜单
fbx_menu = main_window.menuBar().addMenu('FBX测试')
# 切换单位转换
toggle_unit_action = fbx_menu.addAction('切换单位转换 (U)')
toggle_unit_action.triggered.connect(lambda: toggle_unit_conversion())
# 切换缩放标准化
toggle_scale_action = fbx_menu.addAction('切换缩放标准化 (N)')
toggle_scale_action.triggered.connect(lambda: toggle_scale_normalization())
fbx_menu.addSeparator()
# 显示当前设置
settings_action = fbx_menu.addAction('显示当前设置 (S)')
settings_action.triggered.connect(lambda: print_current_settings(unit_conversion_enabled, scale_normalization_enabled))
# 显示导入信息
info_action = fbx_menu.addAction('显示导入信息 (I)')
info_action.triggered.connect(lambda: print_import_info(world))
# 射线显示切换
ray_action = fbx_menu.addAction('切换射线显示 (R)')
ray_action.triggered.connect(lambda: world.toggleRayDisplay())
fbx_menu.addSeparator()
# 手动标准化当前模型
normalize_action = fbx_menu.addAction('手动标准化当前模型缩放')
normalize_action.triggered.connect(lambda: manual_normalize_current_models(world))
# 重置所有模型缩放
reset_action = fbx_menu.addAction('重置所有模型缩放为1.0')
reset_action.triggered.connect(lambda: reset_all_model_scales(world))
def toggle_unit_conversion():
nonlocal unit_conversion_enabled
unit_conversion_enabled = not unit_conversion_enabled
status = "开启" if unit_conversion_enabled else "关闭"
print(f"\n=== 单位转换模式已{status} ===")
def toggle_scale_normalization():
nonlocal scale_normalization_enabled
scale_normalization_enabled = not scale_normalization_enabled
status = "开启" if scale_normalization_enabled else "关闭"
print(f"\n=== 缩放标准化模式已{status} ===")
def print_model_structure(model, depth=0, max_depth=5, world=None):
"""打印模型的层级结构、缩放和位置信息"""
if depth > max_depth:
return
indent = " " * depth
scale = model.getScale()
local_pos = model.getPos()
# 如果有world引用显示世界位置
if world:
world_pos = model.getPos(world.render)
pos_info = f"本地{local_pos} / 世界{world_pos}"
else:
pos_info = f"{local_pos}"
# 计算最大缩放分量
max_scale = max(abs(scale.x), abs(scale.y), abs(scale.z))
scale_status = "🔴大" if max_scale > 10 else "🟢正常"
print(f"{indent}📦 {model.getName()}")
print(f"{indent} 缩放: {scale} {scale_status}")
print(f"{indent} 位置: {pos_info}")
print(f"{indent} 类型: {model.node().__class__.__name__}")
# 递归打印重要子节点(限制数量避免输出过多)
child_count = model.getNumChildren()
max_children_to_show = 3 if depth == 0 else 2
for i in range(min(child_count, max_children_to_show)):
child = model.getChild(i)
print_model_structure(child, depth + 1, max_depth, world)
if child_count > max_children_to_show:
print(f"{indent} ... 还有 {child_count - max_children_to_show} 个子节点")
def print_import_info(world):
"""打印当前导入的模型信息"""
print("\n" + "="*60)
print("🔍 当前场景中的模型信息")
print("="*60)
if not world.models:
print("❌ 场景中没有模型")
return
for i, model in enumerate(world.models):
print(f"\n📦 模型 {i+1}: {model.getName()}")
print(f" 文件: {model.getTag('file') if model.hasTag('file') else '未知'}")
print(f" 单位转换: {'✅是' if model.hasTag('unit_conversion_applied') else '❌否'}")
print(f" 缩放标准化: {'✅是' if model.hasTag('scale_normalization_applied') else '❌否'}")
print(f" 根节点位置: {model.getPos()}")
print(f" 根节点缩放: {model.getScale()}")
print(f" 子节点数量: {model.getNumChildren()}")
# 检查子节点的缩放情况
large_scale_children = 0
if model.getNumChildren() > 0:
print(" 📋 子节点缩放概况:")
for j in range(min(5, model.getNumChildren())): # 只检查前5个子节点
child = model.getChild(j)
child_scale = child.getScale()
max_scale = max(abs(child_scale.x), abs(child_scale.y), abs(child_scale.z))
if max_scale > 10:
large_scale_children += 1
status = "🔴大缩放"
else:
status = "🟢正常"
print(f" {child.getName()}: {child_scale} {status}")
if model.getNumChildren() > 5:
print(f" ... 还有 {model.getNumChildren() - 5} 个子节点")
if large_scale_children > 0:
print(f" ⚠️ 发现 {large_scale_children} 个子节点有大缩放值")
print("="*60)
def print_current_settings(unit_conversion_enabled, scale_normalization_enabled):
"""显示当前导入设置"""
print("\n" + "="*40)
print("⚙️ 当前FBX导入设置")
print("="*40)
print(f"单位转换 (U键): {'🟢开启' if unit_conversion_enabled else '🔴关闭'}")
print(f"缩放标准化 (N键): {'🟢开启' if scale_normalization_enabled else '🔴关闭'}")
print("\n说明:")
print("• 单位转换: 将FBX的厘米单位转换为米")
print("• 缩放标准化: 自动处理子节点的大缩放值(如100)")
print("="*40)
def manual_normalize_current_models(world):
"""手动标准化当前所有模型的缩放"""
print("\n🔧 手动标准化所有模型缩放...")
if not world.models:
print("❌ 没有模型需要处理")
return
for model in world.models:
print(f"\n处理模型: {model.getName()}")
world.scene_manager._normalizeModelScales(model)
print("✅ 手动标准化完成")
def reset_all_model_scales(world):
"""重置所有模型的缩放为1.0(调试用)"""
print("\n🔄 重置所有模型缩放为1.0...")
count = 0
for model in world.models:
# 递归重置所有节点
reset_node_scale_recursive(model)
count += 1
print(f"✅ 已重置 {count} 个模型的所有节点缩放")
def reset_node_scale_recursive(node, depth=0):
"""递归重置节点缩放"""
indent = " " * depth
node.setScale(1.0, 1.0, 1.0)
print(f"{indent}重置 {node.getName()}")
for i in range(node.getNumChildren()):
child = node.getChild(i)
reset_node_scale_recursive(child, depth + 1)
def print_usage_instructions():
"""打印使用说明"""
print("\n" + "="*60)
print("🚀 FBX导入测试启动完成")
print("="*60)
print("🎯 主要改进:")
print("✅ 智能缩放标准化 - 自动处理子节点大缩放值")
print("✅ 保持模型原有缩放结构")
print("✅ 避免根节点0.01 + 子节点100的复杂层级")
print("✅ 缩放时保持世界位置不变 - 修复位置偏移问题")
print("✅ 提供灵活的导入选项")
print("")
print("⌨️ 键盘快捷键:")
print("• U键 - 切换单位转换模式")
print("• N键 - 切换缩放标准化模式")
print("• S键 - 显示当前设置")
print("• I键 - 显示模型信息")
print("• R键 - 切换射线显示")
print("")
print("📁 导入方式:")
print("• 拖拽FBX文件到3D场景")
print("• 使用菜单 [文件] -> [导入模型]")
print("• 使用菜单 [FBX测试] 查看更多选项")
print("")
print("🎛️ 缩放处理模式:")
print("• 关闭单位转换 + 开启缩放标准化(推荐)")
print(" → 保持FBX结构但标准化大缩放值")
print("• 开启单位转换 + 关闭缩放标准化")
print(" → 传统方式应用0.01根缩放")
print("• 两者都关闭")
print(" → 完全保持原始FBX结构")
print("")
print("📊 当前设置:")
print("• 单位转换: 🔴关闭")
print("• 缩放标准化: 🟢开启 (推荐)")
print("="*60)
if __name__ == "__main__":
try:
app, main_window, world = setup_fbx_import_demo()
# 启动应用程序
sys.exit(app.exec_())
except Exception as e:
print(f"❌ 启动失败: {str(e)}")
import traceback
traceback.print_exc()