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>
146 lines
5.1 KiB
Python
146 lines
5.1 KiB
Python
"""Audio ops — MetaSounds, SoundCue, Attenuation, SoundClass/Mix.
|
|
|
|
Ops:
|
|
create_metasound_source {path}
|
|
create_metasound_patch {path}
|
|
create_sound_cue {path}
|
|
create_sound_attenuation {path}
|
|
create_sound_class {path}
|
|
create_sound_mix {path}
|
|
set_property {path, property, value}
|
|
get_property {path, property}
|
|
list_sound_assets {folder?}
|
|
play_sound_2d {path} — preview in editor
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import traceback
|
|
|
|
try:
|
|
import unreal # type: ignore
|
|
except ImportError:
|
|
unreal = None
|
|
|
|
|
|
def _at():
|
|
return unreal.AssetToolsHelpers.get_asset_tools()
|
|
|
|
|
|
def _load(p): return unreal.EditorAssetLibrary.load_asset(p)
|
|
def _split(p):
|
|
folder, name = p.rsplit("/", 1)
|
|
return folder, name
|
|
|
|
|
|
def _create(path, asset_cls_name, factory_name):
|
|
folder, name = _split(path)
|
|
cls = getattr(unreal, asset_cls_name, None)
|
|
factory_cls = getattr(unreal, factory_name, None)
|
|
if cls is None or factory_cls is None:
|
|
return {"ok": False, "error": f"{asset_cls_name}/{factory_name} not exposed (plugin disabled?)"}
|
|
asset = _at().create_asset(name, folder, cls, factory_cls())
|
|
if asset is None:
|
|
return {"ok": False, "error": "create_asset returned None"}
|
|
unreal.EditorAssetLibrary.save_asset(asset.get_path_name())
|
|
return {"ok": True, "path": asset.get_path_name().split(".")[0]}
|
|
|
|
|
|
def create_metasound_source(args): return _create(args["path"], "MetaSoundSource", "MetaSoundSourceFactory")
|
|
def create_metasound_patch(args): return _create(args["path"], "MetaSoundPatch", "MetaSoundPatchFactory")
|
|
def create_sound_cue(args): return _create(args["path"], "SoundCue", "SoundCueFactoryNew")
|
|
def create_sound_attenuation(args): return _create(args["path"], "SoundAttenuation", "SoundAttenuationFactory")
|
|
def create_sound_class(args): return _create(args["path"], "SoundClass", "SoundClassFactory")
|
|
def create_sound_mix(args): return _create(args["path"], "SoundMix", "SoundMixFactory")
|
|
|
|
|
|
def set_property(args):
|
|
a = _load(args["path"])
|
|
if a is None:
|
|
return {"ok": False, "error": "asset not found"}
|
|
try:
|
|
a.set_editor_property(args["property"], args["value"])
|
|
unreal.EditorAssetLibrary.save_asset(a.get_path_name())
|
|
return {"ok": True}
|
|
except Exception as e:
|
|
return {"ok": False, "error": str(e)}
|
|
|
|
|
|
def get_property(args):
|
|
a = _load(args["path"])
|
|
if a is None:
|
|
return {"ok": False, "error": "asset not found"}
|
|
try:
|
|
v = a.get_editor_property(args["property"])
|
|
return {"ok": True, "value": str(v)}
|
|
except Exception as e:
|
|
return {"ok": False, "error": str(e)}
|
|
|
|
|
|
def list_sound_assets(args):
|
|
folder = args.get("folder") or "/Game"
|
|
ar = unreal.AssetRegistryHelpers.get_asset_registry()
|
|
classes = ["SoundCue", "SoundWave", "MetaSoundSource", "MetaSoundPatch",
|
|
"SoundAttenuation", "SoundClass", "SoundMix", "SoundConcurrency"]
|
|
filt = unreal.ARFilter(package_paths=[folder], recursive_paths=True, class_names=classes)
|
|
items = [{"path": str(d.package_name), "class": str(d.asset_class)} for d in ar.get_assets(filt)]
|
|
return {"ok": True, "count": len(items), "items": items}
|
|
|
|
|
|
def play_sound_2d(args):
|
|
a = _load(args["path"])
|
|
if a is None:
|
|
return {"ok": False, "error": "not found"}
|
|
try:
|
|
world = unreal.EditorLevelLibrary.get_editor_world()
|
|
unreal.GameplayStatics.play_sound2d(world, a)
|
|
return {"ok": True}
|
|
except Exception as e:
|
|
return {"ok": False, "error": str(e)}
|
|
|
|
|
|
OPS = {
|
|
"create_metasound_source": create_metasound_source,
|
|
"create_metasound_patch": create_metasound_patch,
|
|
"create_sound_cue": create_sound_cue,
|
|
"create_sound_attenuation": create_sound_attenuation,
|
|
"create_sound_class": create_sound_class,
|
|
"create_sound_mix": create_sound_mix,
|
|
"set_property": set_property,
|
|
"get_property": get_property,
|
|
"list_sound_assets": list_sound_assets,
|
|
"play_sound_2d": play_sound_2d,
|
|
}
|
|
|
|
|
|
def entrypoint(payload_json: str) -> str:
|
|
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-AUDIO] " + txt[:1500])
|
|
except Exception:
|
|
pass
|
|
return txt
|