#!/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()