"""离线资产验证模块。 检查解析数据中的资产引用是否有效: 1. asset_key 是否在 asset_mapping 中定义 2. 材质名是否在 material_mapping 中定义 3. Content/ 目录下 .uasset 文件是否存在(可选) """ import logging from pathlib import Path from dataclasses import dataclass from config.asset_mapping import MESH_ASSETS from config.material_mapping import MATERIAL_PATHS logger = logging.getLogger("level_builder.validator") @dataclass class ValidationResult: """验证结果。""" total_actors: int = 0 valid_actors: int = 0 warnings: list[str] = None errors: list[str] = None missing_assets: set[str] = None missing_materials: set[str] = None def __post_init__(self): if self.warnings is None: self.warnings = [] if self.errors is None: self.errors = [] if self.missing_assets is None: self.missing_assets = set() if self.missing_materials is None: self.missing_materials = set() @property def is_valid(self) -> bool: return len(self.errors) == 0 def validate_actors(actors: list[dict], project_root: str = "") -> ValidationResult: """验证 Actor 列表中的资产引用。 Args: actors: Actor 数据列表(来自解析器的 JSON 输出)。 project_root: 项目根目录路径(用于文件存在性检查)。 Returns: ValidationResult 包含所有验证信息。 """ result = ValidationResult(total_actors=len(actors)) for i, actor in enumerate(actors): name = actor.get("actor_name", f"") asset_key = actor.get("asset_key", "") material = actor.get("material", "") asset_type = actor.get("asset_type", "static_mesh") # 跳过非 mesh 类型(光照、PlayerStart 等) if asset_type != "static_mesh": result.valid_actors += 1 continue # 检查 asset_key if not asset_key: result.warnings.append(f"{name}: 无 asset_key(可能是光照/特殊 Actor)") result.valid_actors += 1 continue if asset_key not in MESH_ASSETS: result.errors.append(f"{name}: 未知资产 '{asset_key}'") result.missing_assets.add(asset_key) continue # 检查材质 if material and material not in MATERIAL_PATHS: result.warnings.append(f"{name}: 未知材质 '{material}'") result.missing_materials.add(material) # 检查 .uasset 文件存在性(如果提供了项目根目录) if project_root: mesh_info = MESH_ASSETS[asset_key] asset_path = mesh_info["path"] # /Game/BlockingStarterPack/Meshes/.../SM_Xxx.SM_Xxx → Content/BlockingStarterPack/Meshes/.../SM_Xxx.uasset fs_path = asset_path.replace("/Game/", "Content/").split(".")[0] + ".uasset" full_path = Path(project_root) / fs_path if not full_path.exists(): result.warnings.append(f"{name}: 资产文件不存在 '{fs_path}'") result.valid_actors += 1 # 汇总日志 if result.missing_assets: logger.error(f"缺失资产定义: {sorted(result.missing_assets)}") if result.missing_materials: logger.warning(f"缺失材质定义: {sorted(result.missing_materials)}") logger.info(f"验证完成: {result.valid_actors}/{result.total_actors} 有效, " f"{len(result.errors)} 错误, {len(result.warnings)} 警告") return result