LifePlay-Revival/Tools/BuildCollections.py

119 lines
6.4 KiB
Python
Raw Normal View History

# Lot O7d : collections thematiques de l'editeur (Docs/Migration/11_Audit_Optimisation.md §16.5).
#
# Les dossiers de Content/Heavy (New, New2...) ne peuvent pas etre renommes : le jeu et les mods construisent des chemins
# a partir de ces noms (§16.2). Ce script regroupe les assets par theme dans des collections statiques de l'editeur,
# sans deplacer ni modifier aucun asset. Les collections sont des fichiers texte non versionnes : ce script est leur
# source et les regenere a l'identique (GUID fixes). Elles sont locales (Saved/Collections) : les collections partagees
# (Content/Collections) exigent un fournisseur de controle de revision, desactive dans ce projet (icone d'avertissement).
#
# Usage :
# UnrealEditor-Cmd.exe <projet> -run=pythonscript -script="<projet>/Tools/BuildCollections.py" -unattended -nopause -nosplash
# Ajouter " -dryrun" dans -script="..." pour afficher les comptes sans ecrire.
# Sortie : Saved/Collections/LifePlay*.collection ; resume dans le journal (LogPython).
import os
import sys
import uuid
import unreal
DRY_RUN = "-dryrun" in [a.lower() for a in sys.argv[1:]]
NAMESPACE = uuid.UUID("6c1f0e2a-7b1d-4f0e-9a51-0f7d2c4b5e10")
# Prefixes ecrits en texte dans les Blueprints et chemins complets cites par Content/Modules (§16.2).
FIXED_PREFIXES = [
"/game/heavy/new2/furniture/", "/game/heavy/clothes4/", "/game/heavy/streetmap/", "/game/heavy/new3/tattoos/",
"/game/heavy/new3/characters/tanlines/", "/game/heavy/daz/bridget/hairs/pubic/", "/game/heavy/daz/michael/hairs/other/",
"/game/heavy/new3/ui/", "/game/heavy/daz/clothes/", "/game/heavy/new2/bdsm/clothes/", "/game/heavy/new3/clothes/",
]
# (nom, couleur RGB ou None, regle) ; un asset va dans le premier theme dont la regle accepte (chemin minuscule, classe).
def under(*prefixes):
return lambda p, c: any(p.startswith("/game/" + x.lower() + "/") for x in prefixes)
THEMES = [
("Animations", (0.2, 0.6, 1.0), lambda p, c: c in ("AnimSequence", "AnimMontage", "BlendSpace", "AnimBlueprint", "PreviewMeshCollection")),
("Sons", (0.6, 0.4, 1.0), lambda p, c: c in ("SoundWave", "SoundCue")),
("Cheveux", (0.9, 0.7, 0.2), lambda p, c: "/hairs/" in p),
("Vetements", (0.9, 0.4, 0.6), under("Heavy/Clothes4", "Heavy/Daz/Clothes", "Heavy/New3/Clothes", "Heavy/New2/BDSM")),
("Meubles", (0.6, 0.4, 0.2), under("Heavy/New2/Furniture")),
("Villes", (0.3, 0.8, 0.3), under("Heavy/StreetMap", "Heavy/New2/Buildings")),
("Tatouages", (0.5, 0.5, 0.5), under("Heavy/New3/Tattoos")),
("Corps", (1.0, 0.8, 0.7), under("Heavy/Daz", "Heavy/Characters", "Heavy/New3/Characters", "Heavy/New2/BridgetFaces",
"Heavy/New2/Beasts", "Heavy/New3/Beasts", "Heavy/New4/Tentacles", "Characters")),
("Icones_UI", (1.0, 1.0, 0.4), under("Heavy/New3/UI", "Heavy/New", "Heavy/New4/UI", "UI/Icons", "Heavy/Backgrounds",
"UI/Backgrounds", "Heavy/New2/Backgrounds")),
("Gameplay", (0.4, 0.9, 0.9), under("BPs", "UI", "Structs", "Scenes", "Render", "Heavy/New4/Notifies")),
("Divers", None, lambda p, c: True),
]
FIXED = ("Chemins_Fixes", (1.0, 0.1, 0.1)) # transversal : les assets a ne jamais deplacer
def guid(name):
return str(uuid.uuid5(NAMESPACE, name)).upper()
def write_collection(folder, name, parent, color, paths):
lines = ["FileVersion:2", "Type:Static", "Guid:" + guid(name), "ParentGuid:" + (guid(parent) if parent else "00000000-0000-0000-0000-000000000000")]
if color:
lines.append("Color:(R={:.6f},G={:.6f},B={:.6f},A=1.000000)".format(*color))
text = "\r\n".join(lines) + "\r\n\r\n" + "".join(p + "\r\n" for p in sorted(paths))
with open(os.path.join(folder, name + ".collection"), "w", encoding="utf-8", newline="") as f:
f.write(text)
registry = unreal.AssetRegistryHelpers.get_asset_registry()
registry.search_all_assets(True)
assets = registry.get_assets_by_path("/Game", recursive=True)
members = {name: [] for name, _, _ in THEMES}
fixed = []
skipped = 0
for a in assets:
cls = str(a.asset_class_path.asset_name)
if cls in ("ObjectRedirector", "MapBuildDataRegistry"):
skipped += 1
continue
package = str(a.package_name)
lower = package.lower()
if lower.startswith("/game/collections/") or lower.startswith("/game/developers/") or lower.startswith("/game/saves/"):
skipped += 1
continue
object_path = package + "." + str(a.asset_name)
for name, _, rule in THEMES:
if rule(lower + "/", cls):
members[name].append(object_path)
break
if any((lower + "/").startswith(p) for p in FIXED_PREFIXES):
fixed.append(object_path)
total = sum(len(v) for v in members.values())
for name, _, _ in THEMES:
unreal.log_warning("BuildCollections : LifePlay_{:<12} {:>5} asset(s)".format(name, len(members[name])))
unreal.log_warning("BuildCollections : LifePlay_{:<12} {:>5} asset(s) (transversal)".format(FIXED[0], len(fixed)))
unreal.log_warning("BuildCollections : {} asset(s) classes, {} ignore(s) (redirecteurs, donnees de carte), {} au total".format(total, skipped, len(assets)))
if DRY_RUN:
unreal.log_warning("BuildCollections : -dryrun, aucune ecriture")
else:
folder = os.path.join(unreal.Paths.convert_relative_path_to_full(unreal.Paths.project_saved_dir()), "Collections")
os.makedirs(folder, exist_ok=True)
write_collection(folder, "LifePlay", None, None, [])
for name, color, _ in THEMES:
write_collection(folder, "LifePlay_" + name, "LifePlay", color, members[name])
write_collection(folder, "LifePlay_" + FIXED[0], "LifePlay", FIXED[1], fixed)
# Controle : relecture des fichiers, chaque chemin doit designer un asset du registre.
known = set(str(a.package_name) + "." + str(a.asset_name) for a in assets)
bad = 0
count = 0
for file in sorted(os.listdir(folder)):
if not file.startswith("LifePlay"):
continue
with open(os.path.join(folder, file), encoding="utf-8", newline="") as f:
body = f.read().split("\r\n\r\n", 1)[1]
for line in body.split("\r\n"):
if line:
count += 1
if line not in known:
bad += 1
unreal.log_error("BuildCollections : chemin inconnu dans {} : {}".format(file, line))
unreal.log_warning("BuildCollections : {} collection(s) ecrite(s) dans {}, {} chemin(s), {} inconnu(s)".format(
len(THEMES) + 2, folder, count, bad))