"""Scene manager model and geometry operations."""

import os
import shutil
import time
import json
import aiohttp
import asyncio
import inspect
from pathlib import Path

from panda3d.core import (
    ModelPool, ModelRoot, Filename, NodePath, GeomNode, Material, Vec4, Vec3,
    MaterialAttrib, ColorAttrib, Point3, CollisionNode, CollisionSphere, CollisionBox,
    BitMask32, TransparencyAttrib, LColor, TransformState, RenderModeAttrib
)
from panda3d.egg import EggData, EggVertexPool
from direct.actor.Actor import Actor
from RenderPipelineFile.rpplugins.smaa.jitters import halton_seq
from scene import util
from scene.tree_roles import TREE_USER_ROLE

class SceneManagerModelMixin:
    def importModel(self, filepath, apply_unit_conversion=False, normalize_scales=True, auto_convert_to_glb=True):
        try:
            if not os.path.exists(filepath):
                print("文件不存在")
                return None

            filepath = util.normalize_model_path(filepath)
            original_filepath = filepath

            # # 在加载前设置忽略未知属性
            # from panda3d.core import ConfigVariableBool
            # ConfigVariableBool("model-cache-ignore-unknown-properties").setValue(True)
            #
            # # 清除可能存在的模型缓存
            # from panda3d.core import ModelPool
            # ModelPool.releaseAllModels()
            #
            # # 检查是否需要转换为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
            #         # 转换成功的消息已在控制台显示，不再弹窗提示
            #     else:
            #         print(f"⚠️ 转换失败，使用原始文件")

            model = self.world.loader.loadModel(filepath)
            if not model:
                print("加载模型失败")
                return None

            # 设置模型名称
            model_name = os.path.basename(filepath)
            # 确保名称有效
            if not model_name:
                model_name = "imported_model"
            model.setName(model_name)
            
            # 移除统一设置颜色的代码，因为它可能覆盖PBR纹理并导致模型在RenderPipeline中渲染异常纯黑
            # # model.setColor(0.8, 0.8, 0.8, 1.0) # 移除以防覆盖PBR纹理 
            
            # 将模型添加到场景
            model.reparentTo(self.world.render)

            # 设置模型名称
            model_name = os.path.basename(filepath)
            model.setName(model_name)


            # 保存原始路径和转换后的路径，处理跨平台路径问题
            # 确保路径在当前系统上有效
            normalized_filepath = filepath
            # 检查路径是否有效，如果无效则尝试修复
            if not os.path.exists(filepath):
                original_filepath = filepath
                # 尝试多种修复策略
                fixed = False
                
                # 策略1: 处理Linux风格路径在Windows上的问题
                if filepath.startswith('/') and ':' not in filepath:
                    # 提取文件名并尝试在当前目录查找
                    filename = os.path.basename(filepath)
                    potential_path = os.path.join(os.getcwd(), filename)
                    if os.path.exists(potential_path):
                        normalized_filepath = potential_path
                        fixed = True
                        
                # 策略2: 处理路径分隔符问题
                if not fixed:
                    # 尝试规范化路径
                    normalized_path = os.path.normpath(filepath)
                    if os.path.exists(normalized_path):
                        normalized_filepath = normalized_path
                        fixed = True
                        
                # 策略3: 在Resources目录中查找
                if not fixed:
                    # 尝试在Resources目录中查找文件
                    resources_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "Resources", "models")
                    potential_path = os.path.join(resources_path, filename)
                    if os.path.exists(potential_path):
                        normalized_filepath = potential_path
                        fixed = True
                        
                if fixed:
                    print(f"路径修复: {original_filepath} -> {normalized_filepath}")
                else:
                    print(f"[警告] 模型文件不存在: {filepath}，将尝试继续加载")
                    # 即使文件不存在，也保存路径，以便属性面板可以尝试修复它
            
            model.setTag("model_path", normalized_filepath)
            model.setTag("original_path", original_filepath)
            if normalized_filepath != original_filepath:
                model.setTag("converted_from", os.path.splitext(original_filepath)[1])
                model.setTag("converted_to_glb", "true")

            #特殊处理FBX模型
            if filepath.lower().endswith('.fbx'):
                print("检测到FBX模型，应用特殊处理...")

                # 将模型缩放设置为原来的1/100
                model.setScale(0.01)
                print("设置模型缩放为 0.01 (原始大小的1/100)")

                # 设置模型旋转为 (0, 90, 0)
                model.setHpr(0, 90, 0)
                print("设置模型旋转为 (0, 90, 0)")

            # 调整模型位置到地面
            model.setPos(0,0,0)
            #self._adjustModelToGround(model)

            # 创建并设置基础材质
            #print("\n=== 开始设置材质 ===")
            self._fixBlackMaterials(model)

            # 设置碰撞检测（重要！用于选择功能）
            print("\n=== 设置碰撞检测 ===")
            self.setupCollision(model)

            # 添加文件标签用于保存/加载
            model.setTag("file", model_name)
            model.setTag("is_model_root", "1")
            model.setTag("is_scene_element", "1")
            model.setTag("tree_item_type", "IMPORTED_MODEL_NODE")

            # 记录应用的处理选项
            if apply_unit_conversion:
                model.setTag("unit_conversion_applied", "true")
            if normalize_scales:
                model.setTag("scale_normalization_applied", "true")

            # 初始化动画标签，避免属性面板首次读取时误判“无动画”
            try:
                self._processModelAnimations(model)
            except Exception as e:
                print(f"初始化模型动画标签失败: {e}")

            # 添加到模型列表
            self.models.append(model)

            # 更新场景树
            # 获取树形控件并添加到Qt树中
            tree_widget = self._get_tree_widget()
            if tree_widget:
                # 找到根节点项
                root_item = None
                for i in range(tree_widget.topLevelItemCount()):
                    item = tree_widget.topLevelItem(i)
                    if item.text(0) == "render" or item.data(0, TREE_USER_ROLE) == self.world.render:
                        root_item = item
                        break

                if root_item:
                    qt_item = tree_widget.add_node_to_tree_widget(model, root_item, "IMPORTED_MODEL_NODE")
                    if qt_item:
                        #tree_widget.setCurrentItem(qt_item)
                        # 更新选择和属性面板
                        #tree_widget.update_selection_and_properties(model, qt_item)
                        print("✅ Qt树节点添加成功")
                    else:
                        print("⚠️ Qt树节点添加失败，但Panda3D对象已创建")
                else:
                    print("⚠️ 未找到根节点项，无法添加到Qt树")
            #self.updateSceneTree()

            print(f"=== 模型导入成功: {model_name} ===\n")
            return model

        except Exception as e:
            print(f"导入模型失败: {str(e)}")
            return None

    def _fixModelStructure(self, model):
        """修复模型结构"""
        try:
            # 使用正确的方式查找动画相关节点
            character_nodes = model.findAllMatches("**/+Character")
            anim_bundle_nodes = model.findAllMatches("**/+AnimBundleNode")

            if character_nodes.getNumPaths() > 0 or anim_bundle_nodes.getNumPaths() > 0:
                print(f"检测到模型{model.getName()}包含角色相节点:")
                if character_nodes.getNumPaths() > 0:
                    print(f"CharacterNode数量：{character_nodes.getNumPaths()}")
                if anim_bundle_nodes.getNumPaths() > 0:
                    print(f"AnimBundleNode数量: {anim_bundle_nodes.getNumPaths()}")

                model.setTag("fixed_structure", "true")
            return True
        except Exception as e:
            print(f"修复模型结构时出错: {e}")
            return False

    def _validateAndFixAllTransforms(self, model):
        """递归验证并修复模型中所有节点的变换矩阵"""
        try:
            fixed_count = 0

            # 先处理根节点
            if not self._validateAndFixTransform(model):
                fixed_count += 1

            # 递归处理所有子节点
            def process_children(node, depth=0):
                nonlocal fixed_count
                for i in range(node.getNumChildren()):
                    try:
                        child = node.getChild(i)
                        if not self._validateAndFixTransform(child):
                            fixed_count += 1
                        # 递归处理孙节点
                        process_children(child, depth + 1)
                    except Exception as e:
                        print(f"处理子节点时出错 (深度 {depth}): {e}")
                        continue

            process_children(model)

            if fixed_count > 0:
                print(f"共修复了 {fixed_count} 个节点的变换")

            return True
        except Exception as e:
            print(f"验证所有变换时出错: {e}")
            return False

    def _validateAndFixTransform(self, node_path):
        """验证并修复单个节点的变换矩阵"""
        try:
            node_name = node_path.getName()

            # 获取当前变换状态
            original_pos = node_path.getPos()
            original_hpr = node_path.getHpr()
            original_scale = node_path.getScale()

            # 检查位置是否包含无效值
            if not original_pos.isFinite():
                print(f"警告: 节点 {node_name} 位置包含无效值 {original_pos}，重置为 (0,0,0)")
                node_path.setPos(0, 0, 0)
                return False

            # 检查旋转是否包含无效值
            if not original_hpr.isFinite():
                print(f"警告: 节点 {node_name} 旋转包含无效值 {original_hpr}，重置为 (0,0,0)")
                node_path.setHpr(0, 0, 0)
                return False

            # 检查缩放是否包含无效值或为零
            if not original_scale.isFinite():
                print(f"警告: 节点 {node_name} 缩放包含无效值 {original_scale}，重置为 (1,1,1)")
                node_path.setScale(1, 1, 1)
                return False

            # 检查缩放是否为零或接近零
            min_scale = 1e-10
            if (abs(original_scale.x) < min_scale or
                    abs(original_scale.y) < min_scale or
                    abs(original_scale.z) < min_scale):
                print(f"警告: 节点 {node_name} 缩放接近零 {original_scale}，重置为 (1,1,1)")
                node_path.setScale(1, 1, 1)
                return False

            # 检查缩放是否过大（防止异常大的缩放）
            max_scale = 1000000  # 100万倍作为上限
            if (abs(original_scale.x) > max_scale or
                    abs(original_scale.y) > max_scale or
                    abs(original_scale.z) > max_scale):
                print(f"警告: 节点 {node_name} 缩放过异常 {original_scale}，重置为 (1,1,1)")
                node_path.setScale(1, 1, 1)
                return False

            return True

        except Exception as e:
            print(f"验证/修复节点 {node_path.getName()} 变换时出错: {e}")
            # 只在出现严重错误时才重置变换
            try:
                node_path.setPos(0, 0, 0)
                node_path.setHpr(0, 0, 0)
                node_path.setScale(1, 1, 1)
            except:
                pass
            return False

    def _applyModelScale(self, model, scale_factor):
        """应用模型特定缩放

        Args:
            model: 要缩放的模型
            scale_factor: 缩放因子
        """
        try:
            print(f"应用模型缩放因子: {scale_factor}")

            # 获取当前边界用于后续位置调整
            original_bounds = model.getBounds()

            # 应用缩放
            model.setScale(scale_factor)

            # 重新调整位置（因为缩放会影响边界）
            if original_bounds and not original_bounds.isEmpty():
                new_bounds = model.getBounds()
                min_point = new_bounds.getMin()
                ground_offset = -min_point.getZ()
                model.setZ(ground_offset)
                print(f"缩放后重新调整位置: Z偏移 = {ground_offset}")

            print(f"模型缩放完成，缩放因子: {scale_factor}")

        except Exception as e:
            print(f"应用模型缩放失败: {str(e)}")

    def _applyMaterialsToModel(self, model):
        """递归应用材质到模型的所有GeomNode"""

        def apply_material(node_path, depth=0):
            indent = "  " * depth
            try:
                #print(f"{indent}处理节点: {node_path.getName()}")
                #print(f"{indent}节点类型: {node_path.node().__class__.__name__}")

                if isinstance(node_path.node(), GeomNode):
                    #print(f"{indent}发现GeomNode，处理材质")
                    geom_node = node_path.node()

                    # 检查所有几何体的状态
                    has_color = False
                    color = None

                    # 首先检查节点自身的状态
                    node_state = node_path.getState()
                    if node_state.hasAttrib(MaterialAttrib.getClassType()):
                        mat_attrib = node_state.getAttrib(MaterialAttrib.getClassType())
                        node_material = mat_attrib.getMaterial()
                        if node_material:
                            if node_material.hasBaseColor():
                                color = node_material.getBaseColor()
                                has_color = True
                                #print(f"{indent}从节点材质获取基础颜色: {color}")
                            elif node_material.hasDiffuse():
                                color = node_material.getDiffuse()
                                has_color = True
                                #print(f"{indent}从节点材质获取漫反射颜色: {color}")

                    # 检查几何体材质
                    if not has_color:
                        for i in range(geom_node.getNumGeoms()):
                            try:
                                geom = geom_node.getGeom(i)
                                state = geom_node.getGeomState(i)

                                # 检查材质属性
                                if state.hasAttrib(MaterialAttrib.getClassType()):
                                    mat_attrib = state.getAttrib(MaterialAttrib.getClassType())
                                    orig_material = mat_attrib.getMaterial()
                                    if orig_material:
                                        if orig_material.hasBaseColor():
                                            color = orig_material.getBaseColor()
                                            has_color = True
                                            #print(f"{indent}从几何体材质获取基础颜色: {color}")
                                            break
                                        elif orig_material.hasDiffuse():
                                            color = orig_material.getDiffuse()
                                            has_color = True
                                            #print(f"{indent}从几何体材质获取漫反射颜色: {color}")
                                            break

                                # 检查颜色属性
                                if not has_color and state.hasAttrib(ColorAttrib.getClassType()):
                                    color_attrib = state.getAttrib(ColorAttrib.getClassType())
                                    if not color_attrib.isOff():
                                        color = color_attrib.getColor()
                                        has_color = True
                                        #print(f"{indent}从颜色属性获取: {color}")
                                        break
                            except Exception as geom_error:
                                print(f"{indent}处理几何体 {i} 时出错: {geom_error}")
                                continue

                    # 创建新材质
                    material = Material()
                    if has_color and color:
                        #print(f"{indent}应用找到的颜色: {color}")
                        try:
                            # 确保颜色值有效
                            if (color.getX() == color.getX() and color.getY() == color.getY() and
                                    color.getZ() == color.getZ() and color.getW() == color.getW()):
                                material.setBaseColor(color)
                                material.setDiffuse(color)
                                node_path.setColor(color)
                            else:
                                print(f"{indent}⚠️  颜色值无效，使用默认颜色")
                                material.setBaseColor((0.8, 0.8, 0.8, 1.0))
                                material.setDiffuse((0.8, 0.8, 0.8, 1.0))
                        except Exception as color_error:
                            print(f"{indent}设置颜色时出错: {color_error}")
                            material.setBaseColor((0.8, 0.8, 0.8, 1.0))
                            material.setDiffuse((0.8, 0.8, 0.8, 1.0))
                    else:
                        print(f"{indent}使用默认颜色")
                        material.setBaseColor((0.8, 0.8, 0.8, 1.0))
                        material.setDiffuse((0.8, 0.8, 0.8, 1.0))

                    # 设置其他材质属性
                    material.setAmbient((0.2, 0.2, 0.2, 1.0))
                    material.setSpecular((0.5, 0.5, 0.5, 1.0))
                    material.setShininess(32.0)

                    # 应用材质
                    try:
                        node_path.setMaterial(material, 1)  # 1表示强制应用
                        #print(f"{indent}材质应用成功")
                    except Exception as mat_error:
                        print(f"{indent}⚠️  应用材质时出错: {mat_error}")

                    #print(f"{indent}几何体数量: {geom_node.getNumGeoms()}")

            except Exception as node_error:
                print(f"{indent}处理节点 {node_path.getName()} 时出错: {node_error}")

            # 递归处理子节点
            child_count = node_path.getNumChildren()
            #print(f"{indent}子节点数量: {child_count}")
            for i in range(child_count):
                try:
                    child = node_path.getChild(i)
                    apply_material(child, depth + 1)
                except Exception as child_error:
                    print(f"{indent}处理子节点 {i} 时出错: {child_error}")
                    continue

        # 应用材质
        #print("\n开始递归应用材质...")
        try:
            apply_material(model)
        except Exception as e:
            print(f"应用材质时出错: {e}")
        print("=== 材质设置完成 ===\n")


    def _fixBlackMaterials(self, model):
        # 修复模型中全黑的材质问题，同时为缺失材质的几何体添加默认材质，保留原有纹理
        try:
            from panda3d.core import MaterialAttrib, Material, GeomNode
            
            for geom_path in model.findAllMatches('**/+GeomNode'):
                geom_node = geom_path.node()
                
                # 级联节点状态
                node_state = geom_path.getState()
                if node_state.hasAttrib(MaterialAttrib.getClassType()):
                    mat_attrib = node_state.getAttrib(MaterialAttrib.getClassType())
                    mat = mat_attrib.getMaterial()
                    if mat:
                        is_black = False
                        if mat.hasBaseColor():
                            c = mat.getBaseColor()
                            if c.x <= 0.05 and c.y <= 0.05 and c.z <= 0.05:
                                is_black = True
                        elif mat.hasDiffuse():
                            c = mat.getDiffuse()
                            if c.x <= 0.05 and c.y <= 0.05 and c.z <= 0.05:
                                is_black = True
                        
                        if is_black or not (mat.hasBaseColor() or mat.hasDiffuse()):
                            new_mat = Material(mat)
                            new_mat.setBaseColor((0.8, 0.8, 0.8, 1.0))
                            new_mat.setDiffuse((0.8, 0.8, 0.8, 1.0))
                            geom_path.setState(node_state.setAttrib(MaterialAttrib.make(new_mat)))
                
                # 几何体状态
                for i in range(geom_node.getNumGeoms()):
                    geom_state = geom_node.getGeomState(i)
                    if geom_state.hasAttrib(MaterialAttrib.getClassType()):
                        mat_attrib = geom_state.getAttrib(MaterialAttrib.getClassType())
                        mat = mat_attrib.getMaterial()
                        if mat:
                            is_black = False
                            if mat.hasBaseColor():
                                c = mat.getBaseColor()
                                if c.x <= 0.05 and c.y <= 0.05 and c.z <= 0.05:
                                    is_black = True
                            elif mat.hasDiffuse():
                                c = mat.getDiffuse()
                                if c.x <= 0.05 and c.y <= 0.05 and c.z <= 0.05:
                                    is_black = True
                                    
                            if is_black or not (mat.hasBaseColor() or mat.hasDiffuse()):
                                new_mat = Material(mat)
                                new_mat.setBaseColor((0.8, 0.8, 0.8, 1.0))
                                new_mat.setDiffuse((0.8, 0.8, 0.8, 1.0))
                                geom_node.setGeomState(i, geom_state.setAttrib(MaterialAttrib.make(new_mat)))
                    else:
                        new_mat = Material()
                        new_mat.setBaseColor((0.8, 0.8, 0.8, 1.0))
                        new_mat.setDiffuse((0.8, 0.8, 0.8, 1.0))
                        new_mat.setSpecular((0.2, 0.2, 0.2, 1.0))
                        new_mat.setRoughness(0.8)
                        geom_node.setGeomState(i, geom_state.addAttrib(MaterialAttrib.make(new_mat)))

            model.clearColor()
        except Exception as e:
            print(f'修复黑色模型材质时出错: {e}')

    def _adjustModelToGround(self, model):
        """智能调整模型到地面，但保持原有缩放结构"""
        try:
            #print("调整模型位置到地面...")

            # 获取模型的边界框
            bounds = model.getBounds()
            if not bounds or bounds.isEmpty():
                print("无法获取模型边界，使用默认位置")
                model.setPos(0, 0, 0)
                return

            # 获取边界框的最低点
            min_point = bounds.getMin()
            center = bounds.getCenter()

            # 计算需要移动的距离，使模型底部贴合地面（Z=0）
            # 这里不涉及缩放，只是简单的位置调整
            ground_offset = -min_point.getZ()

            # 设置模型位置：X,Y居中，Z调整到地面
            model.setPos(0, 0, ground_offset)

            #print(f"模型边界: 最小点{min_point}, 中心{center}")
            #print(f"地面偏移: {ground_offset}")
            #print(f"最终位置: {model.getPos()}")

        except Exception as e:
            print(f"调整模型位置失败: {str(e)}")
            # 失败时使用默认位置
            model.setPos(0, 0, 0)

    def _normalizeModelScales(self, model):
        """智能标准化模型缩放层级

        检测并修复FBX模型中子节点的大缩放值问题
        """
        try:
            print("开始分析模型缩放结构...")

            # 收集所有节点的缩放信息
            scale_info = []
            self._collectScaleInfo(model, scale_info)

            if not scale_info:
                print("没有找到需要处理的缩放信息")
                return

            # 分析缩放模式
            large_scales = [info for info in scale_info if max(abs(info['scale'].x), abs(info['scale'].y), abs(info['scale'].z)) > 10]

            if not large_scales:
                print("没有发现大缩放值，无需标准化")
                return

            print(f"发现 {len(large_scales)} 个节点有大缩放值")

            # 计算标准化因子（基于最常见的大缩放值）
            common_large_scale = self._findCommonLargeScale(large_scales)
            if common_large_scale:
                normalize_factor = 1.0 / common_large_scale
                print(f"检测到常见大缩放值: {common_large_scale}, 标准化因子: {normalize_factor}")

                # 应用标准化
                self._applyScaleNormalization(model, normalize_factor)
                print("✓ 缩放标准化完成")
            else:
                print("无法确定合适的标准化因子，跳过标准化")

        except Exception as e:
            print(f"缩放标准化失败: {str(e)}")

    def _collectScaleInfo(self, node, scale_info, depth=0):
        """递归收集节点缩放信息"""
        try:
            scale = node.getScale()
            scale_info.append({
                'node': node,
                'name': node.getName(),
                'scale': scale,
                'depth': depth
            })

            # 递归处理子节点
            for i in range(node.getNumChildren()):
                child = node.getChild(i)
                self._collectScaleInfo(child, scale_info, depth + 1)

        except Exception as e:
            print(f"收集缩放信息失败 ({node.getName()}): {str(e)}")

    def _findCommonLargeScale(self, large_scales):
        """找到最常见的大缩放值"""
        try:
            # 提取缩放值（取绝对值的最大分量）
            scale_values = []
            for info in large_scales:
                scale = info['scale']
                max_scale = max(abs(scale.x), abs(scale.y), abs(scale.z))
                scale_values.append(round(max_scale))  # 四舍五入到整数

            if not scale_values:
                return None

            # 找到最常见的值
            from collections import Counter
            counter = Counter(scale_values)
            most_common = counter.most_common(1)[0]

            print(f"缩放值统计: {dict(counter)}")
            print(f"最常见的大缩放值: {most_common[0]} (出现{most_common[1]}次)")

            # 只有当最常见的值确实很大时才返回
            if most_common[0] >= 10:
                return float(most_common[0])

            return None

        except Exception as e:
            print(f"分析常见缩放值失败: {str(e)}")
            return None

    def _applyScaleNormalization(self, node, normalize_factor, depth=0):
        """
        安全地应用缩放标准化
        """
        try:
            indent = "  " * depth
            current_scale = node.getScale()
            current_pos = node.getPos()

            # 检查是否需要标准化（只处理明显的大缩放）
            max_scale_component = max(abs(current_scale.x), abs(current_scale.y), abs(current_scale.z))

            if max_scale_component > 10:  # 只标准化明显的大缩放
                # 确保标准化因子有效
                if normalize_factor <= 0 or normalize_factor > 1000:
                    print(f"{indent}无效的标准化因子: {normalize_factor}，跳过")
                    return

                # 应用新的缩放
                new_scale = current_scale * normalize_factor

                # 检查新缩放是否有效
                if any(s <= 0 for s in [new_scale.x, new_scale.y, new_scale.z]):
                    print(f"{indent}标准化后产生无效缩放，跳过")
                    return

                node.setScale(new_scale)

                # 同时调整位置：当缩放变小时，位置也应该相应变小以保持视觉相对位置
                # 这确保了子节点之间的相对距离在视觉上保持一致
                new_pos = current_pos * normalize_factor
                node.setPos(new_pos)

                print(f"{indent}标准化 {node.getName()}:")
                print(f"{indent}  缩放: {current_scale} -> {new_scale}")
                print(f"{indent}  位置: {current_pos} -> {new_pos}")

        except Exception as e:
            print(f"应用缩放标准化失败 ({node.getName()}): {str(e)}")

    def importModelAsync(self, filepath):
        """异步导入模型"""
        try:
            # 创建异步加载请求
            request = self.world.loader.makeAsyncRequest(filepath)

            # 添加完成回调
            def modelLoaded(task):
                if task.isReady():
                    model = task.result()
                    if model:
                        # 处理加载完成的模型
                        self.processLoadedModel(model)
                return task.done()

            request.done_event = modelLoaded

            # 开始异步加载
            self.world.loader.loadAsync(request)

        except Exception as e:
            print(f"异步加载模型失败: {str(e)}")

    def processMaterials(self, model):
        """处理模型材质"""
        if isinstance(model.node(), GeomNode):
            # 创建基础材质
            material = Material()
            material.setAmbient((0.2, 0.2, 0.2, 1.0))
            material.setDiffuse((0.8, 0.8, 0.8, 1.0))
            material.setSpecular((0.5, 0.5, 0.5, 1.0))
            material.setShininess(32.0)

            # 检查FBX材质
            state = model.node().getGeomState(0)
            if state.hasAttrib(MaterialAttrib.getClassType()):
                fbx_material = state.getAttrib(MaterialAttrib.getClassType()).getMaterial()
                if fbx_material:
                    # 复制FBX材质属性
                    material.setAmbient(fbx_material.getAmbient())
                    material.setDiffuse(fbx_material.getDiffuse())
                    material.setSpecular(fbx_material.getSpecular())
                    material.setShininess(fbx_material.getShininess())

            # 应用材质
            model.setMaterial(material)

    def processModelGeometry(self, model):
        """处理模型几何体"""
        # 创建EggData对象
        egg_data = EggData()

        # 处理顶点数据
        vertex_pool = EggVertexPool("vpool")
        egg_data.addChild(vertex_pool)

        # 处理几何体
        if isinstance(model.node(), GeomNode):
            for i in range(model.node().getNumGeoms()):
                geom = model.node().getGeom(i)

    def setupCollision(self, model):
        """为模型设置碰撞检测（增强版本）"""
        try:
            # 创建碰撞节点
            cNode = CollisionNode(f'modelCollision_{model.getName()}')

            # 设置碰撞掩码
            cNode.setIntoCollideMask(BitMask32.bit(2))  # 用于鼠标选择

            # 如果启用了模型间碰撞检测，添加额外的掩码
            if (hasattr(self.world, 'collision_manager') and
                self.world.collision_manager.model_collision_enabled):
                # 同时设置模型间碰撞掩码
                current_mask = cNode.getIntoCollideMask()
                model_collision_mask = BitMask32.bit(6)  # MODEL_COLLISION
                cNode.setIntoCollideMask(current_mask | model_collision_mask)
                print(f"为 {model.getName()} 启用模型间碰撞检测")

            # 获取模型的边界信息，使用与选择框相同的计算方法
            minPoint = Point3()
            maxPoint = Point3()
            
            # 使用与选择框相同的calcTightBounds方法获取边界，但是在局部坐标系中进行计算
            # 这样计算出的包围盒直接贴合几何体，并且无论模型自身受到什么平移/缩放/旋转都不会发生两次形变！
            if model.calcTightBounds(minPoint, maxPoint, model):
                # 检查边界框的有效性
                if (abs(minPoint.x) < 1e10 and abs(minPoint.y) < 1e10 and abs(minPoint.z) < 1e10 and
                        abs(maxPoint.x) < 1e10 and abs(maxPoint.y) < 1e10 and abs(maxPoint.z) < 1e10):
                    
                    # 我们现在获取的是纯局部几何数据，因此不再需要手动乘以100或应用旋转来抵消FBX形变
                    
                    # 创建与选择框完全一致的碰撞体
                    cBox = CollisionBox(minPoint, maxPoint)
                    cNode.addSolid(cBox)
                    radius = max(maxPoint.x - minPoint.x, maxPoint.y - minPoint.y, maxPoint.z - minPoint.z) / 2
                else:
                    # 使用默认球体
                    radius = 1.0
                    cSphere = CollisionSphere(Point3(0, 0, 0), radius)
                    cNode.addSolid(cSphere)
            else:
                # 使用默认球体
                radius = 1.0
                cSphere = CollisionSphere(Point3(0, 0, 0), radius)
                cNode.addSolid(cSphere)

            # 将碰撞节点附加到模型上
            cNodePath = model.attachNewNode(cNode)

            # 根据调试设置决定是否显示碰撞体
            # if hasattr(self.world, 'debug_collision') and self.world.debug_collision:
            #     cNodePath.show()
            # else:
            #     cNodePath.hide()

            # 为模型添加碰撞相关标签
            model.setTag("has_collision", "true")
            model.setTag("collision_radius", str(radius))

            print(f"✅ 为模型 {model.getName()} 设置碰撞检测完成")

            return cNodePath

        except Exception as e:
            print(f"❌ 为模型 {model.getName()} 设置碰撞检测失败: {str(e)}")
            import traceback
            traceback.print_exc()
            return None

    def refreshCollisionBounds(self, model):
        """重新计算并更新模型的碰撞框"""
        try:
            if not model or model.isEmpty():
                return
            
            # 使用列表以便在遍历后安全删除
            children_to_remove = []
            for child in model.getChildren():
                name = child.getName() if hasattr(child, 'getName') else ""
                if name.startswith("modelCollision_"):
                    children_to_remove.append(child)
            
            # 如果存在旧碰撞节点，删除它并重新创建
            if children_to_remove:
                for child in children_to_remove:
                    child.removeNode()
                
                # 由于 calcTightBounds 被修改为使用局部坐标计算 (第三个参数传 model)
                # 无需再像之前一样为了解决偏移而将其重置回0点。
                self.setupCollision(model)

        except Exception as e:
            print(f"刷新碰撞框失败: {e}")

    def updateSceneTree(self):
        """更新场景树显示 - 代理到interface_manager"""
        if hasattr(self.world, 'interface_manager'):
            return self.world.interface_manager.updateSceneTree()
        else:
            print("界面管理器未初始化，无法更新场景树")

    def _get_script_file_path(self, script_class, script_name):
        """
        获取脚本文件路径的可靠方法
        """
        script_file = ""

        # 方法1: 使用 inspect.getfile
        try:
            script_file = inspect.getfile(script_class)
            if script_file and os.path.exists(script_file):
                return script_file
        except:
            pass

        # 方法2: 使用 __file__ 属性
        try:
            if hasattr(script_class, '__file__') and script_class.__file__:
                script_file = script_class.__file__
                if script_file and os.path.exists(script_file):
                    return script_file
        except:
            pass

        # 方法3: 使用模块的 __file__ 属性
        try:
            module = inspect.getmodule(script_class)
            if module and hasattr(module, '__file__') and module.__file__:
                script_file = module.__file__
                if script_file and os.path.exists(script_file):
                    return script_file
        except:
            pass

        # 方法4: 从脚本管理器中查找
        try:
            if hasattr(self.world, 'script_manager') and self.world.script_manager:
                script_manager = self.world.script_manager
                # 查找脚本类对应的文件路径
                for file_path, file_mtime in script_manager.loader.file_mtimes.items():
                    # 检查文件名是否匹配脚本名
                    file_name = os.path.splitext(os.path.basename(file_path))[0]
                    if file_name == script_name:
                        if os.path.exists(file_path):
                            return file_path
        except:
            pass

        # 方法5: 在脚本目录中查找
        try:
            if hasattr(self.world, 'script_manager') and self.world.script_manager:
                script_manager = self.world.script_manager
                scripts_dir = script_manager.scripts_directory

                # 查找匹配的脚本文件
                if os.path.exists(scripts_dir):
                    for file_name in os.listdir(scripts_dir):
                        if file_name.endswith('.py'):
                            base_name = os.path.splitext(file_name)[0]
                            if base_name == script_name:
                                full_path = os.path.join(scripts_dir, file_name)
                                if os.path.exists(full_path):
                                    return full_path
        except:
            pass

        print(f"警告: 无法获取脚本 {script_name} 的文件路径")
        return script_file

    def _restoreModelAnimationInfo(self, model_node):
        """恢复模型的动画信息"""
        try:
            # 从保存的标签中恢复动画信息
            if model_node.hasTag("saved_has_animations"):
                model_node.setTag("has_animations", model_node.getTag("saved_has_animations"))
                print(f"恢复模型 {model_node.getName()} 的动画信息")
            
            # 从保存的标签中恢复模型路径
            if model_node.hasTag("saved_model_path"):
                model_path = model_node.getTag("saved_model_path")
                # 处理跨平台路径问题
                if model_path:
                    # 将Linux风格路径转换为Windows风格路径（如果需要）
                    if model_path.startswith('/'):
                        # 尝试将其转换为Windows路径
                        if ':' not in model_path:  # 不是已经有效的Windows路径
                            # 简单处理：移除前导斜杠
                            model_path = model_path[1:] if len(model_path) > 1 else model_path
                    model_node.setTag("model_path", model_path)
                    
            # 恢复内存创建标记
            if model_node.hasTag("saved_can_create_actor_from_memory"):
                model_node.setTag("can_create_actor_from_memory", model_node.getTag("saved_can_create_actor_from_memory"))
                
        except Exception as e:
            print(f"恢复模型 {model_node.getName()} 动画信息时出错: {e}")

    def _processModelAnimations(self, model_node):
        """处理模型动画，确保在场景加载时正确识别动画信息"""
        try:
            # 已检测过则直接复用，避免重复开销
            if model_node.hasTag("has_animations_checked"):
                return model_node.hasTag("has_animations") and model_node.getTag("has_animations").lower() == "true"

            # 检查模型是否已经有动画信息标签（兼容旧数据）
            if model_node.hasTag("has_animations"):
                has_animations = model_node.getTag("has_animations").lower() == "true"
                if has_animations:
                    model_node.setTag("has_animations_checked", "true")
                    print(f"模型 {model_node.getName()} 已有动画信息")
                    return True

            # 检查模型是否包含动画相关节点
            character_nodes = model_node.findAllMatches("**/+Character")
            anim_bundle_nodes = model_node.findAllMatches("**/+AnimBundleNode")
            
            has_animations = (character_nodes.getNumPaths() > 0 or 
                            anim_bundle_nodes.getNumPaths() > 0)

            # 如果模型树中没检测到，再尝试通过 Actor 从文件路径检测
            if not has_animations:
                model_path = model_node.getTag("model_path") if model_node.hasTag("model_path") else ""
                if model_path:
                    normalized_model_path = model_path.replace("\\", "/").lower()
                    is_project_scene_bam = (
                        normalized_model_path.endswith(".bam")
                        and "/scenes/" in normalized_model_path
                    )
                    # Project scene BAM is an aggregated scene root, not a single actor asset.
                    # Probing it via Actor(...) is unstable and may crash native Panda3D loader.
                    if is_project_scene_bam:
                        model_node.setTag("has_animations", "false")
                        model_node.setTag("has_animations_checked", "true")
                        return False
                    try:
                        from direct.actor.Actor import Actor
                        from panda3d.core import Filename

                        candidate_paths = []
                        # Always prefer normalized Panda paths; avoid raw Windows path fallback,
                        # which triggers noisy loader(error) logs for some absolute/Unicode paths.
                        try:
                            normalized = util.normalize_model_path(model_path)
                            if normalized and normalized != model_path:
                                candidate_paths.append(normalized)
                        except Exception:
                            pass

                        for ctor_name in ("fromOsSpecificW", "from_os_specific_w", "fromOsSpecific", "from_os_specific"):
                            ctor = getattr(Filename, ctor_name, None)
                            if not ctor:
                                continue
                            try:
                                panda_path = ctor(model_path).get_fullpath()
                                if panda_path:
                                    candidate_paths.append(panda_path)
                            except Exception:
                                continue

                        seen = set()
                        unique_paths = []
                        for p in candidate_paths:
                            if not p or p in seen:
                                continue
                            seen.add(p)
                            unique_paths.append(p)

                        for candidate in unique_paths:
                            try:
                                actor = Actor(candidate)
                                anim_names = actor.getAnimNames()
                                actor.cleanup()
                                actor.removeNode()
                                if anim_names:
                                    print(f"通过 Actor 路径检测到动画: {candidate} -> {anim_names}")
                                    has_animations = True
                                    break
                            except Exception:
                                continue
                    except Exception:
                        pass
            
            if has_animations:
                print(f"检测到模型 {model_node.getName()} 包含动画:")
                if character_nodes.getNumPaths() > 0:
                    print(f"  CharacterNode数量: {character_nodes.getNumPaths()}")
                if anim_bundle_nodes.getNumPaths() > 0:
                    print(f"  AnimBundleNode数量: {anim_bundle_nodes.getNumPaths()}")
                
                # 保存动画信息到标签
                model_node.setTag("has_animations", "true")
                
                # 标记模型可以直接从内存中创建Actor
                model_node.setTag("can_create_actor_from_memory", "true")
            else:
                model_node.setTag("has_animations", "false")

            model_node.setTag("has_animations_checked", "true")
                
            return has_animations
            
        except Exception as e:
            print(f"处理模型 {model_node.getName()} 动画时出错: {e}")
            return False

    def _cleanupAuxiliaryNodes(self):
        """清理场景中可能存在的辅助节点"""
        try:
            # 检查world和render是否存在
            if not hasattr(self, 'world') or not self.world:
                print("world对象不存在，跳过辅助节点清理")
                return
                
            if not hasattr(self.world, 'render') or self.world.render.isEmpty():
                print("render节点不存在，跳过辅助节点清理")
                return

            # 查找并移除所有坐标轴节点
            gizmo_nodes = self.world.render.findAllMatches("**/gizmo*")
            for node in gizmo_nodes:
                if node and not node.isEmpty():
                    node.removeNode()
                    print(f"清理坐标轴节点: {node.getName()}")

            # 查找并移除所有选择框节点
            selection_box_nodes = self.world.render.findAllMatches("**/selectionBox*")
            for node in selection_box_nodes:
                if node and not node.isEmpty():
                    node.removeNode()
                    print(f"清理选择框节点: {node.getName()}")

            # 停止相关的更新任务
            try:
                from direct.task.TaskManagerGlobal import taskMgr
                taskMgr.remove("updateGizmo")
                taskMgr.remove("updateSelectionBox")
            except Exception as task_e:
                print(f"停止任务时出错: {task_e}")

            print("辅助节点清理完成")
        except Exception as e:
            print(f"清理辅助节点时出错: {e}")
            import traceback
            traceback.print_exc()

    def deleteModel(self, model):
        """删除模型"""
        try:
            if model in self.models:
                tree_widget = self._get_tree_widget()
                if not tree_widget:
                    return False

                tree_widget.delete_items(tree_widget.selectedItems())
                # model.removeNode()
                # self.models.remove(model)
                # self.updateSceneTree()
                print(f"删除模型: {model.getName()}")
                return True
        except Exception as e:
            print(f"删除模型失败: {str(e)}")
        return False

    def clearAllModels(self):
        """清除所有模型"""
        pass

    def getModels(self):
        """获取模型列表"""
        return self.models.copy()

    def getModelCount(self):
        """获取模型数量"""
        return len(self.models)

    def findModelByName(self, name):
        """根据名称查找模型"""
        for model in self.models:
            if model.getName() == name:
                return model
        return None

    def processLoadedModel(self, model):
        """处理加载完成的模型（用于异步加载回调）"""
        if model:
            # 添加到模型列表
            self.models.append(model)
            
            # 设置基础变换
            model.setPos(0, 0, 0)
            model.setHpr(0, 0, 0)
            model.setScale(1, 1, 1)
            
            # 应用材质
            self.processMaterials(model)
            
            # 设置碰撞检测
            self.setupCollision(model)
            
            # 更新场景树
            self.updateSceneTree()
            
            print(f"异步加载模型完成: {model.getName()}")
