"""资产数据库汇总脚本。 读取 asset-catalog 的扫描结果(meshes/*.json),汇总为统一的单文件 asset_database.json,以资产名为 key 扁平化存储。 此脚本不依赖 UE5,可离线运行。 用法: python build_database.py [--input DIR] [--output FILE] --input 扫描输出目录,默认 tools/asset-catalog/outputs --output 输出文件路径,默认 /asset_database.json """ import sys import argparse from pathlib import Path _SCRIPT_DIR = Path(__file__).resolve().parent if str(_SCRIPT_DIR) not in sys.path: sys.path.insert(0, str(_SCRIPT_DIR)) from _common import read_json, write_json, iso_now TAG = "[BuildDB]" def build_database(input_dir: Path, output_file: Path) -> dict: """汇总 meshes/*.json 为统一数据库。 Args: input_dir: 包含 meshes/ 子目录的扫描输出根目录 output_file: 输出的 asset_database.json 路径 Returns: 统计信息 dict """ meshes_dir = input_dir / "meshes" if not meshes_dir.exists(): print(f"{TAG} 错误: {meshes_dir} 不存在,请先运行 analyze_meshes") return {"status": "error", "reason": "no meshes dir"} # 收集所有子目录的扫描文件 mesh_files = sorted(meshes_dir.glob("*.json")) if not mesh_files: print(f"{TAG} 错误: {meshes_dir} 下无 JSON 文件") return {"status": "error", "reason": "no mesh files"} print(f"{TAG} 发现 {len(mesh_files)} 个扫描文件") # 扁平化合并 assets = {} total = 0 skipped = 0 for mf in mesh_files: try: data = read_json(mf) except Exception as e: print(f"{TAG} 警告: 读取 {mf.name} 失败: {e}") continue subfolder = data.get("subfolder", mf.stem) meshes = data.get("meshes", {}) if not meshes: continue for name, mesh_data in meshes.items(): total += 1 # 跳过无包围盒的(加载失败的) if mesh_data.get("bounding_box") is None: skipped += 1 continue # 构建统一条目 entry = { "ue_path": mesh_data.get("ue_path", ""), "subfolder": subfolder, "bounding_box": mesh_data.get("bounding_box"), "pivot": mesh_data.get("pivot"), "mesh_complexity": mesh_data.get("mesh_complexity", {}), "material_slots": mesh_data.get("material_slots", []), "sockets": mesh_data.get("sockets", []), "tags": { "style": mesh_data.get("style_tags", []), "scene": mesh_data.get("scene_tags", []), }, "ai_inference": None, } # 如果资产名重复(不同子目录下同名),加后缀 key = name if key in assets: safe_sub = subfolder.replace("/", "_").replace("\\", "_") key = f"{name}_{safe_sub}" print(f"{TAG} 注意: 资产名重复 {name},使用 {key}") assets[key] = entry # 写入数据库 db = { "metadata": { "generated_at": iso_now(), "source": "asset_catalog_v1", "total_assets": len(assets), "skipped_no_bbox": skipped, "input_files": [mf.name for mf in mesh_files], }, "assets": assets, } write_json(db, output_file) print(f"{TAG} ✅ 汇总完成: {len(assets)} 个资产 → {output_file}") print(f"{TAG} 跳过 {skipped} 个无包围盒资产 / 总计 {total}") return { "status": "ok", "total": total, "assets": len(assets), "skipped": skipped, } def main(): parser = argparse.ArgumentParser(description="汇总资产扫描数据为统一 JSON 数据库") parser.add_argument( "--input", type=str, default=str(_SCRIPT_DIR / "outputs"), help="扫描输出目录 (默认: tools/asset-catalog/outputs)", ) parser.add_argument( "--output", type=str, default=None, help="输出文件路径 (默认: /asset_database.json)", ) args = parser.parse_args() input_dir = Path(args.input) output_file = Path(args.output) if args.output else input_dir / "asset_database.json" result = build_database(input_dir, output_file) if result.get("status") != "ok": sys.exit(1) if __name__ == "__main__": main()