Files
unreal-engine-mcp-system-pl…/ue_side/validation.py
Bonchellon eba71c4ca8 Unified, portable Unreal Engine MCP system plugin
Merge of the two project copies into one self-contained plugin (the superset:
variable_op + variables.py, full pcg_op runtime/declarative/preset ops, the
CreateWidgetFloatAnimation widget tool, and full Voxel graph authoring).

Made fully project- and machine-agnostic — no hardcoded paths:
- New src/projectPaths.js auto-detects the host .uproject (walk-up), project
  name, Editor build target, log file, and engine install (EngineAssociation
  via launcher manifest/registry, else installed-engine scan). All overridable
  via UE_* env vars.
- Rewired buildOrchestrator/insights/launcher/insightsExporter/server.js and the
  Python workers (cpp_scaffold, live_coding, apply_graph, console) off the old
  C:/Github/ihy, IHY*, E:/UE_Versions and UE_5.7 literals onto the resolver.
- Voxel made optional: Build.cs auto-detects the Voxel plugin (env
  UE_MCP_WITH_VOXEL override) and the C++ compiles to stubs under WITH_MCP_VOXEL,
  so the module builds in projects without Voxel; .uplugin marks Voxel optional.
- De-branded the agent-gateway and docs; scrubbed a leaked API key; excluded
  node_modules/Binaries/Intermediate/__pycache__/secrets from the repo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 01:07:24 +03:00

157 lines
6.0 KiB
Python

"""Asset validation, redirector cleanup, reference analysis.
Ops:
validate_assets {paths:[]} — run EditorValidatorSubsystem
validate_folder {path, recursive?}
fix_redirectors {path} — recursively resolve UObjectRedirectors under folder
get_references {asset_path} — assets THIS asset depends on
get_referencers {asset_path} — assets that REFER TO this asset
find_unused {folder, ignore_paths?:[]} — assets in folder with zero referencers
disk_size {folder} — total .uasset size on disk
list_redirectors {folder}
"""
from __future__ import annotations
import json
import os
import traceback
try:
import unreal # type: ignore
except ImportError:
unreal = None
def _ar():
return unreal.AssetRegistryHelpers.get_asset_registry()
def validate_assets(args):
paths = args.get("paths") or []
evs = unreal.get_editor_subsystem(unreal.EditorValidatorSubsystem)
if evs is None:
return {"ok": False, "error": "EditorValidatorSubsystem unavailable"}
objs = [unreal.EditorAssetLibrary.load_asset(p) for p in paths]
objs = [o for o in objs if o is not None]
results = evs.validate_assets(objs, True, True) if hasattr(evs, "validate_assets") else None
return {"ok": True, "validated_count": len(objs), "result": str(results) if results is not None else None}
def validate_folder(args):
folder = args["path"]
recursive = bool(args.get("recursive", True))
filt = unreal.ARFilter(package_paths=[folder], recursive_paths=recursive)
assets = _ar().get_assets(filt)
paths = [str(d.package_name) for d in assets]
return validate_assets({"paths": paths})
def fix_redirectors(args):
folder = args["path"]
filt = unreal.ARFilter(package_paths=[folder], recursive_paths=True, class_names=["ObjectRedirector"])
redirs = _ar().get_assets(filt)
if not redirs:
return {"ok": True, "fixed": 0}
objs = [d.get_asset() for d in redirs]
objs = [o for o in objs if o is not None]
try:
unreal.AssetToolsHelpers.get_asset_tools().fixup_referencers(objs)
return {"ok": True, "fixed": len(objs)}
except Exception as e:
return {"ok": False, "error": str(e), "found": len(redirs)}
def get_references(args):
deps = _ar().get_dependencies(unreal.Name(args["asset_path"]),
unreal.AssetRegistryDependencyOptions(include_soft_package_references=True,
include_hard_package_references=True))
return {"ok": True, "dependencies": [str(d) for d in (deps or [])]}
def get_referencers(args):
refs = _ar().get_referencers(unreal.Name(args["asset_path"]),
unreal.AssetRegistryDependencyOptions(include_soft_package_references=True,
include_hard_package_references=True))
return {"ok": True, "referencers": [str(d) for d in (refs or [])]}
def find_unused(args):
folder = args["folder"]
ignore = set(args.get("ignore_paths") or [])
filt = unreal.ARFilter(package_paths=[folder], recursive_paths=True)
out = []
for d in _ar().get_assets(filt):
pkg = str(d.package_name)
if pkg in ignore or any(pkg.startswith(p) for p in ignore):
continue
refs = _ar().get_referencers(unreal.Name(pkg),
unreal.AssetRegistryDependencyOptions(include_soft_package_references=True,
include_hard_package_references=True)) or []
if not refs:
out.append({"path": pkg, "class": str(d.asset_class)})
return {"ok": True, "count": len(out), "unused": out}
def disk_size(args):
folder = args["folder"]
# Translate /Game -> Content path
proj_content = unreal.Paths.project_content_dir()
rel = folder.replace("/Game", "").lstrip("/")
root = os.path.join(proj_content, rel)
total = 0; files = 0
for dirpath, _, fnames in os.walk(root):
for f in fnames:
if f.endswith((".uasset", ".umap")):
total += os.path.getsize(os.path.join(dirpath, f))
files += 1
return {"ok": True, "folder": folder, "bytes": total, "files": files, "mb": round(total / 1024 / 1024, 2)}
def list_redirectors(args):
folder = args["folder"]
filt = unreal.ARFilter(package_paths=[folder], recursive_paths=True, class_names=["ObjectRedirector"])
out = [str(d.package_name) for d in _ar().get_assets(filt)]
return {"ok": True, "count": len(out), "redirectors": out}
OPS = {
"validate_assets": validate_assets,
"validate_folder": validate_folder,
"fix_redirectors": fix_redirectors,
"get_references": get_references,
"get_referencers": get_referencers,
"find_unused": find_unused,
"disk_size": disk_size,
"list_redirectors": list_redirectors,
}
def entrypoint(payload_json):
try:
p = json.loads(payload_json)
except Exception as e:
return _tee({"ok": False, "error": f"bad json: {e}"}, {})
op = p.get("op"); fn = OPS.get(op)
if not fn:
return _tee({"ok": False, "error": f"unknown op {op!r}", "available": list(OPS)}, p)
try:
return _tee(fn(p), p)
except Exception as e:
return _tee({"ok": False, "error": str(e), "traceback": traceback.format_exc()}, p)
def _tee(result, p):
txt = json.dumps(result)
try:
if unreal is not None:
sd = unreal.Paths.project_saved_dir() + "MCP/"
os.makedirs(sd, exist_ok=True)
rid = p.get("__rid") or "last"
with open(sd + "last.json", "w", encoding="utf-8") as f: f.write(txt)
if rid != "last":
with open(sd + f"{rid}.json", "w", encoding="utf-8") as f: f.write(txt)
unreal.log("[MCP-VALIDATE] " + txt[:1500])
except Exception:
pass
return txt