367 lines
15 KiB
Python
367 lines
15 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Rangement unique de Content/Game.
|
|
|
|
A lancer depuis l'editeur : Tools > Execute Python Script.
|
|
Mettre DRY_RUN a True pour une simulation qui ne touche a rien, puis a False.
|
|
|
|
Pourquoi un script et pas du drag & drop : ~70 operations, dont des renommages
|
|
qui doivent se faire dans un ordre precis (un materiau prend le nom qu'une
|
|
instance vient de liberer). Une erreur de destination se voit ici, pas trois
|
|
jours plus tard sur un asset au mauvais endroit.
|
|
|
|
Unreal se charge lui-meme de reecrire les references : rename_asset laisse un
|
|
redirector derriere lui, et fixup_referencers() les resout a la fin.
|
|
"""
|
|
|
|
import unreal
|
|
|
|
DRY_RUN = True
|
|
|
|
EAL = unreal.EditorAssetLibrary
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Assets a supprimer -- verifie sans aucun referenceur dans tout le projet.
|
|
# ---------------------------------------------------------------------------
|
|
DELETIONS = [
|
|
# Doublon reel : le M_Pickaxe utilise par le mesh est celui de 3d/, pas celui-ci.
|
|
"/Game/Game/Material/Tools/Woods/Pickaxe/M_Pickaxe",
|
|
"/Game/Game/Audio/Chest/Chest_Closes_2",
|
|
]
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Renommages d'assets. L'ORDRE COMPTE dans le bloc Table : l'instance libere
|
|
# le nom M_Table avant que le materiau parent ne le prenne.
|
|
# ---------------------------------------------------------------------------
|
|
ASSET_MOVES = [
|
|
# --- Art / Building / Table -------------------------------------------
|
|
("/Game/Game/3d/Building/Table/M_Table",
|
|
"/Game/Game/Art/Building/Table/MI_Table"),
|
|
("/Game/Game/3d/Building/Table/textures/M_table_albedo_Mat",
|
|
"/Game/Game/Art/Building/Table/M_Table"),
|
|
("/Game/Game/3d/Building/Table/Table_FBX",
|
|
"/Game/Game/Art/Building/Table/SM_Table"),
|
|
("/Game/Game/3d/Building/Table/textures/M_table_albedo",
|
|
"/Game/Game/Art/Building/Table/T_Table_BaseColor"),
|
|
("/Game/Game/3d/Building/Table/textures/M_table_Metal",
|
|
"/Game/Game/Art/Building/Table/T_Table_Metallic"),
|
|
("/Game/Game/3d/Building/Table/textures/M_table_Normal",
|
|
"/Game/Game/Art/Building/Table/T_Table_Normal"),
|
|
("/Game/Game/3d/Building/Table/textures/M_table_Roughness",
|
|
"/Game/Game/Art/Building/Table/T_Table_Roughness"),
|
|
|
|
# --- Art / Cooking -----------------------------------------------------
|
|
# Les suffixes numeriques sont conserves : rien ne dit lequel est lequel,
|
|
# et inventer un nom parlant ici serait une supposition.
|
|
("/Game/Game/3d/Cooking/Campfire_Logs",
|
|
"/Game/Game/Art/Cooking/SM_Campfire_Logs"),
|
|
("/Game/Game/3d/Cooking/Campfire_Logs_001",
|
|
"/Game/Game/Art/Cooking/SM_Campfire_Logs_001"),
|
|
("/Game/Game/3d/Cooking/Campfire_Logs_002",
|
|
"/Game/Game/Art/Cooking/SM_Campfire_Logs_002"),
|
|
("/Game/Game/3d/Cooking/Campfire_Logs_003",
|
|
"/Game/Game/Art/Cooking/SM_Campfire_Logs_003"),
|
|
("/Game/Game/3d/Cooking/LogS",
|
|
"/Game/Game/Art/Cooking/SM_LogS"),
|
|
("/Game/Game/3d/Cooking/Logs1",
|
|
"/Game/Game/Art/Cooking/SM_Logs1"),
|
|
("/Game/Game/3d/Cooking/Rocks_003",
|
|
"/Game/Game/Art/Cooking/MI_Rocks_003"),
|
|
("/Game/Game/3d/Cooking/Wood_004",
|
|
"/Game/Game/Art/Cooking/MI_Wood_004"),
|
|
("/Game/Game/3d/Cooking/Wood_light_001",
|
|
"/Game/Game/Art/Cooking/MI_Wood_light_001"),
|
|
|
|
# --- Art / Tools / Axe -------------------------------------------------
|
|
# Le materiau s'appelait "Wood_Axe", exactement comme le mesh : deux assets
|
|
# homonymes a deux endroits, c'est ce qui rendait le dossier illisible.
|
|
("/Game/Game/3d/Tools/Wood_Axe",
|
|
"/Game/Game/Art/Tools/Axe/SM_Wood_Axe"),
|
|
("/Game/Game/3d/Tools/Wood_Axe_Texture/Wood_Axe",
|
|
"/Game/Game/Art/Tools/Axe/M_Wood_Axe"),
|
|
("/Game/Game/3d/Tools/Wood_Axe_Texture/Wood_Axe_BaseColor",
|
|
"/Game/Game/Art/Tools/Axe/T_Wood_Axe_BaseColor"),
|
|
("/Game/Game/3d/Tools/Wood_Axe_Texture/Wood_Axe_Normal",
|
|
"/Game/Game/Art/Tools/Axe/T_Wood_Axe_Normal"),
|
|
("/Game/Game/3d/Tools/Wood_Axe_Texture/Wood_Axe_OcclusionRoughnessMetallic",
|
|
"/Game/Game/Art/Tools/Axe/T_Wood_Axe_ORM"),
|
|
# Orphelin, conserve : la hache en bois est un chantier ouvert.
|
|
("/Game/Game/Material/Tools/Woods/Axe/M_Axe",
|
|
"/Game/Game/Art/Tools/Axe/M_Axe"),
|
|
|
|
# --- Art / Tools / Pickaxe ---------------------------------------------
|
|
# Les textures portaient le nom de la hache alors qu'elles alimentent la
|
|
# pioche : on remet le nom en accord avec l'usage reel.
|
|
("/Game/Game/3d/Tools/Wood_Pickaxe",
|
|
"/Game/Game/Art/Tools/Pickaxe/SM_Wood_Pickaxe"),
|
|
("/Game/Game/3d/Tools/Wood_Pickaxe_Texture/M_Pickaxe",
|
|
"/Game/Game/Art/Tools/Pickaxe/M_Wood_Pickaxe"),
|
|
("/Game/Game/3d/Tools/Wood_Pickaxe_Texture/Wood_Axe_BaseColor_sRGB",
|
|
"/Game/Game/Art/Tools/Pickaxe/T_Wood_Pickaxe_BaseColor"),
|
|
("/Game/Game/3d/Tools/Wood_Pickaxe_Texture/Wood_Axe_Normal_Raw",
|
|
"/Game/Game/Art/Tools/Pickaxe/T_Wood_Pickaxe_Normal"),
|
|
("/Game/Game/3d/Tools/Wood_Pickaxe_Texture/Wood_AxeOcclusionRoughnessMetallic_Raw",
|
|
"/Game/Game/Art/Tools/Pickaxe/T_Wood_Pickaxe_ORM"),
|
|
|
|
# --- Art / Tools / Sword -----------------------------------------------
|
|
("/Game/Game/3d/Tools/Wood_Sword",
|
|
"/Game/Game/Art/Tools/Sword/SM_Wood_Sword"),
|
|
("/Game/Game/3d/Tools/Wood_Sword_Texture/Wood_Sword_BaseColor",
|
|
"/Game/Game/Art/Tools/Sword/T_Wood_Sword_BaseColor"),
|
|
("/Game/Game/3d/Tools/Wood_Sword_Texture/Wood_Sword_Normal",
|
|
"/Game/Game/Art/Tools/Sword/T_Wood_Sword_Normal"),
|
|
("/Game/Game/3d/Tools/Wood_Sword_Texture/Wood_Sword_OcclusionRoughnessMetallic",
|
|
"/Game/Game/Art/Tools/Sword/T_Wood_Sword_ORM"),
|
|
|
|
# --- Art / Character / Arms --------------------------------------------
|
|
("/Game/Game/Material/Fps_Arm/M_Fps_Arm",
|
|
"/Game/Game/Art/Character/Arms/M_Fps_Arm"),
|
|
("/Game/Game/Material/Fps_Arm/MaleBody_M_Base_color_sRGB",
|
|
"/Game/Game/Art/Character/Arms/T_Fps_Arm_BaseColor"),
|
|
|
|
# --- Blueprints --------------------------------------------------------
|
|
# Framework = ce qui n'est pas un acteur du monde : GameMode, GameInstance,
|
|
# PlayerControllers. Le critere est celui de CLAUDE.md, pas le type d'asset.
|
|
("/Game/Game/Player/BP_FpsPlayer",
|
|
"/Game/Game/Blueprints/Player/BP_FpsPlayer"),
|
|
("/Game/Game/Player/BP_FpsPlayerController",
|
|
"/Game/Game/Blueprints/Framework/BP_FpsPlayerController"),
|
|
("/Game/Game/Menu/BP_MainMenuPlayerController",
|
|
"/Game/Game/Blueprints/Framework/BP_MainMenuPlayerController"),
|
|
("/Game/Game/GameMode/GM_FpsPlayer",
|
|
"/Game/Game/Blueprints/Framework/GM_FpsPlayer"),
|
|
("/Game/Game/GameMode/GM_MainMenu",
|
|
"/Game/Game/Blueprints/Framework/GM_MainMenu"),
|
|
("/Game/Game/Game-Instance/BP_SurvivalGameInstance",
|
|
"/Game/Game/Blueprints/Framework/BP_SurvivalGameInstance"),
|
|
("/Game/Game/PickupItem/BP_Pickup_Test",
|
|
"/Game/Game/Blueprints/World/BP_Pickup_Test"),
|
|
("/Game/Game/Building/Cooking/BP_Campfire",
|
|
"/Game/Game/Blueprints/World/BP_Campfire"),
|
|
("/Game/Game/Building/Crafting/BP_Workbench",
|
|
"/Game/Game/Blueprints/World/BP_Workbench"),
|
|
("/Game/Game/Building/Storage/BP_Chest_Wooden",
|
|
"/Game/Game/Blueprints/World/BP_Chest_Wooden"),
|
|
("/Game/Game/BP/Menu/CharaterCustomisation/BP_CharacterPreview",
|
|
"/Game/Game/Blueprints/Menu/BP_CharacterPreview"),
|
|
]
|
|
|
|
# Renommages qui doivent avoir lieu APRES un deplacement de dossier, parce que
|
|
# leur chemin source n'existe qu'une fois le dossier deplace.
|
|
ASSET_MOVES_LATE = [
|
|
# Le squelette n'est pas une animation : il sort de Sequences/.
|
|
("/Game/Game/Animation/FirstPerson/Sequences/BasePos_Skeleton",
|
|
"/Game/Game/Animation/FirstPerson/BasePos_Skeleton"),
|
|
# Nom colle deux fois par un import rate.
|
|
("/Game/Game/UI/Widgets/Player/WBP_PlayerNameplateWBP_PlayerNameplate",
|
|
"/Game/Game/UI/Widgets/Player/WBP_PlayerNameplate"),
|
|
]
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Deplacements de dossiers entiers.
|
|
# ---------------------------------------------------------------------------
|
|
DIR_MOVES = [
|
|
# --- Animation ---------------------------------------------------------
|
|
("/Game/Game/Animation/Fps",
|
|
"/Game/Game/Animation/FirstPerson/Sequences"),
|
|
("/Game/Game/Animation/ThirdPerson/RetargetedAnimations",
|
|
"/Game/Game/Animation/ThirdPerson/Sequences"),
|
|
|
|
# --- Audio : fusion de Sounds/ dans Audio/ -----------------------------
|
|
("/Game/Game/Sounds/Mix", "/Game/Game/Audio/Mix"),
|
|
("/Game/Game/Sounds/Music", "/Game/Game/Audio/Music"),
|
|
("/Game/Game/Sounds/Playlists", "/Game/Game/Audio/Playlists"),
|
|
("/Game/Game/Sounds/UI", "/Game/Game/Audio/UI"),
|
|
# FootSteps -> Footsteps : Windows ne distingue pas la casse, donc on passe
|
|
# par un nom intermediaire, sinon le renommage est un no-op silencieux.
|
|
("/Game/Game/Audio/FootSteps", "/Game/Game/Audio/_TmpFootsteps"),
|
|
("/Game/Game/Audio/_TmpFootsteps", "/Game/Game/Audio/Footsteps"),
|
|
|
|
# --- Data --------------------------------------------------------------
|
|
("/Game/Game/Data/Harvetable", "/Game/Game/Data/Harvestable"),
|
|
("/Game/Game/Data/Item", "/Game/Game/Data/Items"),
|
|
|
|
# --- UI : les widgets d'un cote, les ressources de l'autre -------------
|
|
("/Game/Game/UI/Images/512x512", "/Game/Game/UI/Icons/Items"),
|
|
("/Game/Game/UI/Images/Tools", "/Game/Game/UI/Icons/Tools"),
|
|
("/Game/Game/UI/Font", "/Game/Game/UI/Fonts"),
|
|
("/Game/Game/UI/Confirm", "/Game/Game/UI/Widgets/Confirm"),
|
|
("/Game/Game/UI/Craft", "/Game/Game/UI/Widgets/Craft"),
|
|
("/Game/Game/UI/CrossHair", "/Game/Game/UI/Widgets/Crosshair"),
|
|
("/Game/Game/UI/Customization", "/Game/Game/UI/Widgets/Customization"),
|
|
("/Game/Game/UI/Fade", "/Game/Game/UI/Widgets/Fade"),
|
|
("/Game/Game/UI/Interaction", "/Game/Game/UI/Widgets/Interaction"),
|
|
("/Game/Game/UI/Inventory", "/Game/Game/UI/Widgets/Inventory"),
|
|
("/Game/Game/UI/Menu", "/Game/Game/UI/Widgets/MainMenu"),
|
|
("/Game/Game/UI/Pause", "/Game/Game/UI/Widgets/Pause"),
|
|
("/Game/Game/UI/Player", "/Game/Game/UI/Widgets/Player"),
|
|
("/Game/Game/UI/Setting", "/Game/Game/UI/Widgets/Settings"),
|
|
("/Game/Game/UI/Stats", "/Game/Game/UI/Widgets/Stats"),
|
|
]
|
|
|
|
# Les deux icones egarees hors de Content/Game.
|
|
STRAY_MOVES = [
|
|
("/Game/UI/Icons/T_Icon_Food_06", "/Game/Game/UI/Icons/Items/T_Icon_Food_06"),
|
|
("/Game/UI/Icons/T_Icon_Food_07", "/Game/Game/UI/Icons/Items/T_Icon_Food_07"),
|
|
]
|
|
|
|
# Dossiers vides une fois tout deplace ET les redirectors resolus.
|
|
EMPTY_DIRS = [
|
|
"/Game/Game/3d",
|
|
"/Game/Game/Material",
|
|
"/Game/Game/Player",
|
|
"/Game/Game/Menu",
|
|
"/Game/Game/GameMode",
|
|
"/Game/Game/Game-Instance",
|
|
"/Game/Game/PickupItem",
|
|
"/Game/Game/Building",
|
|
"/Game/Game/BP",
|
|
"/Game/Game/Sounds",
|
|
"/Game/Game/Animation/Anim_BP",
|
|
"/Game/Game/UI/Images",
|
|
"/Game/UI",
|
|
]
|
|
|
|
# ---------------------------------------------------------------------------
|
|
|
|
stats = {"ok": 0, "skip": 0, "fail": 0}
|
|
|
|
|
|
def log(kind, message):
|
|
stats[kind] = stats.get(kind, 0) + 1
|
|
prefix = {"ok": " OK ", "skip": " SKIP ", "fail": " FAIL "}[kind]
|
|
unreal.log("{}{}".format(prefix, message))
|
|
|
|
|
|
def move_asset(src, dst):
|
|
if not EAL.does_asset_exist(src):
|
|
log("skip", "source absente : {}".format(src))
|
|
return
|
|
if EAL.does_asset_exist(dst):
|
|
log("fail", "destination DEJA OCCUPEE : {}".format(dst))
|
|
return
|
|
if DRY_RUN:
|
|
log("ok", "{} -> {}".format(src, dst))
|
|
return
|
|
if EAL.rename_asset(src, dst):
|
|
log("ok", "{} -> {}".format(src, dst))
|
|
else:
|
|
log("fail", "rename_asset a echoue : {}".format(src))
|
|
|
|
|
|
def move_dir(src, dst):
|
|
if not EAL.does_directory_exist(src):
|
|
log("skip", "dossier absent : {}".format(src))
|
|
return
|
|
if DRY_RUN:
|
|
log("ok", "[dir] {} -> {}".format(src, dst))
|
|
return
|
|
if EAL.rename_directory(src, dst):
|
|
log("ok", "[dir] {} -> {}".format(src, dst))
|
|
else:
|
|
log("fail", "rename_directory a echoue : {}".format(src))
|
|
|
|
|
|
def delete_asset(path):
|
|
if not EAL.does_asset_exist(path):
|
|
log("skip", "deja absent : {}".format(path))
|
|
return
|
|
if DRY_RUN:
|
|
log("ok", "[del] {}".format(path))
|
|
return
|
|
if EAL.delete_asset(path):
|
|
log("ok", "[del] {}".format(path))
|
|
else:
|
|
log("fail", "delete_asset a echoue : {}".format(path))
|
|
|
|
|
|
def fixup_redirectors():
|
|
"""Resout tous les ObjectRedirector du projet et les supprime.
|
|
|
|
C'est l'etape que personne n'avait lancee : deux redirectors de 2024
|
|
trainaient encore dans Data/Item.
|
|
"""
|
|
registry = unreal.AssetRegistryHelpers.get_asset_registry()
|
|
registry.search_all_assets(True)
|
|
|
|
asset_filter = unreal.ARFilter(
|
|
class_paths=[unreal.TopLevelAssetPath("/Script/CoreUObject", "ObjectRedirector")],
|
|
package_paths=["/Game"],
|
|
recursive_paths=True,
|
|
)
|
|
found = registry.get_assets(asset_filter)
|
|
unreal.log("Redirectors trouves : {}".format(len(found)))
|
|
if not found:
|
|
return
|
|
|
|
for data in found:
|
|
unreal.log(" {}".format(data.package_name))
|
|
|
|
if DRY_RUN:
|
|
return
|
|
|
|
redirectors = [data.get_asset() for data in found]
|
|
unreal.AssetToolsHelpers.get_asset_tools().fixup_referencers(redirectors)
|
|
|
|
|
|
def run():
|
|
mode = "SIMULATION (rien n'est modifie)" if DRY_RUN else "EXECUTION REELLE"
|
|
unreal.log("=" * 70)
|
|
unreal.log("Rangement de Content/Game -- {}".format(mode))
|
|
unreal.log("=" * 70)
|
|
|
|
unreal.log("\n--- 1. Redirectors existants ---")
|
|
fixup_redirectors()
|
|
|
|
unreal.log("\n--- 2. Suppressions ---")
|
|
for path in DELETIONS:
|
|
delete_asset(path)
|
|
|
|
unreal.log("\n--- 3. Assets ---")
|
|
for src, dst in ASSET_MOVES:
|
|
move_asset(src, dst)
|
|
|
|
unreal.log("\n--- 4. Dossiers ---")
|
|
for src, dst in DIR_MOVES:
|
|
move_dir(src, dst)
|
|
|
|
unreal.log("\n--- 5. Assets dependant d'un dossier deplace ---")
|
|
for src, dst in ASSET_MOVES_LATE:
|
|
move_asset(src, dst)
|
|
|
|
unreal.log("\n--- 6. Icones egarees hors de Content/Game ---")
|
|
for src, dst in STRAY_MOVES:
|
|
move_asset(src, dst)
|
|
|
|
# Obligatoire avant de supprimer les dossiers d'origine : chaque
|
|
# deplacement y a laisse un redirector, donc ils ne sont pas vides.
|
|
unreal.log("\n--- 7. Resolution des redirectors crees ---")
|
|
fixup_redirectors()
|
|
|
|
unreal.log("\n--- 8. Dossiers vides ---")
|
|
for path in EMPTY_DIRS:
|
|
if not EAL.does_directory_exist(path):
|
|
log("skip", "deja absent : {}".format(path))
|
|
continue
|
|
if DRY_RUN:
|
|
log("ok", "[rmdir] {}".format(path))
|
|
continue
|
|
if EAL.delete_directory(path):
|
|
log("ok", "[rmdir] {}".format(path))
|
|
else:
|
|
log("fail", "dossier non vide : {}".format(path))
|
|
|
|
if not DRY_RUN:
|
|
unreal.log("\n--- 9. Sauvegarde ---")
|
|
EAL.save_directory("/Game/Game", only_if_is_dirty=False, recursive=True)
|
|
|
|
unreal.log("\n" + "=" * 70)
|
|
unreal.log("Termine -- OK: {} SKIP: {} FAIL: {}".format(
|
|
stats["ok"], stats["skip"], stats["fail"]))
|
|
if stats["fail"]:
|
|
unreal.log_error("{} operation(s) en echec : lire le log ci-dessus.".format(stats["fail"]))
|
|
unreal.log("=" * 70)
|
|
|
|
|
|
run()
|