diff --git a/Docs/Migration/08_Guide_Developpeur.md b/Docs/Migration/08_Guide_Developpeur.md index b769019..48f0c01 100644 --- a/Docs/Migration/08_Guide_Developpeur.md +++ b/Docs/Migration/08_Guide_Developpeur.md @@ -71,6 +71,9 @@ Collections, section locale) : animations, vêtements, meubles, cheveux, icônes `LifePlay_Chemins_Fixes` liste les assets à ne jamais déplacer. Après ajout d'assets, les régénérer avec `Tools/BuildCollections.py` (lot O7d, `11_Audit_Optimisation.md` §16.5). +Coiffures et pilosité : listes décrites par les fichiers `Modules/**/*.lphair` (base : `vin_Base/Hairstyles/vin_Base.lphair`) ; +ajouter une coiffure : [`13_Protocole_Coiffure.md`](13_Protocole_Coiffure.md). + `Content/Saves` reçoit les sauvegardes des joueurs : il doit exister, mais reste vide dans le dépôt et l'archive. Les parties de test de l'éditeur ont été déplacées vers `D:\Game\LifePlay_Backup_5.5\O7\Content_Saves` (lot O7a). ## 3. Compiler diff --git a/Docs/Migration/09_Commandes_Utiles.md b/Docs/Migration/09_Commandes_Utiles.md index 1ce8130..163f6d7 100644 --- a/Docs/Migration/09_Commandes_Utiles.md +++ b/Docs/Migration/09_Commandes_Utiles.md @@ -50,6 +50,11 @@ git status; git log --oneline -5; git tag --list '*-valide' # où en est-on & $Cmd $Proj -run=LPCheckStreetMap -CSV=Docs/Migration/etat/streetmap.csv -unattended -nopause & $Cmd $Proj -run=Cook -TargetPlatform=Windows -unattended # T-LOAD-05 +# Coiffure : import FBX, conversion, matériaux (protocole complet : 13_Protocole_Coiffure.md) +& $Cmd $Proj -run=pythonscript "-script=/Tools/HairImportFbx.py fbx= dest=/Game/Heavy/New5/Hairs/_Work" -unattended -nopause +& $Cmd $Proj -run=LPHairFromStaticMesh -Static= -FitHead= -Out=/Game/Heavy/New5/Hairs/ -DryRun -unattended -nopause +& $Cmd $Proj -run=pythonscript "-script=/Tools/HairMaterials.py mesh=/Game/Heavy/New5/Hairs/ diffuse= strands_opacity= cap_opacity=white cap=" -unattended -nopause + # Collections thématiques de l'éditeur (O7d), essai à blanc puis écriture & $Cmd $Proj -run=pythonscript "-script=$($Proj | Split-Path)/Tools/BuildCollections.py -dryrun" -unattended -nopause & $Cmd $Proj -run=pythonscript "-script=$($Proj | Split-Path)/Tools/BuildCollections.py" -unattended -nopause diff --git a/Docs/Migration/13_Protocole_Coiffure.md b/Docs/Migration/13_Protocole_Coiffure.md new file mode 100644 index 0000000..08fd1bb --- /dev/null +++ b/Docs/Migration/13_Protocole_Coiffure.md @@ -0,0 +1,179 @@ +# 13. Protocole de conversion d'une coiffure + +Ce protocole décrit comment ajouter au jeu une coiffure livrée en **maillage statique** (FBX de Fab ou d'ailleurs), +sans modifier aucun Blueprint. Il a été établi sur *Pocolov Hair 17* (lot O7e, `11_Audit_Optimisation.md` §16.8) puis +rejoué de bout en bout avec les outils ci-dessous : résultat identique au millimètre. + +Durée : environ 30 minutes pour une coiffure, contrôles en jeu compris. + +## 1. Ce que le jeu attend d'une coiffure + +| Exigence | Raison | +|---|---| +| `SkeletalMesh` dont les os portent les noms Genesis 8 (`hip`, `pelvis`… `neckUpper`, `head`) | `Person` fait suivre la coiffure au corps par `SetLeaderPoseComponent`, qui associe les os **par leur nom** | +| Placée autour de la tête de Bridget, dans la pose de référence | les sommets sont liés à l'os `head` ; une coiffure mal placée flotte ou traverse le crâne | +| Matériaux enfants de `/Game/Heavy/Daz/Bridget/Hairs/DazHair` | le jeu règle la couleur et l'épaisseur par les paramètres `HairColor` et `HairThickness` | +| Déclarée dans un fichier `Modules//**/*.lphair` | la liste des coiffures du jeu est construite depuis ces fichiers (lots H1, H2) ; un maillage non déclaré n'est jamais utilisé | +| Nom d'objet unique | les personnages désignent leur coiffure par ce nom (`HAIR Hairstyle: ` dans les `.lpcharacter`) | + +Coiffures **exclues** : les « grooms » MetaHuman (liés à la tête MetaHuman ; plugin `HairStrands` limité à l'éditeur +depuis O1). Une coiffure déjà riggée Genesis 8 (export Daz) n'a pas besoin des étapes 2 et 3 : l'importer en +`SkeletalMesh` et passer à l'étape 4. + +## 2. Outils + +| Outil | Rôle | +|---|---| +| `Tools/HairImportFbx.py` | import du FBX en maillages statiques séparés, dans un dossier de travail ; affiche tailles et emplacements de matériaux | +| commandlet `LPHairFromStaticMesh` (`Source/LifePlayEditorTools`) | ajuste la coiffure sur la tête de Bridget et la convertit en `SkeletalMesh` Genesis 8 | +| `Tools/HairMaterials.py` | importe les textures, crée les matériaux `_Cap` et `_Strands`, les affecte, contrôle les dépendances | +| `Tools/BuildCollections.py` | range la coiffure dans la collection `LifePlay_Cheveux` de l'éditeur | +| option de jeu `-LPVerifyHairstyles` | journalise la construction de la liste des coiffures | + +Variables utilisées ci-dessous (PowerShell) : + +```powershell +$UE = 'D:\Game\Launcher\Engine\UE_5.5\Engine\Binaries\Win64\UnrealEditor-Cmd.exe' +$P = 'D:\Game\LifePlay-master 5.5\LifePlay.uproject' +$T = 'D:/Game/LifePlay-master 5.5/Tools' +$W = 'D:/Game/LifePlay_Backup_5.5//src' # sources de travail, chemins SANS espaces +``` + +## 3. Étapes + +### Étape 0 : préparation + +1. Vérifier la **licence** du produit (page Fab : « License terms »). CC BY impose une attribution ; noter auteur, titre, + lien. +2. Télécharger l'archive (accord de l'utilisateur), la copier dans `D:\Game\LifePlay_Backup_5.5\\`. +3. Extraire ; repérer le FBX et les textures. Copier dans `$W` sous des noms **sans espaces** (les arguments des + scripts Python sont séparés par des espaces) : `hair.fbx`, `diffuse.png`, `strands_opacity.jpg`… +4. Identifier les textures en les ouvrant : + - **couleur des mèches** : de préférence grise (elle est teintée par `HairColor`) ; + - **masque des mèches** : blanc = visible, noir = transparent, en bandes verticales ; + - **masque de la calotte** (lisière) : facultatif, voir étape 4. +5. Tag git `-avant`. Choisir le **nom** de la coiffure (unique, sans espace, ex. `Pocolov17`) et son dossier : + `/Game/Heavy/New5/Hairs/` (hors des dossiers à chemin fixe, `08_Guide_Developpeur.md` §2.1). + +### Étape 1 : import + +```powershell +& $UE $P -run=pythonscript "-script=$T/HairImportFbx.py fbx=$W/hair.fbx dest=/Game/Heavy/New5/Hairs/_Work" -unattended -nopause -nosplash +``` + +Lire les lignes `HAIRIMPORT` : repérer le **maillage de la coiffure** et la **tête de référence** souvent livrée avec +(ex. Pocolov : `Hair_17`, 8 250 triangles, emplacements `Hair_Base`, `Hair_01` ; `Head`, 21 262 triangles). Noter +quel emplacement est la **calotte** (ici `Hair_Base`). Sans tête de référence, voir §5. + +### Étape 2 : ajustement et conversion + +Essai à blanc d'abord : + +```powershell +& $UE $P -run=LPHairFromStaticMesh '-Static=/Game/Heavy/New5/Hairs/_Work/Hair_17' '-FitHead=/Game/Heavy/New5/Hairs/_Work/Head' '-Out=/Game/Heavy/New5/Hairs/' -DryRun -unattended -nopause -nosplash +``` + +Contrôler les lignes `LogLPHairFromStatic` : + +| Ligne | Attendu | +|---|---| +| `Tete du corps` | Bridget : haut du crâne ≈ 15 × 20 cm | +| `Tete de reference` | haut du crâne du même ordre | +| `Ajustement : echelle` | entre 0,8 et 1,5 sur chaque axe ; un écart fort entre X et Y signale une tête de référence atypique | +| `Os 'head'` | `correction aucune` (squelette cible identique au corps) | +| `Coiffure placee` | largeur ≈ 25 à 32 cm (Flora : 31 cm), sommet ≈ 181-183 cm | + +Puis sans `-DryRun`. La commandlet refuse d'écraser un asset existant : pour refaire, déplacer d'abord `` et +`_Skeleton` vers la sauvegarde. + +Paramètres de retouche (après un premier contrôle en jeu) : `-OffsetZ=` (cm, monter/descendre), `-OffsetY=` (cm, +avancer/reculer), `-Scale=` (facteur global), `-Margin=` (défaut 1,05 : écart calotte/cuir chevelu), `-Cranium=` (défaut +0,3 : hauteur mesurée sous le sommet, en fraction de la profondeur de la tête), `-Uniform` (même échelle sur X et Y). + +### Étape 3 : textures et matériaux + +```powershell +& $UE $P -run=pythonscript "-script=$T/HairMaterials.py mesh=/Game/Heavy/New5/Hairs/ diffuse=$W/diffuse.png strands_opacity=$W/strands_opacity.jpg cap_opacity=white cap=Hair_Base thicken=1" -unattended -nopause -nosplash +``` + +- `cap=` : emplacements de la calotte (séparés par des virgules) ; tous les autres reçoivent le matériau des mèches. +- `cap_opacity=white` : calotte **opaque** (recommandé : une lisière découpée laisse voir le crâne entre les mèches) ; + sinon le fichier du masque de la calotte. +- `thicken=` : épaississement du masque des mèches. **1** pour un masque large (cartes de mèches, Pocolov) ; 10 pour un + masque de mèches fines (coiffures Daz). Une valeur trop forte donne des cartes opaques. + +Contrôle : la dernière ligne `HAIRMAT dependances … -> OK` (la coiffure ne dépend que de son squelette et de ses deux +matériaux). + +### Étape 4 : nettoyage + +Déplacer `Content/Heavy/New5/Hairs/_Work` vers `D:\Game\LifePlay_Backup_5.5\\` (il ne doit pas être cuit dans le +jeu). Ne rien supprimer sans copie. + +### Étape 5 : déclaration + +Fichier `Content/Modules//Hairstyles/.lphair` (module existant, ex. `lp_Revival`, ou module du mod) : + +``` +// - - licence +// Source : +// Modifications : rattachee au squelette Genesis 8 (os head), ajustee a la tete de Bridget, materiaux DazHair. +HAIRSTYLE: /Game/Heavy/New5/Hairs/. +SEX: F +DAZ: true +``` + +Licence à attribution : ajouter la même mention dans `Licenses/Free_3D_Models_Credits.txt`. + +### Étape 6 : collections, build, contrôle du journal + +```powershell +& $UE $P -run=pythonscript "-script=$T/BuildCollections.py" -unattended -nopause -nosplash +``` + +Build Development (`09_Commandes_Utiles.md` §2) dans un **nouveau** dossier d'archive (la mise en place avec `-iterate` +ne retire pas les fichiers supprimés). Lancer le jeu ; le journal doit contenir : + +``` +LogLifePlay: Coiffures : ajoutee (index N, F, module ) +``` + +### Étape 7 : contrôle en jeu (utilisateur) + +| # | Contrôle | Défaut typique → correction | +|---|---|---| +| 1 | Création de personnage du bon sexe : la coiffure est en fin de liste | absente : chemin du `.lphair`, module désactivé, journal | +| 2 | **De face** : le crâne est couvert | zones chauves, coiffure étroite : vérifier l'ajustement (§4), `-Scale`, `-Margin` | +| 3 | **De profil** : rien ne traverse le crâne, l'oreille, les épaules ; pas de trou au-dessus de l'oreille | trous : `cap_opacity=white` ; trop haute/basse : `-OffsetZ` ; trop en avant/arrière : `-OffsetY` | +| 4 | Couleur et épaisseur réglables dans le menu | sans effet : matériau non `DazHair` | +| 5 | Mouvement (marche, animation) : la coiffure suit la tête | décalée : os `head` absent ou correction de pose non nulle | +| 6 | Nouvelle partie (liste des villes) et Continue | — | + +Puis : entrée CHG dans `Journal_Modifications.md`, commit, tags `-apres` / `-valide`. + +## 4. Pièges rencontrés (O7e) + +| Piège | Effet | Règle | +|---|---|---| +| Ajuster sur la tête **entière** | la tête Pocolov a des oreilles très larges (24,4 cm) : coiffure rétrécie à 74 %, enfoncée dans le crâne | ajuster sur le **haut du crâne** (option `-Cranium`, par défaut) | +| `Thicken` des coiffures Daz (10) sur un masque large | mèches en cartes noires opaques | `thicken=1` pour les cartes de mèches | +| Lisière découpée de la calotte | crâne visible au-dessus de l'oreille | `cap_opacity=white` | +| Affectation des matériaux en Python | `save_loaded_asset` ne sauve que les assets « modifiés » ; une affectation par propriété ne les marque pas | sauvegarder avec `only_if_is_dirty=False` (fait par `HairMaterials.py`) | +| Matériaux importés du FBX | souvent sans texture reliée | ne pas importer les matériaux (`HairImportFbx.py`) ; recréer avec `HairMaterials.py` | +| Comparer des noms d'assets | `soldier` et `Soldier` désignent le même asset | comparaisons insensibles à la casse | + +## 5. Cas particuliers + +- **Pas de tête de référence** dans le FBX : utiliser la coiffure elle-même comme `-FitHead` (son haut est la calotte), + avec `-Margin=1.0`, puis corriger en jeu avec `-Scale` et `-OffsetZ`. +- **Coiffure masculine** : `SEX: M` dans le `.lphair` (le menu du personnage masculin la propose). +- **Morphs de forme** : non générés (la coiffure ne suit pas les morphs du visage). Un morph existant de la coiffure peut + être déclaré avec `FIT_MORPH: `. +- **Mèches animées** : non gérées (coiffure rigide sur la tête). +- **Coiffure apportée par un mod sans recompiler le jeu** : impossible aujourd'hui (le maillage doit être cuit avec le + jeu) ; étape H3 prévue. + +## 6. Retour arrière + +Retirer le fichier `.lphair` suffit à retirer la coiffure du jeu. Pour retirer aussi les assets : déplacer +`Content/Heavy/New5/Hairs/*` vers la sauvegarde, régénérer les collections, reconstruire. diff --git a/Docs/Migration/Journal_Modifications.md b/Docs/Migration/Journal_Modifications.md index 378a96c..dc697ed 100644 --- a/Docs/Migration/Journal_Modifications.md +++ b/Docs/Migration/Journal_Modifications.md @@ -935,3 +935,4 @@ Validation L7c/L7d (2026-10-02, parc, même cadrage) : la nuit reste claire (sol | CHG-088 | 2026-10-02 | O7e | — | O7e validé en jeu par l'utilisateur (profil sans trou, mouvement suivi) ; défaut mineur accepté : bas de la calotte visible sous le lobe de l'oreille | — | | CHG-089 | 2026-10-02 | H2 | `Source/LifePlay/LPHairstyleSubsystem.h/.cpp` (mode base, plusieurs coiffures par fichier, `NONE`, `MORPHABLE`, vérification `-LPVerifyHairstyles`) ; nouveau `Content/Modules/vin_Base/Hairstyles/vin_Base.lphair` (126 coiffures, 89 morphs) ; `11_Audit_Optimisation.md` §16.9 | liste de base des coiffures sortie du Blueprint (demande utilisateur) ; vérifié : table et morphs reconstruits identiques au Blueprint ; build `LifePlay_Build_5.5_H2` ; validé en jeu le 2026-10-02 (nouvelle partie, liste du menu inchangée avec Pocolov17 en dernier, personnages générés, Continue) | supprimer `vin_Base.lphair` (retour à la table du Blueprint) ; `git checkout H2-avant -- Source/LifePlay` | | CHG-090 | 2026-10-02 | H2b | `Source/LifePlay/LPHairstyleSubsystem.h/.cpp` (clés `FACIAL_HAIR`, `BODY_HAIR_M`, `BODY_HAIR_F`) ; `Content/Modules/vin_Base/Hairstyles/vin_Base.lphair` (+ 44 noms) ; `11_Audit_Optimisation.md` §16.10 | pilosité sortie du Blueprint (demande utilisateur) : listes `GI.FHairstyles`, `BHairstyles_M`, `BHairstyles` ; `GI.OtherTable` laissée (maillages absents depuis 4.27) ; vérifié : 3 listes identiques au Blueprint ; build `LifePlay_Build_5.5_H2` ; validé en jeu le 2026-10-02 (28 barbes + sans barbe dans le menu de création) | retirer les lignes `*_HAIR` de `vin_Base.lphair` ; `git checkout H2b-avant -- Source/LifePlay` | +| CHG-091 | 2026-10-02 | Doc | nouveaux `Docs/Migration/13_Protocole_Coiffure.md`, `Tools/HairImportFbx.py`, `Tools/HairMaterials.py` ; `README.md` (migration), `08_Guide_Developpeur.md`, `09_Commandes_Utiles.md` | protocole de conversion d'une coiffure (demande utilisateur) ; scripts ponctuels d'O7e rendus paramétrables ; protocole rejoué de bout en bout sur Pocolov 17 sous le nom `Pocolov17P` : bornes identiques au millimètre, dépendances OK ; essai retiré du projet (`D:\Game\LifePlay_Backup_5.5\O7e\proto_essai`) | `git revert` du commit | diff --git a/Docs/Migration/README.md b/Docs/Migration/README.md index 286f396..e8c9f33 100644 --- a/Docs/Migration/README.md +++ b/Docs/Migration/README.md @@ -23,6 +23,7 @@ La migration a été reprise le 2026-09-29, après l'échec d'un premier portage | [10_Protocole_Operations.md](10_Protocole_Operations.md) | **obligatoire avant toute modification** | déroulé d'un lot, règles de diagnostic, leçons de la campagne, tests avec l'utilisateur | | [11_Audit_Optimisation.md](11_Audit_Optimisation.md) | pilotage | chantier d'optimisation UE5 : audit (lot O0), goulots, leviers, lots O1 à O8, Fab | | [12_Reconciliation_Vinfamy.md](12_Reconciliation_Vinfamy.md) | historique | comparaison avec `vinfamy/LifePlay`, fusion d'historique, rien à récupérer | +| [13_Protocole_Coiffure.md](13_Protocole_Coiffure.md) | protocole | ajouter une coiffure (FBX statique, Fab) : import, ajustement sur la tête de Bridget, matériaux, fichier `.lphair`, contrôles | | [Journal_Modifications.md](Journal_Modifications.md) | traçabilité | `CHG-001` à `CHG-057`, chacune avec son retour arrière | | [Registre_Erreurs.md](Registre_Erreurs.md) | traçabilité | `ERR-001` à `ERR-037`, statut et lot | | `correctifs/` | relecture | écarts de code 4.27 → 5.5 en patchs (`P01` à `P06`) | diff --git a/Tools/HairImportFbx.py b/Tools/HairImportFbx.py new file mode 100644 index 0000000..70e08a4 --- /dev/null +++ b/Tools/HairImportFbx.py @@ -0,0 +1,46 @@ +# Protocole de conversion d'une coiffure (Docs/Migration/13_Protocole_Coiffure.md), etape 2 : import du FBX. +# +# Importe les maillages d'un FBX en maillages statiques separes (sans materiaux ni textures) dans un dossier de travail, +# puis affiche pour chacun : nom, boite englobante, triangles, emplacements de materiaux. Ces informations servent a +# choisir le maillage de la coiffure (-Static) et la tete de reference (-FitHead) de LPHairFromStaticMesh. +# +# Usage : +# UnrealEditor-Cmd.exe -run=pythonscript "-script=/Tools/HairImportFbx.py fbx= dest=/Game/Heavy/New5/Hairs/_Work" -unattended -nopause -nosplash +# Sortie : lignes "HAIRIMPORT" du journal. + +import sys +import unreal + +args = dict(a.split("=", 1) for a in sys.argv[1:] if "=" in a) +fbx = args.get("fbx") +dest = args.get("dest", "/Game/Heavy/New5/Hairs/_Work") +if not fbx: + unreal.log_error("HAIRIMPORT usage : fbx= [dest=/Game/...]") +else: + task = unreal.AssetImportTask() + task.filename = fbx.replace("\\", "/") + task.destination_path = dest + task.automated = True + task.replace_existing = True + task.save = True + ui = unreal.FbxImportUI() + ui.import_mesh = True + ui.import_as_skeletal = False + ui.import_materials = False + ui.import_textures = False + ui.mesh_type_to_import = unreal.FBXImportType.FBXIT_STATIC_MESH + ui.static_mesh_import_data.combine_meshes = False + ui.static_mesh_import_data.auto_generate_collision = False + task.options = ui + unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) + count = 0 + for path in task.imported_object_paths: + asset = unreal.load_asset(path) + if isinstance(asset, unreal.StaticMesh): + box = asset.get_bounding_box() + size = box.max - box.min + slots = [str(m.material_slot_name) for m in asset.static_materials] + unreal.log_warning("HAIRIMPORT %s : %d triangles, taille X=%.1f Y=%.1f Z=%.1f cm, min Z=%.1f max Z=%.1f, emplacements %s" + % (path, asset.get_num_triangles(0), size.x, size.y, size.z, box.min.z, box.max.z, slots)) + count += 1 + unreal.log_warning("HAIRIMPORT %d maillage(s) statique(s) importe(s) dans %s" % (count, dest)) diff --git a/Tools/HairMaterials.py b/Tools/HairMaterials.py new file mode 100644 index 0000000..1a73f77 --- /dev/null +++ b/Tools/HairMaterials.py @@ -0,0 +1,83 @@ +# Protocole de conversion d'une coiffure (Docs/Migration/13_Protocole_Coiffure.md), etape 4 : textures et materiaux. +# +# Importe les textures de la coiffure, cree deux materiaux enfants de /Game/Heavy/Daz/Bridget/Hairs/DazHair (le +# materiau des coiffures Daz : parametres HairColor et HairThickness pilotes par le jeu) et les affecte aux +# emplacements du SkeletalMesh produit par LPHairFromStaticMesh : +# _Cap : emplacements listes dans cap= (calotte) ; opacite cap_opacity= ou "white" (calotte opaque) +# _Strands : tous les autres emplacements (meches) ; opacite strands_opacity= +# Controle final : dependances du SkeletalMesh (squelette et ces deux materiaux seulement). +# +# Usage : +# UnrealEditor-Cmd.exe -run=pythonscript "-script=/Tools/HairMaterials.py mesh=/Game/Heavy/New5/Hairs/Nom +# diffuse= strands_opacity= cap_opacity=white cap=Hair_Base [thicken=1]" -unattended -nopause -nosplash +# Les fichiers de textures sont des chemins sur disque ; les textures sont creees a cote du SkeletalMesh. +# Sortie : lignes "HAIRMAT" du journal. + +import os +import sys +import unreal + +eal = unreal.EditorAssetLibrary +mel = unreal.MaterialEditingLibrary +tools = unreal.AssetToolsHelpers.get_asset_tools() +args = dict(a.split("=", 1) for a in sys.argv[1:] if "=" in a) + + +def import_texture(file, folder, name): + task = unreal.AssetImportTask() + task.filename = file.replace("\\", "/") + task.destination_path = folder + task.destination_name = name + task.automated = True + task.replace_existing = True + task.save = True + tools.import_asset_tasks([task]) + return unreal.load_asset(folder + "/" + name) + + +def make_mic(folder, name, parent, diffuse, opacity, thicken): + path = folder + "/" + name + mic = unreal.load_asset(path) if eal.does_asset_exist(path) else tools.create_asset( + name, folder, unreal.MaterialInstanceConstant, unreal.MaterialInstanceConstantFactoryNew()) + mel.set_material_instance_parent(mic, parent) + mel.set_material_instance_texture_parameter_value(mic, "MapDiffuse", diffuse) + mel.set_material_instance_texture_parameter_value(mic, "MapOpacity", opacity) + mel.set_material_instance_scalar_parameter_value(mic, "Thicken", thicken) + mel.update_material_instance(mic) + eal.save_loaded_asset(mic, only_if_is_dirty=False) + return mic + + +mesh_path = args.get("mesh") +if not mesh_path or not args.get("diffuse") or not args.get("strands_opacity"): + unreal.log_error("HAIRMAT usage : mesh=/Game/... diffuse= strands_opacity= [cap_opacity=|white] [cap=Slot1,Slot2] [thicken=1]") +else: + folder, name = mesh_path.rsplit("/", 1) + thicken = float(args.get("thicken", "1")) + cap_slots = [s for s in args.get("cap", "").split(",") if s] + parent = unreal.load_asset("/Game/Heavy/Daz/Bridget/Hairs/DazHair") + diffuse = import_texture(args["diffuse"], folder, name + "_Diffuse") + strands_opacity = import_texture(args["strands_opacity"], folder, name + "_StrandsOpacity") + cap_arg = args.get("cap_opacity", "white") + cap_opacity = unreal.load_asset("/Engine/EngineResources/WhiteSquareTexture") if cap_arg == "white" else import_texture(cap_arg, folder, name + "_CapOpacity") + strands = make_mic(folder, name + "_Strands", parent, diffuse, strands_opacity, thicken) + cap = make_mic(folder, name + "_Cap", parent, diffuse, cap_opacity, 1.0) + + # Affectation : modifier les elements puis reaffecter le tableau entier et sauvegarder sans condition + # (une affectation Python ne marque pas l'asset modifie). + mesh = unreal.load_asset(mesh_path) + materials = [] + for slot in mesh.get_editor_property("materials"): + slot_name = str(slot.get_editor_property("material_slot_name")) + slot.set_editor_property("material_interface", cap if slot_name in cap_slots else strands) + materials.append(slot) + unreal.log_warning("HAIRMAT emplacement %s -> %s" % (slot_name, (cap if slot_name in cap_slots else strands).get_name())) + mesh.set_editor_property("materials", materials) + eal.save_loaded_asset(mesh, only_if_is_dirty=False) + + registry = unreal.AssetRegistryHelpers.get_asset_registry() + registry.scan_modified_asset_files([unreal.Paths.convert_relative_path_to_full(unreal.Paths.project_content_dir()) + mesh_path[len("/Game/"):] + ".uasset"]) + options = unreal.AssetRegistryDependencyOptions(include_soft_package_references=True, include_hard_package_references=True) + deps = [str(d) for d in registry.get_dependencies(mesh_path, options) if not str(d).startswith("/Script/")] + expected = {mesh_path + "_Skeleton", folder + "/" + name + "_Cap", folder + "/" + name + "_Strands"} + unreal.log_warning("HAIRMAT dependances de %s : %s -> %s" % (name, deps, "OK" if set(deps) == expected else "A VERIFIER"))