From 884268479ed10cb8afb58af75b9f33d9a158b75a Mon Sep 17 00:00:00 2001 From: edujiro5972 <336072401+edujiro5972@users.noreply.github.com> Date: Wed, 7 Oct 2026 15:49:12 +0200 Subject: [PATCH] Sandbox / AnimToGenesis : LPDirectControlSubsystem (mannequin Quaternius, copie vers Genesis), ImportQuaternius, DiagnosePose Phase Sandbox/AnimToGenesis (CHG-168..186). Co-Authored-By: Claude Opus 5.5 --- Source/LifePlay/LPDirectControlSubsystem.cpp | 16 ++- Source/LifePlay/LPDirectControlSubsystem.h | 4 +- Tools/DiagnosePose.py | 119 +++++++++++++++++++ Tools/ImportQuaternius.py | 40 +++++++ 4 files changed, 173 insertions(+), 6 deletions(-) create mode 100644 Tools/DiagnosePose.py create mode 100644 Tools/ImportQuaternius.py diff --git a/Source/LifePlay/LPDirectControlSubsystem.cpp b/Source/LifePlay/LPDirectControlSubsystem.cpp index fe88c6c..8249b86 100644 --- a/Source/LifePlay/LPDirectControlSubsystem.cpp +++ b/Source/LifePlay/LPDirectControlSubsystem.cpp @@ -7,6 +7,8 @@ #include "LPDirectControlSubsystem.h" +#include "LPAnimRedirect.h" + #include "Animation/AnimInstance.h" #include "Animation/AnimationAsset.h" #include "Blueprint/UserWidget.h" @@ -22,6 +24,7 @@ #include "EngineUtils.h" #include "Framework/Application/SlateApplication.h" #include "GameFramework/Character.h" +#include "GameFramework/GameModeBase.h" #include "GameFramework/CharacterMovementComponent.h" #include "GameFramework/InputSettings.h" #include "GameFramework/PlayerController.h" @@ -872,6 +875,13 @@ void ULPDirectControlSubsystem::Walk(float DeltaTime) const float Forward = AxisValue(PC, ForwardAxis); const float Right = AxisValue(PC, RightAxis); const bool bMoving = !FMath::IsNearlyZero(Forward) || !FMath::IsNearlyZero(Right); + const AGameModeBase* Mode = World->GetAuthGameMode(); + const bool bSandbox = Mode && Mode->GetClass()->GetName().Contains(TEXT("LPPairSandbox")); + if (Character && !bSandbox && LPAnimRedirect::IsGameTarget()) + { + const float Speed = bMoving ? FMath::Max(Character->GetVelocity().Size(), 20.f) : 0.f; + WalkAnim(Character, Speed); + } if (bMoving) { const FRotator Yaw(0.f, PC->GetControlRotation().Yaw, 0.f); @@ -947,10 +957,8 @@ void ULPDirectControlSubsystem::WalkAnim(ACharacter* Character, float Speed) { bMale = Male->GetPropertyValue_InContainer(Character); } - const TCHAR* Path = bMale - ? TEXT("/Game/Heavy/New2/Anims2/walk_M.walk_M") - : TEXT("/Game/Heavy/New2/Anims2/walk_F.walk_F"); - UAnimationAsset* WalkCycle = LoadObject(nullptr, Path); + const FString Path = LPAnimRedirect::ClipPath(bMale ? TEXT("M_Walk_Loop") : TEXT("B_Walk_Loop")); + UAnimationAsset* WalkCycle = LoadObject(nullptr, *Path); if (!WalkCycle) { return; diff --git a/Source/LifePlay/LPDirectControlSubsystem.h b/Source/LifePlay/LPDirectControlSubsystem.h index 959946b..a6347c2 100644 --- a/Source/LifePlay/LPDirectControlSubsystem.h +++ b/Source/LifePlay/LPDirectControlSubsystem.h @@ -8,8 +8,8 @@ // .lpnear (MESH, PROMPT, ACTION). La liste est tenue ici, sur la personne ou le maillage le // plus proche, et elle reste cachee. E l'affiche ; un second E, ou un clic, lance l'action. // Le personnage n'est pas possede : la marche active bRunPhysicsWithNoController. -// Le cycle walk_F et la camera suiveuse ne sont plus appliques ici : ils cassaient -// le corps et la vue. L'essai est la sandbox interaction (LifePlay_interaction_sandbox.bat). +// La marche du jeu ne joue un cycle que si LPAnimTarget.ini vaut game (clips B_ / M_). +// sandbox laisse le corps au Blueprint. La camera suiveuse n'est pas branchee. // Aucun Blueprint n'est modifie. #pragma once diff --git a/Tools/DiagnosePose.py b/Tools/DiagnosePose.py new file mode 100644 index 0000000..d53296c --- /dev/null +++ b/Tools/DiagnosePose.py @@ -0,0 +1,119 @@ +# Mesure la pose Idle (image 0) : mannequin, Bridget, Michael. +# But : voir si la main passe derriere le buste, et de quel angle le bras est roule. + +import unreal + +PAIRS = ( + ("source", "/Game/Quaternius/UAL/Idle_Loop.Idle_Loop", "/Game/Quaternius/UAL/UAL1_Standard.UAL1_Standard", + ("pelvis", "spine_03", "clavicle_l", "upperarm_l", "lowerarm_l", "hand_l", + "clavicle_r", "upperarm_r", "lowerarm_r", "hand_r")), + ("bridget", "/Game/Quaternius/UAL/B_Idle_Loop.B_Idle_Loop", "/Game/Heavy/Daz/Bridget/bridget_gp.bridget_gp", + ("pelvis", "chestUpper", "lCollar", "lShldrBend", "lForearmBend", "lHand", + "rCollar", "rShldrBend", "rForearmBend", "rHand")), + ("michael", "/Game/Quaternius/UAL/M_Idle_Loop.M_Idle_Loop", "/Game/Heavy/Daz/Michael/michael.michael", + ("pelvis", "chestUpper", "lCollar", "lShldrBend", "lForearmBend", "lHand", + "rCollar", "rShldrBend", "rForearmBend", "rHand")), +) + +FINGERS = ( + "lThumb1", "lIndex1", "lMid1", "lRing1", "lPinky1", + "thumb_01_l", "index_01_l", "middle_01_l", "pinky_01_l", + "hand_l", "lHand", +) + + +def dot(a, b): + return a.x * b.x + a.y * b.y + a.z * b.z + + +def sub(a, b): + return unreal.MathLibrary.subtract_vector_vector(a, b) + + +def norm(v): + return unreal.MathLibrary.normal(v) + + +def cross(a, b): + return unreal.MathLibrary.cross_vector_vector(a, b) + + +def fmt(v): + return "(%.1f %.1f %.1f)" % (v.x, v.y, v.z) + + +def bone_names(mesh): + pose = mesh.skeleton.get_reference_pose() + names = unreal.AnimPoseExtensions.get_bone_names(pose) + return [str(n) for n in names] + + +def pose_at_zero(sequence): + for call in ( + lambda: unreal.AnimPoseExtensions.get_anim_pose_at_time(sequence, 0.0), + lambda: unreal.AnimPoseExtensions.get_anim_pose_at_frame(sequence, 0), + ): + try: + return call() + except Exception as exc: + unreal.log_warning("POSE api %s" % exc) + raise RuntimeError("pas de lecture de pose") + + +def bone_tm(pose, name): + return unreal.AnimPoseExtensions.get_bone_pose(pose, name, unreal.AnimPoseSpaces.WORLD) + + +def main(): + unreal.log_warning("POSE debut") + for label, anim_path, mesh_path, bones in PAIRS: + anim = unreal.load_asset(anim_path) + mesh = unreal.load_asset(mesh_path) + if not anim or not mesh: + unreal.log_error("POSE absent %s" % label) + continue + names = bone_names(mesh) + pose = pose_at_zero(anim) + got = {} + for bone in bones: + if bone not in names: + unreal.log_warning("POSE %s os absent %s" % (label, bone)) + continue + got[bone] = bone_tm(pose, bone).translation + pelvis = got.get("pelvis") + chest_name = "spine_03" if "spine_03" in got else "chestUpper" + chest = got.get(chest_name) + if not pelvis or not chest: + unreal.log_error("POSE %s sans buste" % label) + continue + up = norm(sub(chest, pelvis)) + # Avant approximatif : produit du haut du buste et de l'axe gauche-droite des epaules. + left = got.get("clavicle_l") or got.get("upperarm_l") or got.get("lCollar") or got.get("lShldrBend") + right = got.get("clavicle_r") or got.get("upperarm_r") or got.get("rCollar") or got.get("rShldrBend") + if not left or not right: + unreal.log_error("POSE %s sans epaules" % label) + continue + side = norm(sub(left, right)) + forward = norm(cross(side, up)) + unreal.log_warning("POSE %s avant %s" % (label, fmt(forward))) + for side_name, hand_name, elbow_name in ( + ("gauche", "hand_l" if "hand_l" in got else "lHand", "lowerarm_l" if "lowerarm_l" in got else "lForearmBend"), + ("droite", "hand_r" if "hand_r" in got else "rHand", "lowerarm_r" if "lowerarm_r" in got else "rForearmBend"), + ): + hand = got.get(hand_name) + elbow = got.get(elbow_name) + if not hand: + continue + rel = sub(hand, chest) + ahead = dot(rel, forward) + unreal.log_warning( + "POSE %s main %s %s devant %.1f pos %s" % (label, side_name, hand_name, ahead, fmt(hand))) + if elbow: + unreal.log_warning("POSE %s coude %s %s" % (label, side_name, fmt(elbow))) + present = [n for n in FINGERS if n in names] + unreal.log_warning("POSE %s doigts %s" % (label, " ".join(present))) + unreal.log_warning("POSE fin") + unreal.SystemLibrary.quit_editor() + + +main() diff --git a/Tools/ImportQuaternius.py b/Tools/ImportQuaternius.py new file mode 100644 index 0000000..6f05e40 --- /dev/null +++ b/Tools/ImportQuaternius.py @@ -0,0 +1,40 @@ +# Importe le mannequin et les animations CC0 de Quaternius (Universal Animation Library, sans root motion). +# +# Usage : +# UnrealEditor-Cmd.exe -run=pythonscript "-script=/Tools/ImportQuaternius.py" -unattended -nopause -nosplash +# Sortie : lignes "QUATERNIUS" du journal. + +import unreal + +glb = "D:/Game/Universal Animation Library[Standard]/Unreal-Godot/UAL1_Standard.glb" +dest = "/Game/Quaternius/UAL" + +task = unreal.AssetImportTask() +task.filename = glb +task.destination_path = dest +task.automated = True +task.replace_existing = True +task.save = True +unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) + +reported = list(task.imported_object_paths) +unreal.log_warning("QUATERNIUS tache %d objet(s)" % len(reported)) +for path in reported: + unreal.log_warning("QUATERNIUS PATH %s" % path) + +if not unreal.EditorAssetLibrary.does_directory_exist(dest): + unreal.log_error("QUATERNIUS dossier absent %s" % dest) +else: + unreal.EditorAssetLibrary.save_directory(dest, False, True) + assets = unreal.EditorAssetLibrary.list_assets(dest, True, False) + unreal.log_warning("QUATERNIUS dossier %d asset(s)" % len(assets)) + for path in assets: + asset = unreal.load_asset(path) + kind = asset.get_class().get_name() if asset else "null" + extra = "" + if isinstance(asset, unreal.SkeletalMesh): + box = asset.get_bounds() + extra = " origin=(%.1f %.1f %.1f) extent=(%.1f %.1f %.1f)" % ( + box.origin.x, box.origin.y, box.origin.z, + box.box_extent.x, box.box_extent.y, box.box_extent.z) + unreal.log_warning("QUATERNIUS ASSET %s %s%s" % (kind, path, extra))