Protocole de conversion d'une coiffure (13_Protocole_Coiffure.md) et outils HairImportFbx, HairMaterials
Protocole rejoue de bout en bout sur Pocolov 17 : resultat identique. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
eb6cda3106
commit
2744242609
7 changed files with 318 additions and 0 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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=<projet>/Tools/HairImportFbx.py fbx=<f.fbx> dest=/Game/Heavy/New5/Hairs/_Work" -unattended -nopause
|
||||
& $Cmd $Proj -run=LPHairFromStaticMesh -Static=<maillage> -FitHead=<tete> -Out=/Game/Heavy/New5/Hairs/<Nom> -DryRun -unattended -nopause
|
||||
& $Cmd $Proj -run=pythonscript "-script=<projet>/Tools/HairMaterials.py mesh=/Game/Heavy/New5/Hairs/<Nom> diffuse=<f> strands_opacity=<f> cap_opacity=white cap=<emplacement>" -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
|
||||
|
|
|
|||
179
Docs/Migration/13_Protocole_Coiffure.md
Normal file
179
Docs/Migration/13_Protocole_Coiffure.md
Normal file
|
|
@ -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/<module>/**/*.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: <nom>` 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 `<Nom>_Cap` et `<Nom>_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/<lot>/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\<lot>\`.
|
||||
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 `<lot>-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/<Nom>' -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 `<Nom>` et
|
||||
`<Nom>_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/<Nom> 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\<lot>\` (il ne doit pas être cuit dans le
|
||||
jeu). Ne rien supprimer sans copie.
|
||||
|
||||
### Étape 5 : déclaration
|
||||
|
||||
Fichier `Content/Modules/<module>/Hairstyles/<nom>.lphair` (module existant, ex. `lp_Revival`, ou module du mod) :
|
||||
|
||||
```
|
||||
// <Titre> - <auteur> - licence <licence>
|
||||
// Source : <lien>
|
||||
// Modifications : rattachee au squelette Genesis 8 (os head), ajustee a la tete de Bridget, materiaux DazHair.
|
||||
HAIRSTYLE: /Game/Heavy/New5/Hairs/<Nom>.<Nom>
|
||||
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 : <Nom> ajoutee (index N, F, module <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 `<lot>-apres` / `<lot>-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: <morph> <valeur>`.
|
||||
- **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/<Nom>*` vers la sauvegarde, régénérer les collections, reconstruire.
|
||||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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`) |
|
||||
|
|
|
|||
46
Tools/HairImportFbx.py
Normal file
46
Tools/HairImportFbx.py
Normal file
|
|
@ -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 <projet> -run=pythonscript "-script=<projet>/Tools/HairImportFbx.py fbx=<fichier.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=<fichier.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))
|
||||
83
Tools/HairMaterials.py
Normal file
83
Tools/HairMaterials.py
Normal file
|
|
@ -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 :
|
||||
# <Nom>_Cap : emplacements listes dans cap= (calotte) ; opacite cap_opacity= ou "white" (calotte opaque)
|
||||
# <Nom>_Strands : tous les autres emplacements (meches) ; opacite strands_opacity=
|
||||
# Controle final : dependances du SkeletalMesh (squelette et ces deux materiaux seulement).
|
||||
#
|
||||
# Usage :
|
||||
# UnrealEditor-Cmd.exe <projet> -run=pythonscript "-script=<projet>/Tools/HairMaterials.py mesh=/Game/Heavy/New5/Hairs/Nom
|
||||
# diffuse=<couleur.png> strands_opacity=<masque.jpg> 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=<fichier> strands_opacity=<fichier> [cap_opacity=<fichier>|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"))
|
||||
Loading…
Reference in a new issue