9f6572600b
- discover_assets: 枚举Content资产,按UE5类型分类,输出index.json - analyze_meshes: 包围盒/锚点/bbox_faces(area+orientation)/Line Trace细节包围盒/规则Tags - analyze_materials: 动态参数提取/base_color_hex自动转换/规则Tags - analyze_blueprints: generated_class API Actor检测/组件提取/interaction推断Tags - run_all编排器: 断点续跑/步骤控制/错误traceback/UE5 JSON序列化兼容 - probe脚本: UE5.7 API探测(TraceTypeQuery/DrawDebugTrace枚举发现)
220 lines
6.8 KiB
Python
220 lines
6.8 KiB
Python
"""UE5 资产发现脚本。
|
|
|
|
扫描指定 Content 路径下所有资产,记录名称、路径、UE5 类型。
|
|
不做任何推断,仅记录 UE5 原始数据。
|
|
|
|
输出: output/index.json
|
|
|
|
用法:
|
|
交互模式: exec(open(r"...\discover_assets.py").read())
|
|
命令行: 通过 run_all.py 调用
|
|
"""
|
|
|
|
import sys
|
|
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 (
|
|
HAS_UNREAL, get_scan_root, get_output_dir, write_json, read_json,
|
|
wait_for_editor, progress_log, iso_now, safe_subfolder_name,
|
|
)
|
|
|
|
TAG = "[Discover]"
|
|
|
|
# 已知类型分类
|
|
MESH_TYPES = {"StaticMesh"}
|
|
MATERIAL_TYPES = {"MaterialInstanceConstant", "Material"}
|
|
# 其余全部归入 others
|
|
|
|
|
|
def _get_subfolder(asset_path: str, scan_root: str) -> str:
|
|
"""从资产完整路径中提取相对于 scan_root/Meshes|Materials|... 的子目录。
|
|
|
|
例:
|
|
path = "/Game/BlockingStarterPack/Meshes/Architecture/Walls/SM_Wall_400x300.SM_Wall_400x300"
|
|
scan_root = "/Game/BlockingStarterPack"
|
|
→ "Architecture/Walls"
|
|
"""
|
|
# 去掉末尾的 .XXX
|
|
base = asset_path.rsplit(".", 1)[0]
|
|
# 找到 scan_root 之后的路径
|
|
if base.startswith(scan_root + "/"):
|
|
relative = base[len(scan_root) + 1:]
|
|
else:
|
|
relative = base
|
|
|
|
# 去掉第一级目录和最后一级(资产名)
|
|
# Meshes/Architecture/Walls/SM_Wall_400x300 → Architecture/Walls
|
|
parts = relative.split("/")
|
|
if len(parts) > 2:
|
|
return "/".join(parts[1:-1]) # 去掉首段(Meshes 等)和末段(资产名)
|
|
return ""
|
|
|
|
|
|
def _get_asset_name(asset_path: str) -> str:
|
|
"""从 UE 资产路径提取资产名。
|
|
|
|
例: "/Game/.../SM_Wall_400x300.SM_Wall_400x300" → "SM_Wall_400x300"
|
|
"""
|
|
# 取最后一个 / 后、第一个 . 前
|
|
last_part = asset_path.rsplit("/", 1)[-1]
|
|
return last_part.rsplit(".", 1)[0]
|
|
|
|
|
|
def _load_and_classify(asset_path: str) -> dict:
|
|
"""加载资产并记录其 UE5 类型名。
|
|
|
|
Returns:
|
|
{"name": str, "ue_path": str, "ue_class_type": str}
|
|
加载失败时 ue_class_type 为 "LOAD_FAILED"
|
|
"""
|
|
name = _get_asset_name(asset_path)
|
|
try:
|
|
import unreal
|
|
obj = unreal.load_asset(asset_path)
|
|
if obj is None:
|
|
return {"name": name, "ue_path": asset_path, "ue_class_type": "LOAD_FAILED"}
|
|
class_name = type(obj).__name__
|
|
return {"name": name, "ue_path": asset_path, "ue_class_type": class_name}
|
|
except Exception as e:
|
|
return {"name": name, "ue_path": asset_path, "ue_class_type": f"ERROR:{e}"}
|
|
|
|
|
|
def run() -> dict:
|
|
"""执行资产发现。
|
|
|
|
Returns:
|
|
统计信息 dict
|
|
"""
|
|
if not HAS_UNREAL:
|
|
print(f"{TAG} 错误: unreal 模块不可用")
|
|
return {"status": "error", "reason": "no unreal"}
|
|
|
|
import unreal
|
|
|
|
scan_root = get_scan_root()
|
|
output_dir = get_output_dir()
|
|
|
|
print(f"{TAG} 扫描根目录: {scan_root}")
|
|
print(f"{TAG} 输出目录: {output_dir}")
|
|
|
|
# ---- 枚举所有资产 ----
|
|
print(f"{TAG} 开始枚举资产...")
|
|
all_assets = []
|
|
try:
|
|
asset_list = unreal.EditorAssetLibrary.list_assets(scan_root, recursive=True, include_folder=False)
|
|
all_assets = list(asset_list)
|
|
except Exception as e:
|
|
print(f"{TAG} 错误: list_assets 失败: {e}")
|
|
return {"status": "error", "reason": str(e)}
|
|
|
|
total = len(all_assets)
|
|
print(f"{TAG} 发现 {total} 个资产")
|
|
|
|
if total == 0:
|
|
print(f"{TAG} 未发现任何资产,退出")
|
|
return {"status": "ok", "total": 0}
|
|
|
|
# ---- 分类 ----
|
|
meshes = {} # subfolder -> [asset_info, ...]
|
|
materials = {} # subfolder -> [asset_info, ...]
|
|
others = {} # subfolder -> [asset_info, ...]
|
|
by_type = {}
|
|
|
|
for i, asset_path in enumerate(all_assets):
|
|
progress_log(i, total, "资产分类")
|
|
|
|
info = _load_and_classify(asset_path)
|
|
class_type = info["ue_class_type"]
|
|
subfolder = _get_subfolder(asset_path, scan_root)
|
|
|
|
# 统计
|
|
by_type[class_type] = by_type.get(class_type, 0) + 1
|
|
|
|
# 分类
|
|
if class_type in MESH_TYPES:
|
|
meshes.setdefault(subfolder, []).append(info)
|
|
elif class_type in MATERIAL_TYPES:
|
|
materials.setdefault(subfolder, []).append(info)
|
|
else:
|
|
others.setdefault(subfolder, []).append(info)
|
|
|
|
# ---- 蓝图归类 ----
|
|
# 从 others 中提取蓝图相关(Blueprint 类型或目录含 Blueprint 的)
|
|
blueprints = {}
|
|
# UE5 的蓝图资产 class type 可能是各种名称
|
|
# 我们把 ue_path 中包含 /Blueprints/ 的单独归类
|
|
remaining_others = {}
|
|
for subfolder, assets in others.items():
|
|
bp_assets = []
|
|
other_assets = []
|
|
for a in assets:
|
|
if "/Blueprints/" in a["ue_path"] or a["ue_class_type"] in {
|
|
"Blueprint", "BlueprintGeneratedClass", "WidgetBlueprint",
|
|
"AnimBlueprint", "BlueprintType",
|
|
}:
|
|
bp_assets.append(a)
|
|
else:
|
|
other_assets.append(a)
|
|
if bp_assets:
|
|
blueprints[subfolder] = bp_assets
|
|
if other_assets:
|
|
remaining_others[subfolder] = other_assets
|
|
|
|
# ---- 写入 index.json ----
|
|
index = {
|
|
"metadata": {
|
|
"generated_at": iso_now(),
|
|
"scan_root": scan_root,
|
|
"total_assets": total,
|
|
"by_type": by_type,
|
|
},
|
|
"meshes": meshes,
|
|
"materials": materials,
|
|
"blueprints": blueprints,
|
|
"others": remaining_others,
|
|
}
|
|
|
|
output_path = output_dir / "index.json"
|
|
write_json(index, output_path)
|
|
print(f"{TAG} ✅ 已写入: {output_path}")
|
|
|
|
# ---- 统计 ----
|
|
mesh_count = sum(len(v) for v in meshes.values())
|
|
mat_count = sum(len(v) for v in materials.values())
|
|
bp_count = sum(len(v) for v in blueprints.values())
|
|
other_count = sum(len(v) for v in remaining_others.values())
|
|
|
|
stats = {
|
|
"status": "ok",
|
|
"total": total,
|
|
"meshes": mesh_count,
|
|
"materials": mat_count,
|
|
"blueprints": bp_count,
|
|
"others": other_count,
|
|
"mesh_subfolders": list(meshes.keys()),
|
|
"material_subfolders": list(materials.keys()),
|
|
"blueprint_subfolders": list(blueprints.keys()),
|
|
}
|
|
|
|
print(f"{TAG} 统计:")
|
|
print(f"{TAG} StaticMesh: {mesh_count} ({len(meshes)} 子目录)")
|
|
print(f"{TAG} Materials: {mat_count} ({len(materials)} 子目录)")
|
|
print(f"{TAG} Blueprints: {bp_count} ({len(blueprints)} 子目录)")
|
|
print(f"{TAG} Others: {other_count}")
|
|
|
|
return stats
|
|
|
|
|
|
# ---- 入口 ----
|
|
if __name__ == "__main__":
|
|
if not wait_for_editor():
|
|
sys.exit(1)
|
|
result = run()
|
|
if result.get("status") != "ok":
|
|
sys.exit(1)
|