136 lines
5.7 KiB
Python
136 lines
5.7 KiB
Python
# Livraison des clips d'animation convertis dans un mod d'assets (lot AP5).
|
|
# Lancement :
|
|
# UnrealEditor-Cmd.exe <projet> -run=pythonscript "-script=<chemin>/Tools/anim/DeliverAnims.py mod=<Nom> src=/Game/Quaternius/UAL prefix=qua exclude=A_TPose pipeline=AnimPipeline/quaternius"
|
|
# Copie <src>/B_<Clip> -> /<mod>/Anims/Female/<prefix>_<clip>_F
|
|
# <src>/M_<Clip> -> /<mod>/Anims/Male/<prefix>_<clip>_M (src = dossier des clips convertis B_/M_)
|
|
# Les sources ne sont jamais deplacees ni renommees. Seules des copies du mod peuvent etre supprimees.
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
import unreal
|
|
|
|
# Arguments : mod=... src=... prefix=... exclude=A,B (le script peut recevoir les arguments en un seul bloc)
|
|
# pipeline= : dossier de livraison.json, relatif a Saved/ (ou absolu) ; sans espace (arguments decoupes sur les espaces).
|
|
args = {"mod": "lpr_anim_quaternius", "src": "/Game/Quaternius/UAL", "prefix": "qua", "exclude": "A_TPose",
|
|
"pipeline": "AnimPipeline/quaternius"}
|
|
tokens = []
|
|
for a in sys.argv[1:]:
|
|
tokens.extend(a.split())
|
|
for t in tokens:
|
|
if "=" in t:
|
|
k, v = t.split("=", 1)
|
|
args[k.strip().lower()] = v.strip()
|
|
|
|
MOD = args["mod"]
|
|
SRC = args["src"].rstrip("/")
|
|
PREFIX = args["prefix"]
|
|
EXCLUDE = set(x for x in args["exclude"].split(",") if x)
|
|
erreurs = []
|
|
livraison = []
|
|
|
|
|
|
def erreur(msg):
|
|
erreurs.append(msg)
|
|
unreal.log_error("LIVRAISON erreur : " + msg)
|
|
|
|
|
|
def lister(dossier, debut):
|
|
noms = set()
|
|
for p in unreal.EditorAssetLibrary.list_assets(dossier, recursive=False, include_folder=False):
|
|
nom = p.split("/")[-1].split(".")[0]
|
|
if nom.startswith(debut):
|
|
noms.add(nom[len(debut):])
|
|
return noms
|
|
|
|
|
|
def infos(asset):
|
|
return asset.get_skeleton(), asset.get_editor_property("number_of_sampled_keys"), asset.get_editor_property("sequence_length")
|
|
|
|
|
|
# Garde-fou : on ne supprime et n'ecrit que dans le mod.
|
|
def sous_mod(chemin):
|
|
return chemin.startswith("/" + MOD + "/")
|
|
|
|
|
|
# En commandlet, le registre n'a pas encore explore le dossier du mod : sans ce scan, les copies d'une passe
|
|
# precedente sont ignorees par does_asset_exist et duplicate_asset echoue (AP5, deuxieme passe).
|
|
unreal.AssetRegistryHelpers.get_asset_registry().scan_paths_synchronous(["/" + MOD], True)
|
|
|
|
clips = sorted((lister(SRC, "B_") & lister(SRC, "M_")) - EXCLUDE)
|
|
unreal.log("LIVRAISON %d clip(s) a livrer vers /%s/" % (len(clips), MOD))
|
|
|
|
for clip in clips:
|
|
cle = PREFIX + "_" + clip.lower()
|
|
if not re.match(r"^[a-z0-9_]+$", cle):
|
|
erreur("cle invalide : " + cle)
|
|
continue
|
|
sources = {"F": SRC + "/B_" + clip, "M": SRC + "/M_" + clip}
|
|
dests = {"F": "/%s/Anims/Female/%s_F" % (MOD, cle), "M": "/%s/Anims/Male/%s_M" % (MOD, cle)}
|
|
ref = {}
|
|
ok = True
|
|
for sexe in ("F", "M"):
|
|
s, d = sources[sexe], dests[sexe]
|
|
if not sous_mod(d):
|
|
erreur("destination hors du mod : " + d)
|
|
ok = False
|
|
continue
|
|
asrc = unreal.EditorAssetLibrary.load_asset(s)
|
|
if not isinstance(asrc, unreal.AnimSequence):
|
|
erreur("source absente ou pas une AnimSequence : " + s)
|
|
ok = False
|
|
continue
|
|
if unreal.EditorAssetLibrary.does_asset_exist(d):
|
|
if not unreal.EditorAssetLibrary.delete_asset(d):
|
|
erreur("suppression de la copie precedente impossible : " + d)
|
|
ok = False
|
|
continue
|
|
copie = unreal.EditorAssetLibrary.duplicate_asset(s, d)
|
|
if copie is None:
|
|
erreur("copie impossible : %s -> %s" % (s, d))
|
|
ok = False
|
|
continue
|
|
# Un mod DLC ne peut pas referencer du contenu moteur non cuit : les clips d'un pack UE5 pointent
|
|
# explicitement sur /Engine/Animation/DefaultAnim*CompressionSettings (cuisson refusee, AP7). Sans
|
|
# reglage (None), la compression par defaut du projet s'applique, comme pour les clips Quaternius.
|
|
for prop in ("bone_compression_settings", "curve_compression_settings"):
|
|
valeur = copie.get_editor_property(prop)
|
|
if valeur is not None and valeur.get_path_name().startswith("/Engine/"):
|
|
copie.set_editor_property(prop, None)
|
|
if not unreal.EditorAssetLibrary.save_asset(d, only_if_is_dirty=False):
|
|
erreur("enregistrement impossible : " + d)
|
|
ok = False
|
|
continue
|
|
# Controles : existe, AnimSequence, meme squelette, meme nombre d'images, meme duree.
|
|
acopie = unreal.EditorAssetLibrary.load_asset(d)
|
|
if not unreal.EditorAssetLibrary.does_asset_exist(d) or not isinstance(acopie, unreal.AnimSequence):
|
|
erreur("copie absente ou pas une AnimSequence : " + d)
|
|
ok = False
|
|
continue
|
|
sk0, n0, l0 = infos(asrc)
|
|
sk1, n1, l1 = infos(acopie)
|
|
if sk0 != sk1:
|
|
erreur("squelette different : " + d)
|
|
ok = False
|
|
if n0 != n1:
|
|
erreur("nombre d'images different (%s / %s) : %s" % (n0, n1, d))
|
|
ok = False
|
|
if abs(l0 - l1) > 1e-4:
|
|
erreur("duree differente (%s / %s) : %s" % (l0, l1, d))
|
|
ok = False
|
|
ref[sexe] = (n1, l1)
|
|
if ok:
|
|
livraison.append({
|
|
"cle": cle,
|
|
"source_F": sources["F"], "source_M": sources["M"],
|
|
"asset_F": dests["F"], "asset_M": dests["M"],
|
|
"images": ref["F"][0], "duree_s": ref["F"][1],
|
|
})
|
|
|
|
sortie = os.path.join(unreal.Paths.project_saved_dir(), args["pipeline"], "livraison.json") # join : absolu prime
|
|
os.makedirs(os.path.dirname(sortie), exist_ok=True)
|
|
with open(sortie, "w", encoding="utf-8") as f:
|
|
json.dump(livraison, f, indent=2, ensure_ascii=False)
|
|
|
|
unreal.log("LIVRAISON copies %d erreurs %d" % (len(livraison) * 2, len(erreurs)))
|