/** * @file * * @author Mamadou Babaei * * @section LICENSE * * (The MIT License) * * Copyright (c) 2020 - 2023 SenseGlove * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * @section DESCRIPTION * * */ #include "SGEditor/SGAssetUtils.h" #include "Animation/DebugSkelMeshComponent.h" #include "Animation/Skeleton.h" #include "Containers/Array.h" #include "Containers/UnrealString.h" #include "Engine/SkeletalMesh.h" #include "Engine/SkeletalMeshSocket.h" #include "HeadMountedDisplayTypes.h" #include "IEditableSkeleton.h" #include "Internationalization/Text.h" #include "IPersonaToolkit.h" #include "ISkeletalMeshEditorModule.h" #include "ISkeletonEditorModule.h" #include "Misc/AssertionMacros.h" #include "Misc/CoreMiscDefines.h" #include "ReferenceSkeleton.h" #include "Rendering/SkeletalMeshLODRenderData.h" #include "Rendering/SkeletalMeshRenderData.h" #include "Subsystems/AssetEditorSubsystem.h" #include "UObject/Object.h" #include "UObject/UnrealNames.h" #include "SGLog/SGLog.h" #include "SGSettings/SGSettings.h" struct FSGAssetUtils::FImpl { /************************ * Static methods ************************/ FORCEINLINE static const FSGVirtualHandSettings& GetVirtualHandSettings() { const USGSettings* Settings{USGSettings::GetInstance()}; ensureAlwaysMsgf(Settings, TEXT("The settings subsystem has not been initialized!")); const FSGVirtualHandSettings& VirtualHandSettings{Settings->GetVirtualHandSettings()}; return VirtualHandSettings; } FORCEINLINE static const FSGVirtualHandGrabSettings& GetVirtualHandGrabSettings() { const FSGVirtualHandSettings& VirtualHandSettings{GetVirtualHandSettings()}; return VirtualHandSettings.GrabSettings; } FORCEINLINE static const FSGVirtualHandMeshSettings& GetVirtualHandMeshSettings() { const FSGVirtualHandSettings& VirtualHandSettings{GetVirtualHandSettings()}; return VirtualHandSettings.MeshSettings; } FORCEINLINE static const FSGVirtualHandTouchSettings& GetVirtualHandTouchSettings() { const FSGVirtualHandSettings& VirtualHandSettings{GetVirtualHandSettings()}; return VirtualHandSettings.TouchSettings; } static bool IsHandBoneWeighted( const ISkeletalMeshEditor* SkeletalMeshEditor, const USkeletalMesh* SkeletalMesh, const FName& BoneName); static FName GetHandBoneName(const ISkeletalMeshEditor* SkeletalMeshEditor, const USkeletalMesh* SkeletalMesh); static bool IsLeftHandMesh(const ISkeletalMeshEditor* SkeletalMeshEditor, const USkeletalMesh* SkeletalMesh); static bool IsRightHandMesh(const ISkeletalMeshEditor* SkeletalMeshEditor, const USkeletalMesh* SkeletalMesh); static FTransform GetHandBoneRefTransform(const USkeletalMesh* SkeletalMesh, const FName& BoneName); static bool AddSocket( const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh, const FName& BoneName, const FName& SocketName, const FTransform& SocketTransform); static bool AddGrabAttachPointSocket( const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh, bool bRight); static bool AddFingertipColliderSocket( const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh, bool bRight, EHandKeypoint DistalKeypoint, const FName& SocketName); static bool AddGrabThumbColliderSocket( const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh, bool bRight); static bool AddGrabIndexColliderSocket( const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh, bool bRight); static bool AddGrabMiddleColliderSocket( const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh, bool bRight); static bool AddTouchThumbColliderSocket( const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh, bool bRight); static bool AddTouchIndexColliderSocket( const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh, bool bRight); static bool AddTouchMiddleColliderSocket( const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh, bool bRight); static bool AddTouchRingColliderSocket( const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh, bool bRight); static bool AddTouchLittleColliderSocket( const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh, bool bRight); static bool AddSenseGloveSockets(const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh); static bool AddSenseGloveSockets(USkeletalMesh* SkeletalMesh, USkeleton* Skeleton); static bool ClearExistingSockets(USkeletalMesh* SkeletalMesh, USkeleton* Skeleton); }; bool FSGAssetUtils::FImpl::IsHandBoneWeighted( const ISkeletalMeshEditor* SkeletalMeshEditor, const USkeletalMesh* SkeletalMesh, const FName& BoneName) { if (!ensureAlwaysMsgf(IsValid(SkeletalMesh), TEXT("Invalid SkeletalMesh!"))) { return false; } if (!ensureAlwaysMsgf(BoneName != NAME_None, TEXT("Invalid BoneName!"))) { return false; } const FReferenceSkeleton& RefSkeleton{SkeletalMesh->GetRefSkeleton()}; const int32 BoneIndex = RefSkeleton.FindBoneIndex(BoneName); // MeshBoneIndex must be an index into the mesh's skeleton, *not* the source skeleton!!! if (!ensureAlwaysMsgf(BoneIndex != INDEX_NONE, TEXT("Invalid BoneIndex!"))) { // If we get an invalid index, we are done here. return false; } const FSkeletalMeshRenderData* ResourceForRendering{SkeletalMesh->GetResourceForRendering()}; if (!ensureAlwaysMsgf(ResourceForRendering, TEXT("Invalid ResourceForRendering!"))) { // If there's no mesh, then this bone can't possibly be weighted! return false; } const TIndirectArray& LODRenderData{ResourceForRendering->LODRenderData}; const int32 LODRenderDataNum{LODRenderData.Num()}; if (!ensureAlwaysMsgf(LODRenderDataNum > 0, TEXT("Invalid LODRenderDataNum!"))) { // If there's no mesh, then this bone can't possibly be weighted! return false; } const TSharedRef PersonaToolkit{SkeletalMeshEditor->GetPersonaToolkit()}; const UDebugSkelMeshComponent* PreviewMeshComponent{PersonaToolkit->GetPreviewMeshComponent()}; if (!ensureAlwaysMsgf(PreviewMeshComponent, TEXT("Invalid PreviewMeshComponent!"))) { return false; } const int32 PredictedLODLevel = PreviewMeshComponent->GetPredictedLODLevel(); const USkeletalMesh* PreviewSkeletalMeshAsset{PreviewMeshComponent->GetSkeletalMeshAsset()}; if (!ensureAlwaysMsgf(IsValid(PreviewSkeletalMeshAsset), TEXT("Invalid PreviewSkeletalMeshAsset!"))) { return false; } const FSkeletalMeshRenderData* PreviewResourceForRendering{PreviewSkeletalMeshAsset->GetResourceForRendering()}; if (!ensureAlwaysMsgf(PreviewResourceForRendering, TEXT("Invalid PreviewResourceForRendering!"))) { return false; } const TIndirectArray& PreviewLODRenderData{PreviewResourceForRendering->LODRenderData}; const int32 PreviewLODRenderDataNum{PreviewLODRenderData.Num()}; if (!ensureAlwaysMsgf(LODRenderDataNum > 0, TEXT("Invalid LODRenderDataNum!"))) { return false; } const int32 MaxPreviewLODRenderDataNum = PreviewLODRenderDataNum - 1; // Get current LOD const int32 LODIndex = FMath::Clamp(PredictedLODLevel, 0, MaxPreviewLODRenderDataNum); const FSkeletalMeshLODRenderData& LODData{PreviewLODRenderData[LODIndex]}; // Check whether the bone is vertex weighted const int32 Index = LODData.ActiveBoneIndices.Find(IntCastChecked(BoneIndex)); return Index != INDEX_NONE; } FName FSGAssetUtils::FImpl::GetHandBoneName( const ISkeletalMeshEditor* SkeletalMeshEditor, const USkeletalMesh* SkeletalMesh) { if (!ensureAlwaysMsgf(IsValid(SkeletalMesh), TEXT("Invalid SkeletalMesh!"))) { return NAME_None; } static const FString HandBonePrefix{TEXT("hand_")}; const FReferenceSkeleton& RefSkeleton{SkeletalMesh->GetRefSkeleton()}; const int32 BoneNum = RefSkeleton.GetNum(); for (int i = 0; i < BoneNum; ++i) { const FName BoneName{RefSkeleton.GetBoneName(i)}; const FString BoneNameString{BoneName.ToString()}; if (!BoneNameString.StartsWith(HandBonePrefix)) { continue; } if (!IsHandBoneWeighted(SkeletalMeshEditor, SkeletalMesh, BoneName)) { continue; } return BoneName; } SGLOG_ERROR("Invalid SenseGlove virtual hand mesh!"); return NAME_None; } bool FSGAssetUtils::FImpl::IsLeftHandMesh( const ISkeletalMeshEditor* SkeletalMeshEditor, const USkeletalMesh* SkeletalMesh) { static constexpr int32 HandBoneNameIndex = static_cast(EHandKeypoint::Wrist); const FSGVirtualHandMeshSettings& MeshSettings{GetVirtualHandMeshSettings()}; if (!ensureAlwaysMsgf(HandBoneNameIndex < MeshSettings.RightHandBoneNames.Num() && HandBoneNameIndex < MeshSettings.RightHandBoneNames.Num(), TEXT("Invalid HandBoneNameIndex"))) { return false; } const FName LeftHandBoneName{MeshSettings.LeftHandBoneNames[HandBoneNameIndex]}; const FName HandBoneName{GetHandBoneName(SkeletalMeshEditor, SkeletalMesh)}; if (HandBoneName == NAME_None) { return false; } if (HandBoneName != LeftHandBoneName) { return false; } return true; } bool FSGAssetUtils::FImpl::IsRightHandMesh( const ISkeletalMeshEditor* SkeletalMeshEditor, const USkeletalMesh* SkeletalMesh) { static constexpr int32 HandBoneNameIndex = static_cast(EHandKeypoint::Wrist); const FSGVirtualHandMeshSettings& MeshSettings{GetVirtualHandSettings().MeshSettings}; if (!ensureAlwaysMsgf(HandBoneNameIndex < MeshSettings.RightHandBoneNames.Num() && HandBoneNameIndex < MeshSettings.RightHandBoneNames.Num(), TEXT("Invalid HandBoneNameIndex"))) { return false; } const FName RightHandBoneName{MeshSettings.RightHandBoneNames[HandBoneNameIndex]}; const FName HandBoneName{GetHandBoneName(SkeletalMeshEditor, SkeletalMesh)}; if (HandBoneName == NAME_None) { return false; } if (HandBoneName != RightHandBoneName) { return false; } return true; } FTransform FSGAssetUtils::FImpl::GetHandBoneRefTransform( const USkeletalMesh* SkeletalMesh, const FName& BoneName) { if (!ensureAlwaysMsgf(IsValid(SkeletalMesh), TEXT("Invalid SkeletalMesh"))) { return FTransform::Identity; } const FReferenceSkeleton& ReferenceSkeleton{SkeletalMesh->GetRefSkeleton()}; const int32 BoneIndex = ReferenceSkeleton.FindBoneIndex(BoneName); if (BoneIndex == INDEX_NONE) { SGLOG_WARNING(FString::Printf(TEXT("Could not find a bone index for the '%s' joint!"), *BoneName.ToString())); } const TArray Transforms{ReferenceSkeleton.GetRefBonePose()}; if (!ensureAlwaysMsgf(Transforms.Num() > 0 && Transforms.Num() > BoneIndex, TEXT("Invalid bone index '%d' or no transforms found!"), BoneIndex)) { return FTransform::Identity; } const FTransform& Transform{Transforms[BoneIndex]}; return Transform; } bool FSGAssetUtils::FImpl::AddSocket( const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh, const FName& BoneName, const FName& SocketName, const FTransform& SocketTransform) { if (!ensureAlwaysMsgf(SkeletalMeshEditor, TEXT("Invalid SkeletalMeshEditor!"))) { return false; } if (!ensureAlwaysMsgf(IsValid(SkeletalMesh), TEXT("Invalid SkeletalMesh!"))) { return false; } TSharedRef PersonaToolkit{SkeletalMeshEditor->GetPersonaToolkit()}; TSharedPtr EditableSkeleton{PersonaToolkit->GetEditableSkeleton()}; if (!ensureAlwaysMsgf(EditableSkeleton.IsValid(), TEXT("Invalid SkeletalMesh!"))) { return false; } USkeletalMeshSocket* Socket{NewObject()}; Socket->BoneName = BoneName; Socket->SocketName = SocketName; if (!ensureAlwaysMsgf(IsValid(Socket), TEXT("Invalid Socket!"))) { return false; } const bool bDoesSocketAlreadyExistOnTheMesh = EditableSkeleton->DoesSocketAlreadyExist( Socket, FText::FromName(SocketName), ESocketParentType::Mesh, SkeletalMesh); const bool bDoesSocketAlreadyExistOnTheSkeleton = EditableSkeleton->DoesSocketAlreadyExist( Socket, FText::FromName(SocketName), ESocketParentType::Skeleton, SkeletalMesh); if (bDoesSocketAlreadyExistOnTheMesh || bDoesSocketAlreadyExistOnTheSkeleton) { SGLOG_ERROR(FString::Printf(TEXT("Socket '%s' already exists on '%s'; refuse to add a duplicate!"), *SocketName.ToString(), *SkeletalMesh->GetPathName())); return false; } Socket = EditableSkeleton->AddSocket(BoneName); if (!ensureAlwaysMsgf(IsValid(Socket), TEXT("Invalid Socket!"))) { return false; } EditableSkeleton->RenameSocket(Socket->SocketName, SocketName, SkeletalMesh); Socket->RelativeLocation = SocketTransform.GetLocation(); Socket->RelativeRotation = SocketTransform.GetRotation().Rotator(); Socket->RelativeScale = SocketTransform.GetScale3D(); return true; } bool FSGAssetUtils::FImpl::AddGrabAttachPointSocket( const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh, const bool bRight) { static constexpr int32 BoneNameIndex = static_cast(EHandKeypoint::Wrist); const FSGVirtualHandMeshSettings MeshSettings{GetVirtualHandMeshSettings()}; if (!ensureAlwaysMsgf(BoneNameIndex < MeshSettings.RightHandBoneNames.Num() && BoneNameIndex < MeshSettings.RightHandBoneNames.Num(), TEXT("Invalid BoneNameIndex"))) { return false; } const FName& BoneName{ bRight ? MeshSettings.RightHandBoneNames[BoneNameIndex] : MeshSettings.LeftHandBoneNames[BoneNameIndex] }; const FSGVirtualHandGrabSettings GrabSettings{GetVirtualHandGrabSettings()}; const FName& SocketName{GrabSettings.GrabAttachPointSocketName}; const FTransform& SocketTransform{GrabSettings.GrabAttachPointSocketTransform}; const bool bAddedSocket = AddSocket( SkeletalMeshEditor, SkeletalMesh, BoneName, SocketName, SocketTransform); if (!bAddedSocket) { SGLOG_ERROR(FString::Printf(TEXT("Failed to add the socket '%s' to '%s'!"), *SocketName.ToString(), *SkeletalMesh->GetPathName())); return false; } return true; } bool FSGAssetUtils::FImpl::AddFingertipColliderSocket( const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh, const bool bRight, const EHandKeypoint DistalKeypoint, const FName& SocketName) { const int32 BoneNameIndex = static_cast(DistalKeypoint); const FSGVirtualHandMeshSettings MeshSettings{GetVirtualHandMeshSettings()}; if (!ensureAlwaysMsgf(BoneNameIndex < MeshSettings.RightHandBoneNames.Num() && BoneNameIndex < MeshSettings.RightHandBoneNames.Num(), TEXT("Invalid BoneNameIndex"))) { return false; } const FName& BoneName{ bRight ? MeshSettings.RightHandBoneNames[BoneNameIndex] : MeshSettings.LeftHandBoneNames[BoneNameIndex] }; FTransform SocketTransform{ GetHandBoneRefTransform(SkeletalMesh, BoneName) }; const bool bAddedSocket = AddSocket( SkeletalMeshEditor, SkeletalMesh, BoneName, SocketName, MoveTemp(SocketTransform)); if (!bAddedSocket) { SGLOG_ERROR(FString::Printf(TEXT("Failed to add the socket '%s' to '%s'!"), *BoneName.ToString(), *SkeletalMesh->GetPathName())); return false; } return true; } bool FSGAssetUtils::FImpl::AddGrabThumbColliderSocket( const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh, const bool bRight) { const FSGVirtualHandGrabSettings GrabSettings{GetVirtualHandGrabSettings()}; const FName& SocketName{GrabSettings.ThumbColliderSocketName}; return AddFingertipColliderSocket(SkeletalMeshEditor, SkeletalMesh, bRight, EHandKeypoint::ThumbDistal, SocketName); } bool FSGAssetUtils::FImpl::AddGrabIndexColliderSocket( const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh, const bool bRight) { const FSGVirtualHandGrabSettings GrabSettings{GetVirtualHandGrabSettings()}; const FName& SocketName{GrabSettings.IndexColliderSocketName}; return AddFingertipColliderSocket(SkeletalMeshEditor, SkeletalMesh, bRight, EHandKeypoint::IndexDistal, SocketName); } bool FSGAssetUtils::FImpl::AddGrabMiddleColliderSocket( const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh, const bool bRight) { const FSGVirtualHandGrabSettings GrabSettings{GetVirtualHandGrabSettings()}; const FName& SocketName{GrabSettings.MiddleColliderSocketName}; return AddFingertipColliderSocket(SkeletalMeshEditor, SkeletalMesh, bRight, EHandKeypoint::MiddleDistal, SocketName); } bool FSGAssetUtils::FImpl::AddTouchThumbColliderSocket( const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh, const bool bRight) { const FSGVirtualHandTouchSettings TouchSettings{GetVirtualHandTouchSettings()}; const FName& SocketName{TouchSettings.ThumbColliderSocketName}; return AddFingertipColliderSocket(SkeletalMeshEditor, SkeletalMesh, bRight, EHandKeypoint::ThumbDistal, SocketName); } bool FSGAssetUtils::FImpl::AddTouchIndexColliderSocket( const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh, const bool bRight) { const FSGVirtualHandTouchSettings TouchSettings{GetVirtualHandTouchSettings()}; const FName& SocketName{TouchSettings.IndexColliderSocketName}; return AddFingertipColliderSocket(SkeletalMeshEditor, SkeletalMesh, bRight, EHandKeypoint::IndexDistal, SocketName); } bool FSGAssetUtils::FImpl::AddTouchMiddleColliderSocket( const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh, const bool bRight) { const FSGVirtualHandTouchSettings TouchSettings{GetVirtualHandTouchSettings()}; const FName& SocketName{TouchSettings.MiddleColliderSocketName}; return AddFingertipColliderSocket(SkeletalMeshEditor, SkeletalMesh, bRight, EHandKeypoint::MiddleDistal, SocketName); } bool FSGAssetUtils::FImpl::AddTouchRingColliderSocket( const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh, const bool bRight) { const FSGVirtualHandTouchSettings TouchSettings{GetVirtualHandTouchSettings()}; const FName& SocketName{TouchSettings.RingColliderSocketName}; return AddFingertipColliderSocket(SkeletalMeshEditor, SkeletalMesh, bRight, EHandKeypoint::RingDistal, SocketName); } bool FSGAssetUtils::FImpl::AddTouchLittleColliderSocket( const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh, const bool bRight) { const FSGVirtualHandTouchSettings TouchSettings{GetVirtualHandTouchSettings()}; const FName& SocketName{TouchSettings.PinkyColliderSocketName}; return AddFingertipColliderSocket(SkeletalMeshEditor, SkeletalMesh, bRight, EHandKeypoint::LittleDistal, SocketName); } bool FSGAssetUtils::FImpl::AddSenseGloveSockets(const ISkeletalMeshEditor* SkeletalMeshEditor, USkeletalMesh* SkeletalMesh) { if (!ensureAlwaysMsgf(SkeletalMeshEditor, TEXT("Invalid SkeletalMeshEditor!"))) { return false; } if (!ensureAlwaysMsgf(IsValid(SkeletalMesh), TEXT("Invalid SkeletalMesh!"))) { return false; } const bool bIsLeft = IsLeftHandMesh(SkeletalMeshEditor, SkeletalMesh); const bool bIsRight = IsRightHandMesh(SkeletalMeshEditor, SkeletalMesh); const bool bIsHandMesh = bIsLeft || bIsRight; if (!bIsHandMesh) { SGLOG_ERROR(FString::Printf(TEXT("Asset '%s' is not a valid virtual hand mesh!"), *SkeletalMesh->GetPathName())); return false; } const bool bAddedGrabAttachPointSocket = AddGrabAttachPointSocket( SkeletalMeshEditor, SkeletalMesh, bIsRight); if (!bAddedGrabAttachPointSocket) { SGLOG_ERROR(FString::Printf(TEXT("Failed to add the grab attach point socket to asset '%s'!"), *SkeletalMesh->GetPathName())); return false; } const bool GrabThumbCollider = AddGrabThumbColliderSocket( SkeletalMeshEditor, SkeletalMesh, bIsRight); if (!GrabThumbCollider) { SGLOG_ERROR(FString::Printf(TEXT("Failed to add the grab thumb collider socket to asset '%s'!"), *SkeletalMesh->GetPathName())); return false; } const bool GrabIndexCollider = AddGrabIndexColliderSocket( SkeletalMeshEditor, SkeletalMesh, bIsRight); if (!GrabIndexCollider) { SGLOG_ERROR(FString::Printf(TEXT("Failed to add the grab index collider socket to asset '%s'!"), *SkeletalMesh->GetPathName())); return false; } const bool GrabMiddleCollider = AddGrabMiddleColliderSocket( SkeletalMeshEditor, SkeletalMesh, bIsRight); if (!GrabMiddleCollider) { SGLOG_ERROR(FString::Printf(TEXT("Failed to add the grab middle collider socket to asset '%s'!"), *SkeletalMesh->GetPathName())); return false; } const bool TouchThumbCollider = AddTouchThumbColliderSocket( SkeletalMeshEditor, SkeletalMesh, bIsRight); if (!TouchThumbCollider) { SGLOG_ERROR(FString::Printf(TEXT("Failed to add the touch thumb collider socket to asset '%s'!"), *SkeletalMesh->GetPathName())); return false; } const bool TouchIndexCollider = AddTouchIndexColliderSocket( SkeletalMeshEditor, SkeletalMesh, bIsRight); if (!TouchIndexCollider) { SGLOG_ERROR(FString::Printf(TEXT("Failed to add the touch index collider socket to asset '%s'!"), *SkeletalMesh->GetPathName())); return false; } const bool TouchMiddleCollider = AddTouchMiddleColliderSocket( SkeletalMeshEditor, SkeletalMesh, bIsRight); if (!TouchMiddleCollider) { SGLOG_ERROR(FString::Printf(TEXT("Failed to add the touch middle collider socket to asset '%s'!"), *SkeletalMesh->GetPathName())); return false; } const bool TouchRingCollider = AddTouchRingColliderSocket( SkeletalMeshEditor, SkeletalMesh, bIsRight); if (!TouchRingCollider) { SGLOG_ERROR(FString::Printf(TEXT("Failed to add the touch ring collider socket to asset '%s'!"), *SkeletalMesh->GetPathName())); return false; } const bool TouchLittleCollider = AddTouchLittleColliderSocket( SkeletalMeshEditor, SkeletalMesh, bIsRight); if (!TouchLittleCollider) { SGLOG_ERROR(FString::Printf(TEXT("Failed to add the touch little collider socket to asset '%s'!"), *SkeletalMesh->GetPathName())); return false; } return true; } bool FSGAssetUtils::FImpl::AddSenseGloveSockets(USkeletalMesh* SkeletalMesh, USkeleton* Skeleton) { if (!ensureAlwaysMsgf(IsValid(SkeletalMesh), TEXT("Invalid SkeletalMesh!"))) { return false; } if (!ensureAlwaysMsgf(IsValid(Skeleton), TEXT("Invalid Skeleton!"))) { return false; } const bool bAssetEdotirOpenForSkeletalMesh = IsAssetEditorOpenForAsset(SkeletalMesh); if (bAssetEdotirOpenForSkeletalMesh) { const int32 ClosedEditorsNum = CloseAllEditorsForAsset(SkeletalMesh); (void) ClosedEditorsNum; } const bool bAssetEdotirOpenForSkeleton = IsAssetEditorOpenForAsset(Skeleton); if (bAssetEdotirOpenForSkeleton) { const int32 ClosedEditorsNum = CloseAllEditorsForAsset(Skeleton); (void) ClosedEditorsNum; } const bool bCanModifySkeletalMesh = SkeletalMesh->CanModify(); if (!bCanModifySkeletalMesh) { SGLOG_ERROR(FString::Printf(TEXT("Could not modify the asset '%s'!"), *SkeletalMesh->GetPathName())); return false; } const bool bCanModifySkeleton = Skeleton->CanModify(); if (!bCanModifySkeleton) { SGLOG_ERROR(FString::Printf(TEXT("Could not modify the asset '%s'!"), *Skeleton->GetPathName())); return false; } bool bOpenedSkeletalMeshEditor = OpenEditorForAsset(SkeletalMesh); if (!bOpenedSkeletalMeshEditor) { SGLOG_ERROR(FString::Printf(TEXT("Could not open the editor for the asset '%s'!"), *SkeletalMesh->GetPathName())); return false; } TArray AssetEditors{FindEditorsForAsset(SkeletalMesh)}; if (AssetEditors.IsEmpty()) { SGLOG_ERROR(FString::Printf(TEXT("Could not find any editor for the asset '%s'!"), *SkeletalMesh->GetPathName())); return false; } for (IAssetEditorInstance* AssetEditor: AssetEditors) { if (!ensureAlwaysMsgf(AssetEditor, TEXT("Invalid AssetEditor!"))) { return false; } const ISkeletalMeshEditor* SkeletalMeshEditor{static_cast(AssetEditor)}; if (!SkeletalMeshEditor) { continue; } const bool bAddedSenseGloveSockets = AddSenseGloveSockets(SkeletalMeshEditor, SkeletalMesh); if (!bAddedSenseGloveSockets) { SGLOG_ERROR(FString::Printf(TEXT("Failed to add the SenseGlove sockets to the asset '%s'!"), *SkeletalMesh->GetPathName())); const int32 ClosedEditorsNum = CloseAllEditorsForAsset(SkeletalMesh); (void) ClosedEditorsNum; return false; } const int32 ClosedEditorsNum = CloseAllEditorsForAsset(SkeletalMesh); (void) ClosedEditorsNum; break; } SkeletalMesh->PostEditChange(); Skeleton->PostEditChange(); const bool bMarkedSkeletalMeshPackageDirty = SkeletalMesh->MarkPackageDirty(); if (!bMarkedSkeletalMeshPackageDirty) { SGLOG_ERROR(FString::Printf(TEXT("Could not mark the asset '%s' as dirty!"), *SkeletalMesh->GetPathName())); return false; } const bool bMarkedSkeletonPackageDirty = Skeleton->MarkPackageDirty(); if (!bMarkedSkeletonPackageDirty) { SGLOG_ERROR(FString::Printf(TEXT("Could not mark the asset '%s' as dirty!"), *Skeleton->GetPathName())); return false; } bOpenedSkeletalMeshEditor = OpenEditorForAsset(SkeletalMesh); if (!bOpenedSkeletalMeshEditor) { SGLOG_ERROR(FString::Printf(TEXT("Could not refresh the editor for the asset '%s'!"), *SkeletalMesh->GetPathName())); return false; } const bool bOpenedSkeletonEditor = OpenEditorForAsset(Skeleton); if (!bOpenedSkeletonEditor) { SGLOG_ERROR(FString::Printf(TEXT("Could not refresh the editor for the asset '%s'!"), *Skeleton->GetPathName())); return false; } return true; } bool FSGAssetUtils::FImpl::ClearExistingSockets(USkeletalMesh* SkeletalMesh, USkeleton* Skeleton) { if (!ensureAlwaysMsgf(IsValid(SkeletalMesh), TEXT("Invalid SkeletalMesh!"))) { return false; } if (!ensureAlwaysMsgf(IsValid(Skeleton), TEXT("Invalid Skeleton!"))) { return false; } const bool bAssetEdotirOpenForSkeletalMesh = IsAssetEditorOpenForAsset(SkeletalMesh); if (bAssetEdotirOpenForSkeletalMesh) { const int32 ClosedEditorsNum = CloseAllEditorsForAsset(SkeletalMesh); (void) ClosedEditorsNum; } const bool bAssetEdotirOpenForSkeleton = IsAssetEditorOpenForAsset(Skeleton); if (bAssetEdotirOpenForSkeleton) { const int32 ClosedEditorsNum = CloseAllEditorsForAsset(Skeleton); (void) ClosedEditorsNum; } const bool bCanModifySkeletalMesh = SkeletalMesh->CanModify(); if (!bCanModifySkeletalMesh) { SGLOG_ERROR(FString::Printf(TEXT("Could not modify the asset '%s'!"), *SkeletalMesh->GetPathName())); return false; } const bool bCanModifySkeleton = Skeleton->CanModify(); if (!bCanModifySkeleton) { SGLOG_ERROR(FString::Printf(TEXT("Could not modify the asset '%s'!"), *Skeleton->GetPathName())); return false; } Skeleton->Sockets.Empty(); SkeletalMesh->PostEditChange(); Skeleton->PostEditChange(); const bool bMarkedSkeletalMeshPackageDirty = SkeletalMesh->MarkPackageDirty(); if (!bMarkedSkeletalMeshPackageDirty) { SGLOG_ERROR(FString::Printf(TEXT("Could not mark the asset '%s' as dirty!"), *SkeletalMesh->GetPathName())); return false; } const bool bMarkedSkeletonPackageDirty = Skeleton->MarkPackageDirty(); if (!bMarkedSkeletonPackageDirty) { SGLOG_ERROR(FString::Printf(TEXT("Could not mark the asset '%s' as dirty!"), *Skeleton->GetPathName())); return false; } const bool bOpenedSkeletalMeshEditor = OpenEditorForAsset(SkeletalMesh); if (!bOpenedSkeletalMeshEditor) { SGLOG_ERROR(FString::Printf(TEXT("Could not refresh the editor for the asset '%s'!"), *SkeletalMesh->GetPathName())); return false; } const bool bOpenedSkeletonEditor = OpenEditorForAsset(Skeleton); if (!bOpenedSkeletonEditor) { SGLOG_ERROR(FString::Printf(TEXT("Could not refresh the editor for the asset '%s'!"), *Skeleton->GetPathName())); return false; } return true; } UAssetEditorSubsystem* FSGAssetUtils::GetAssetEditorSubsystem() { if (!ensureAlwaysMsgf(GEditor, TEXT("Invalid GEditor!"))) { return nullptr; } UAssetEditorSubsystem* AssetEditorSubsystem{GEditor->GetEditorSubsystem()}; return AssetEditorSubsystem; } UObject* FSGAssetUtils::GetEditedAsset() { UAssetEditorSubsystem* AssetEditorSubsystem{GetAssetEditorSubsystem()}; if (!ensureAlwaysMsgf(AssetEditorSubsystem, TEXT("Invalid AssetEditorSubsystem!"))) { return nullptr; } TArray EditedAssets{AssetEditorSubsystem->GetAllEditedAssets()}; for (UObject* EditedAsset: EditedAssets) { TArray AssetEditors{FindEditorsForAsset(EditedAsset)}; for (IAssetEditorInstance* AssetEditor: AssetEditors) { if (!ensureAlwaysMsgf(AssetEditor, TEXT("Invalid AssetEditor!"))) { return nullptr; } TSharedPtr TabManager = AssetEditor->GetAssociatedTabManager(); if (!ensureAlwaysMsgf(TabManager.IsValid(), TEXT("Invalid TabManager!"))) { return nullptr; } TSharedPtr OwnerTab{TabManager->GetOwnerTab()}; if (!ensureAlwaysMsgf(OwnerTab.IsValid(), TEXT("Invalid OwnerTab!"))) { return nullptr; } const bool bForeground = OwnerTab->IsForeground(); if (bForeground) { return EditedAsset; } } } ensureAlwaysMsgf(false, TEXT("The code flow should never reach here!")); return nullptr; } bool FSGAssetUtils::IsAssetEditorOpenForAsset(UObject* Asset) { if (!ensureAlwaysMsgf(Asset, TEXT("Invalid Asset!"))) { return false; } TArray AssetEditors{FindEditorsForAsset(Asset)}; return !AssetEditors.IsEmpty(); } TArray FSGAssetUtils::FindEditorsForAsset(UObject* Asset) { if (!ensureAlwaysMsgf(Asset, TEXT("Invalid Asset!"))) { return TArray{}; } UAssetEditorSubsystem* AssetEditorSubsystem{GetAssetEditorSubsystem()}; if (!ensureAlwaysMsgf(AssetEditorSubsystem, TEXT("Invalid AssetEditorSubsystem!"))) { return TArray{}; } TArray AssetEditors{AssetEditorSubsystem->FindEditorsForAsset(Asset)}; return AssetEditors; } bool FSGAssetUtils::OpenEditorForAsset(UObject* Asset) { if (!ensureAlwaysMsgf(Asset, TEXT("Invalid Asset!"))) { return false; } UAssetEditorSubsystem* AssetEditorSubsystem{GetAssetEditorSubsystem()}; if (!ensureAlwaysMsgf(AssetEditorSubsystem, TEXT("Invalid AssetEditorSubsystem!"))) { return false; } const bool bOpenedEditor = AssetEditorSubsystem->OpenEditorForAsset(Asset); return bOpenedEditor; } int32 FSGAssetUtils::CloseAllEditorsForAsset(UObject* Asset) { if (!ensureAlwaysMsgf(Asset, TEXT("Invalid Asset!"))) { return 0; } UAssetEditorSubsystem* AssetEditorSubsystem{GetAssetEditorSubsystem()}; if (!ensureAlwaysMsgf(AssetEditorSubsystem, TEXT("Invalid AssetEditorSubsystem!"))) { return false; } const int32 ClosedEditorsNum = AssetEditorSubsystem->CloseAllEditorsForAsset(Asset); return ClosedEditorsNum; } bool FSGAssetUtils::AddSenseGloveSockets(USkeletalMesh* SkeletalMesh) { if (!ensureAlwaysMsgf(IsValid(SkeletalMesh), TEXT("Invalid SkeletalMesh!"))) { return false; } USkeleton* Skeleton{SkeletalMesh->GetSkeleton()}; if (!ensureAlwaysMsgf(IsValid(Skeleton), TEXT("Invalid Skeleton!"))) { return false; } const bool bAddedSenseGloveSockets = FImpl::AddSenseGloveSockets(SkeletalMesh, Skeleton); return bAddedSenseGloveSockets; } bool FSGAssetUtils::AddSenseGloveSockets(USkeleton* Skeleton) { if (!ensureAlwaysMsgf(IsValid(Skeleton), TEXT("Invalid Skeleton!"))) { return false; } USkeletalMesh* SkeletalMesh{Skeleton->GetPreviewMesh()}; if (!ensureAlwaysMsgf(IsValid(SkeletalMesh), TEXT("Invalid SkeletalMesh!"))) { return false; } const bool bAddedSenseGloveSockets = FImpl::AddSenseGloveSockets(SkeletalMesh, Skeleton); return bAddedSenseGloveSockets; } bool FSGAssetUtils::ClearExistingSockets(USkeletalMesh* SkeletalMesh) { if (!ensureAlwaysMsgf(IsValid(SkeletalMesh), TEXT("Invalid SkeletalMesh!"))) { return false; } USkeleton* Skeleton{SkeletalMesh->GetSkeleton()}; if (!ensureAlwaysMsgf(IsValid(Skeleton), TEXT("Invalid Skeleton!"))) { return false; } const bool bClearedExistingSockets = FImpl::ClearExistingSockets(SkeletalMesh, Skeleton); return bClearedExistingSockets; } bool FSGAssetUtils::ClearExistingSockets(USkeleton* Skeleton) { if (!ensureAlwaysMsgf(IsValid(Skeleton), TEXT("Invalid Skeleton!"))) { return false; } USkeletalMesh* SkeletalMesh{Skeleton->GetPreviewMesh()}; if (!ensureAlwaysMsgf(IsValid(SkeletalMesh), TEXT("Invalid SkeletalMesh!"))) { return false; } const bool bClearedExistingSockets = FImpl::ClearExistingSockets(SkeletalMesh, Skeleton); return bClearedExistingSockets; }