From 92727bdc2f48557e8747ec9acbc5b2565902e436 Mon Sep 17 00:00:00 2001 From: ergoxiv Date: Sat, 2 Nov 2024 15:01:29 +0100 Subject: [PATCH 1/3] Default import now restores face positions on face mismatch; Simplified import process --- Anamnesis/Actor/Pages/PosePage.xaml.cs | 62 +++++++++---------- Anamnesis/Actor/Refresh/BrioActorRefresher.cs | 8 +-- Anamnesis/Files/PoseFile.cs | 22 +------ Anamnesis/Files/SceneFile.cs | 2 +- Anamnesis/Views/TargetSelectorView.xaml.cs | 2 +- 5 files changed, 38 insertions(+), 58 deletions(-) diff --git a/Anamnesis/Actor/Pages/PosePage.xaml.cs b/Anamnesis/Actor/Pages/PosePage.xaml.cs index 0f79c571..a3d87e12 100644 --- a/Anamnesis/Actor/Pages/PosePage.xaml.cs +++ b/Anamnesis/Actor/Pages/PosePage.xaml.cs @@ -379,7 +379,8 @@ private async Task ImportPose(PoseImportOptions importOption, PoseFile.Mode mode if (result.File is not PoseFile poseFile) return; - Dictionary bonePositions = new(); + Dictionary bodyPositions = new(); + Dictionary facePositions = new(); bool mismatchedFaceBones = false; // Disable auto-commit at the beginning @@ -411,22 +412,29 @@ private async Task ImportPose(PoseImportOptions importOption, PoseFile.Mode mode PoseService.Instance.SetEnabled(true); PoseService.Instance.FreezeScale |= mode.HasFlag(PoseFile.Mode.Scale); PoseService.Instance.FreezeRotation |= mode.HasFlag(PoseFile.Mode.Rotation); + PoseService.Instance.FreezePositions = mode.HasFlag(PoseFile.Mode.Position); if (importOption == PoseImportOptions.SelectedBones) { - PoseService.Instance.FreezePositions = mode.HasFlag(PoseFile.Mode.Position); - // Don't unselected bones after import. Let the user decide what to do with the selection. var selectedBones = this.Skeleton.SelectedBones.Select(bone => bone.BoneName).ToHashSet(); - await poseFile.Apply(this.Actor, this.Skeleton, selectedBones, mode, false); + poseFile.Apply(this.Actor, this.Skeleton, selectedBones, mode, false); return; } + // Backup face bone positions before importing the body pose. + // "Freeze Position" toggle resets them, so restore after import. Relevant only when pose service is enabled. + this.Skeleton.SelectHead(); + facePositions = this.Skeleton.SelectedBones.ToDictionary(bone => bone, bone => bone.Position); + this.Skeleton.ClearSelection(); + // Step 1: Import body part of the pose if (importOption is PoseImportOptions.Character or PoseImportOptions.FullTransform or PoseImportOptions.BodyOnly) { this.Skeleton.SelectBody(); var selectedBoneNames = this.Skeleton.SelectedBones.Select(bone => bone.BoneName).ToHashSet(); + bodyPositions = this.Skeleton.SelectedBones.ToDictionary(bone => bone, bone => bone.Position); + this.Skeleton.ClearSelection(); // Don't import body with positions unless it's "Full Transform". // Otherwise, the body will be deformed if the pose file was created for another race. @@ -436,28 +444,22 @@ private async Task ImportPose(PoseImportOptions importOption, PoseFile.Mode mode mode &= ~PoseFile.Mode.Position; } - PoseService.Instance.FreezePositions = mode.HasFlag(PoseFile.Mode.Position); - // Don't apply the facial expression hack for the body import step. // Otherwise, the head won't pose as intended and will return to its original position. - await poseFile.Apply(this.Actor, this.Skeleton, selectedBoneNames, mode, false); + poseFile.Apply(this.Actor, this.Skeleton, selectedBoneNames, mode, false); // Re-enable positions if they were disabled. if (doLegacyImport) { mode |= PoseFile.Mode.Position; - PoseService.Instance.FreezePositions |= mode.HasFlag(PoseFile.Mode.Position); + foreach ((BoneVisual3d bone, Vector3 position) in bodyPositions) + { + bone.Position = position; + bone.WriteTransform(this.Skeleton, false); + } } - - // Store updated bone positions as they will get reset by the expression import - bonePositions = this.Skeleton.SelectedBones.Select(bone => (bone.BoneName, bone.Position)).ToDictionary(t => t.BoneName, t => t.Position); - this.Skeleton.ClearSelection(); } - // Body pose is loaded. Exit early if we're only importing the body. - if (importOption == PoseImportOptions.BodyOnly) - return; - // Step 2: Import the facial expression if (!mismatchedFaceBones && (importOption is PoseImportOptions.Character or PoseImportOptions.FullTransform or PoseImportOptions.ExpressionOnly)) { @@ -473,33 +475,26 @@ private async Task ImportPose(PoseImportOptions importOption, PoseFile.Mode mode mode &= ~PoseFile.Mode.Position; } - PoseService.Instance.FreezePositions = mode.HasFlag(PoseFile.Mode.Position); - // Apply facial expression hack for the expression import - await poseFile.Apply(this.Actor, this.Skeleton, selectedBones, mode, true); + poseFile.Apply(this.Actor, this.Skeleton, selectedBones, mode, true); // Re-enable positions if they were disabled. if (doLegacyImport) { mode |= PoseFile.Mode.Position; - PoseService.Instance.FreezePositions |= mode.HasFlag(PoseFile.Mode.Position); + foreach ((BoneVisual3d bone, Vector3 position) in facePositions) + { + bone.Position = position; + bone.WriteTransform(this.Skeleton, false); + } } } - // Expression is loaded. Exit early if we're only importing expressions. - if (importOption == PoseImportOptions.ExpressionOnly) - return; - - // Step 3: Restore body bone positions - if (importOption == PoseImportOptions.FullTransform && mode.HasFlag(PoseFile.Mode.Position)) + // Step 3: Restore face bone positions if face bones were mismatched + if (mismatchedFaceBones) { - foreach ((string name, Vector3 position) in bonePositions) + foreach ((BoneVisual3d bone, Vector3 position) in facePositions) { - BoneVisual3d? bone = this.Skeleton.GetBone(name); - - if (bone == null) - continue; - bone.Position = position; bone.WriteTransform(this.Skeleton, false); } @@ -511,6 +506,9 @@ private async Task ImportPose(PoseImportOptions importOption, PoseFile.Mode mode } finally { + // Update the skeleton after applying the pose + this.Skeleton.ReadTransforms(); + // Re-enable auto-commit and commit changes this.Actor.History.Commit(); this.Actor.History.AutoCommitEnabled = originalAutoCommitEnabled; diff --git a/Anamnesis/Actor/Refresh/BrioActorRefresher.cs b/Anamnesis/Actor/Refresh/BrioActorRefresher.cs index dab0cc24..1eb7410b 100644 --- a/Anamnesis/Actor/Refresh/BrioActorRefresher.cs +++ b/Anamnesis/Actor/Refresh/BrioActorRefresher.cs @@ -3,11 +3,11 @@ namespace Anamnesis.Actor.Refresh; -using System.Threading.Tasks; using Anamnesis.Brio; using Anamnesis.Files; using Anamnesis.Memory; using Anamnesis.Services; +using System.Threading.Tasks; using XivToolsWpf; public class BrioActorRefresher : IActorRefresher @@ -28,7 +28,7 @@ public async Task RefreshActor(ActorMemory actor) RedrawType redrawType = RedrawType.AllowFull | RedrawType.PreservePosition | RedrawType.AllowOptimized | RedrawType.ForceAllowNPCAppearance; - if(actor.IsWeaponDirty) + if (actor.IsWeaponDirty) { redrawType |= RedrawType.ForceRedrawWeaponsOnOptimized; } @@ -48,7 +48,7 @@ public async Task RefreshActor(ActorMemory actor) // Redraw var result = await Brio.Redraw(actor.ObjectIndex, redrawType); - if(result == "\"Full\"") + if (result == "\"Full\"") { // TODO: It's probably best to find some way to detect when it's safe // this is a good first attempt though. @@ -60,7 +60,7 @@ public async Task RefreshActor(ActorMemory actor) // Restore current pose skeletonVisual3D = new SkeletonVisual3d(); await skeletonVisual3D.SetActor(actor); - await poseFile.Apply(actor, skeletonVisual3D, null, PoseFile.Mode.All, true); + poseFile.Apply(actor, skeletonVisual3D, null, PoseFile.Mode.All, true); }).Start(); } } diff --git a/Anamnesis/Files/PoseFile.cs b/Anamnesis/Files/PoseFile.cs index 14457e6b..e188dcf2 100644 --- a/Anamnesis/Files/PoseFile.cs +++ b/Anamnesis/Files/PoseFile.cs @@ -46,20 +46,7 @@ public enum BoneProcessingModes public static BoneProcessingModes GetBoneMode(ActorMemory? actor, SkeletonVisual3d? skeleton, string boneName) { - if (boneName == "n_root") - return BoneProcessingModes.Ignore; - - // Special case for elezen ears as they cannot use other races ear values. - if (actor?.Customize?.Race == ActorCustomizeMemory.Races.Elezen) - { - // append '_elezen' to both ear bones. - if (boneName == "j_mimi_l" || boneName == "j_mimi_r") - { - return BoneProcessingModes.KeepRelative; - } - } - - return BoneProcessingModes.FullLoad; + return boneName != "n_root" ? BoneProcessingModes.FullLoad : BoneProcessingModes.Ignore; } public static async Task Save(DirectoryInfo? dir, ActorMemory? actor, SkeletonVisual3d? skeleton, HashSet? bones = null, bool editMeta = false) @@ -107,7 +94,7 @@ public void WriteToFile(ActorMemory actor, SkeletonVisual3d skeleton, HashSet? bones, Mode mode, bool doFacialExpressionHack) + public void Apply(ActorMemory actor, SkeletonVisual3d skeleton, HashSet? bones, Mode mode, bool doFacialExpressionHack) { if (actor == null) throw new ArgumentNullException(nameof(actor)); @@ -298,8 +285,6 @@ public async Task Apply(ActorMemory actor, SkeletonVisual3d skeleton, HashSet Date: Sun, 10 Nov 2024 14:12:12 +0100 Subject: [PATCH 2/3] Removed changed value check to allow bones lines to redraw --- .../Actor/Posing/Visuals/BoneVisual3d.cs | 44 ++++++++----------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/Anamnesis/Actor/Posing/Visuals/BoneVisual3d.cs b/Anamnesis/Actor/Posing/Visuals/BoneVisual3d.cs index 1ab23e73..dbed7dc3 100644 --- a/Anamnesis/Actor/Posing/Visuals/BoneVisual3d.cs +++ b/Anamnesis/Actor/Posing/Visuals/BoneVisual3d.cs @@ -290,33 +290,25 @@ public virtual void ReadTransform(bool readChildren = false, Dictionary Date: Tue, 12 Nov 2024 22:50:50 +0100 Subject: [PATCH 3/3] Fixed error in DT pose file detection; Updated pose mode toggle behavior --- Anamnesis/Actor/Pages/PosePage.xaml.cs | 2 +- Anamnesis/Files/PoseFile.cs | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Anamnesis/Actor/Pages/PosePage.xaml.cs b/Anamnesis/Actor/Pages/PosePage.xaml.cs index a3d87e12..239a5032 100644 --- a/Anamnesis/Actor/Pages/PosePage.xaml.cs +++ b/Anamnesis/Actor/Pages/PosePage.xaml.cs @@ -412,7 +412,7 @@ private async Task ImportPose(PoseImportOptions importOption, PoseFile.Mode mode PoseService.Instance.SetEnabled(true); PoseService.Instance.FreezeScale |= mode.HasFlag(PoseFile.Mode.Scale); PoseService.Instance.FreezeRotation |= mode.HasFlag(PoseFile.Mode.Rotation); - PoseService.Instance.FreezePositions = mode.HasFlag(PoseFile.Mode.Position); + PoseService.Instance.FreezePositions |= mode.HasFlag(PoseFile.Mode.Position); if (importOption == PoseImportOptions.SelectedBones) { diff --git a/Anamnesis/Files/PoseFile.cs b/Anamnesis/Files/PoseFile.cs index e188dcf2..78662bd5 100644 --- a/Anamnesis/Files/PoseFile.cs +++ b/Anamnesis/Files/PoseFile.cs @@ -331,13 +331,10 @@ public bool IsPreDTPoseFile() } } - if (!hasFaceBones) - return false; - // Looking for the tongue-A bone, a new bone common to all races and genders added in DT. // If we dont have it, we are assumed to be a pre-DT pose file. // This doesn't account for users manually editing the JSON. - if (!this.Bones.ContainsKey("j_f_bero_01")) + if (!hasFaceBones || !this.Bones.ContainsKey("j_f_bero_01")) return true; return false;