Description

This is a Regression. Tested in:
//UE5/Release-5.0 CL 18747223 GitHub
//UE4/Release-4.27 CL 18319896 Binary

The example can't be completed in UE5 as expected presumably because of a change within SkeletalMeshMerge.h.

What happens if you rename the Struct?
Renaming the struct to SkelMeshMergeUVTransformMapping2 for example will allow the code to compile. However:

  • If a blueprint is created like in the example the Dynamic Shadows will be broken on the merged skeletal meshes.
  • If the user tries to add an element to the UVTransforms Per Mesh array the Editor crashes
  • Gifs included
Steps to Reproduce
  1. Create a New Blank C++ Project w/o Starter Content
  2. Create a new C++ class derived from BlueprintFunctionLibrary
    1. in the C++ Classes folder right click then select New C++ Class
    2. Select All Classes then search for and select BlueprintFunctionLibrary
    3. Press Next then name it MeshMergeFunctionLibrary then select Create Class
    4. In Visual Studio select Reload All if prompted
  3. In MeshMergeFunctionLibrary.h delete everything then paste in the code from the example:
    // Fill out your copyright notice in the Description page of Project Settings.
    #pragma once
    #include "CoreMinimal.h"
    #include "Kismet/BlueprintFunctionLibrary.h"
    #include "UObject/NoExportTypes.h"
    #include "MeshMergeFunctionLibrary.generated.h"
    /**
    * Blueprint equivalent of FSkeleMeshMergeSectionMapping
    * Info to map all the sections from a single source skeletal mesh to
    * a final section entry in the merged skeletal mesh.
    */
    USTRUCT(BlueprintType)
    struct PROJECTNAME_API FSkelMeshMergeSectionMapping_BP
    {
        GENERATED_BODY()
            /** Indices to final section entries of the merged skeletal mesh */
            UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mesh Merge Params")
            TArray < int32 > SectionIDs;
    };
    /**
    * Used to wrap a set of UV Transforms for one mesh.
    */
    USTRUCT(BlueprintType)
    struct PROJECTNAME_API FSkelMeshMergeUVTransform
    {
        GENERATED_BODY()
            /** A list of how UVs should be transformed on a given mesh, where index represents a specific UV channel. */
            UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mesh Merge Params")
            TArray < FTransform > UVTransforms;
    };
    /**
    * Blueprint equivalent of FSkelMeshMergeUVTransforms
    * Info to map all the sections about how to transform their UVs
    */
    USTRUCT(BlueprintType)
    struct PROJECTNAME_API FSkelMeshMergeUVTransformMapping
    {
        GENERATED_BODY()
            /** For each UV channel on each mesh, how the UVS should be transformed. */
            UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mesh Merge Params")
            TArray < FSkelMeshMergeUVTransform > UVTransformsPerMesh;
    };
    /**
    * Struct containing all parameters used to perform a Skeletal Mesh merge.
    */
    USTRUCT(BlueprintType)
    struct PROJECTNAME_API FSkeletalMeshMergeParams
    {
        GENERATED_BODY()
            FSkeletalMeshMergeParams()
        {
            StripTopLODS = 0;
            bNeedsCpuAccess = false;
            bSkeletonBefore = false;
            Skeleton = nullptr;
        }
        // An optional array to map sections from the source meshes to merged section entries
        UPROPERTY(EditAnywhere, BlueprintReadWrite)
            TArray < FSkelMeshMergeSectionMapping_BP > MeshSectionMappings;
        // An optional array to transform the UVs in each mesh
        UPROPERTY(EditAnywhere, BlueprintReadWrite)
            TArray < FSkelMeshMergeUVTransformMapping > UVTransformsPerMesh;
        // The list of skeletal meshes to merge.
        UPROPERTY(EditAnywhere, BlueprintReadWrite)
            TArray < USkeletalMesh* > MeshesToMerge;
        // The number of high LODs to remove from input meshes
        UPROPERTY(EditAnywhere, BlueprintReadWrite)
            int32 StripTopLODS;
        // Whether or not the resulting mesh needs to be accessed by the CPU for any reason (e.g. for spawning particle effects).
        UPROPERTY(EditAnywhere, BlueprintReadWrite)
            uint32 bNeedsCpuAccess : 1;
        // Update skeleton before merge. Otherwise, update after.
        // Skeleton must also be provided.
        UPROPERTY(EditAnywhere, BlueprintReadWrite)
            uint32 bSkeletonBefore : 1;
        // Skeleton that will be used for the merged mesh.
        // Leave empty if the generated skeleton is OK.
        UPROPERTY(EditAnywhere, BlueprintReadOnly)
            class USkeleton* Skeleton;
    };
    /**
    *
    */
    UCLASS()
    class PROJECTNAME_API UMeshMergeFunctionLibrary : public UBlueprintFunctionLibrary
    {
        GENERATED_BODY()
    public:
        /**
        * Merges the given meshes into a single mesh.
        * @return The merged mesh (will be invalid if the merge failed).
        */
        UFUNCTION(BlueprintCallable, Category = "Mesh Merge", meta = (UnsafeDuringActorConstruction = "true"))
            static class USkeletalMesh* MergeMeshes(const FSkeletalMeshMergeParams& Params);
    };

    4. In MeshMergeFunctionLibrary.cpp delete everything then paste in the the code from the example:

    // Fill out your copyright notice in the Description page of Project Settings.
    #include "MeshMergeFunctionLibrary.h"
    #include "SkeletalMeshMerge.h"
    #include "Engine/SkeletalMeshSocket.h"
    #include "Engine/SkeletalMesh.h"
    #include "Animation/Skeleton.h"
    static void ToMergeParams(const TArray<FSkelMeshMergeSectionMapping_BP>& InSectionMappings, TArray<FSkelMeshMergeSectionMapping>& OutSectionMappings)
    {
        if (InSectionMappings.Num() > 0)
        {
            OutSectionMappings.AddUninitialized(InSectionMappings.Num());
            for (int32 i = 0; i < InSectionMappings.Num(); ++i)
            {
                OutSectionMappings[i].SectionIDs = InSectionMappings[i].SectionIDs;
            }
        }
    };
    static void ToMergeParams(const TArray<FSkelMeshMergeUVTransformMapping>& InUVTransformsPerMesh, TArray<FSkelMeshMergeUVTransforms>& OutUVTransformsPerMesh)
    {
        if (InUVTransformsPerMesh.Num() > 0)
        {
            OutUVTransformsPerMesh.Empty();
            OutUVTransformsPerMesh.AddUninitialized(InUVTransformsPerMesh.Num());
            for (int32 i = 0; i < InUVTransformsPerMesh.Num(); ++i)
            {
                TArray<TArray<FTransform>>& OutUVTransforms = OutUVTransformsPerMesh[i].UVTransformsPerMesh;
                const TArray<FSkelMeshMergeUVTransform>& InUVTransforms = InUVTransformsPerMesh[i].UVTransformsPerMesh;
                if (InUVTransforms.Num() > 0)
                {
                    OutUVTransforms.Empty();
                    OutUVTransforms.AddUninitialized(InUVTransforms.Num());
                    for (int32 j = 0; j < InUVTransforms.Num(); j++)
                    {
                        OutUVTransforms[i] = InUVTransforms[i].UVTransforms;
                    }
                }
            }
        }
    };
    USkeletalMesh* UMeshMergeFunctionLibrary::MergeMeshes(const FSkeletalMeshMergeParams& Params)
    {
        TArray<USkeletalMesh*> MeshesToMergeCopy = Params.MeshesToMerge;
        MeshesToMergeCopy.RemoveAll([](USkeletalMesh* InMesh)
        {
            return InMesh == nullptr;
        });
        if (MeshesToMergeCopy.Num() <= 1)
        {
            UE_LOG(LogTemp, Warning, TEXT("Must provide multiple valid Skeletal Meshes in order to perform a merge."));
            return nullptr;
        }
        EMeshBufferAccess BufferAccess = Params.bNeedsCpuAccess ?
            EMeshBufferAccess::ForceCPUAndGPU :
            EMeshBufferAccess::Default;
        TArray<FSkelMeshMergeSectionMapping> SectionMappings;
        TArray<FSkelMeshMergeUVTransforms> UvTransforms;
        ToMergeParams(Params.MeshSectionMappings, SectionMappings);
        ToMergeParams(Params.UVTransformsPerMesh, UvTransforms);
        bool bRunDuplicateCheck = false;
        USkeletalMesh* BaseMesh = NewObject<USkeletalMesh>();
        if (Params.Skeleton && Params.bSkeletonBefore)
        {
            BaseMesh->Skeleton = Params.Skeleton;
            bRunDuplicateCheck = true;
            for (USkeletalMeshSocket* Socket : BaseMesh->GetMeshOnlySocketList())
            {
                if (Socket)
                {
                    UE_LOG(LogTemp, Warning, TEXT("SkelMeshSocket: %s"), *(Socket->SocketName.ToString()));
                }
            }
            for (USkeletalMeshSocket* Socket : BaseMesh->Skeleton->Sockets)
            {
                if (Socket)
                {
                    UE_LOG(LogTemp, Warning, TEXT("SkelSocket: %s"), *(Socket->SocketName.ToString()));
                }
            }
        }
        FSkeletalMeshMerge Merger(BaseMesh, MeshesToMergeCopy, SectionMappings, Params.StripTopLODS, BufferAccess, UvTransforms.GetData());
        if (!Merger.DoMerge())
        {
            UE_LOG(LogTemp, Warning, TEXT("Merge failed!"));
            return nullptr;
        }
        if (Params.Skeleton && !Params.bSkeletonBefore)
        {
            BaseMesh->Skeleton = Params.Skeleton;
        }
        if (bRunDuplicateCheck)
        {
            TArray<FName> SkelMeshSockets;
            TArray<FName> SkelSockets;
            for (USkeletalMeshSocket* Socket : BaseMesh->GetMeshOnlySocketList())
            {
                if (Socket)
                {
                    SkelMeshSockets.Add(Socket->GetFName());
                    UE_LOG(LogTemp, Warning, TEXT("SkelMeshSocket: %s"), *(Socket->SocketName.ToString()));
                }
            }
            for (USkeletalMeshSocket* Socket : BaseMesh->Skeleton->Sockets)
            {
                if (Socket)
                {
                    SkelSockets.Add(Socket->GetFName());
                    UE_LOG(LogTemp, Warning, TEXT("SkelSocket: %s"), *(Socket->SocketName.ToString()));
                }
            }
            TSet<FName> UniqueSkelMeshSockets;
            TSet<FName> UniqueSkelSockets;
            UniqueSkelMeshSockets.Append(SkelMeshSockets);
            UniqueSkelSockets.Append(SkelSockets);
            int32 Total = SkelSockets.Num() + SkelMeshSockets.Num();
            int32 UniqueTotal = UniqueSkelMeshSockets.Num() + UniqueSkelSockets.Num();
            UE_LOG(LogTemp, Warning, TEXT("SkelMeshSocketCount: %d | SkelSocketCount: %d | Combined: %d"), SkelMeshSockets.Num(), SkelSockets.Num(), Total);
            UE_LOG(LogTemp, Warning, TEXT("SkelMeshSocketCount: %d | SkelSocketCount: %d | Combined: %d"), UniqueSkelMeshSockets.Num(), UniqueSkelSockets.Num(), UniqueTotal);
            UE_LOG(LogTemp, Warning, TEXT("Found Duplicates: %s"), *((Total != UniqueTotal) ? FString("True") : FString("False")));
        }
        return BaseMesh;
    }

    5. Save all in Visual Studio
    6. In the Editor press Recompile and Reload in the bottom right corner

 

Expected Results:
The Code compiles and the user can continue to follow along with the example.

Actual Results:
The Code fails to compile stating "Error: Duplicate struct name: SkelMeshMergeUVTransformMapping also exists in file E:\Git\UE5Rel-18747223\Engine\Source\Runtime\Engine\Public\SkeletalMeshMerge.h"

Have Comments or More Details?

There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-142182 in the post.

5
Login to Vote

Won't Fix
ComponentUE - Anim
Affects Versions5.0
CreatedFeb 10, 2022
ResolvedFeb 11, 2022
UpdatedMar 15, 2022
View Jira Issue