Merge Multiple StaticMesh Components into a Single Mesh using Python

Is the way to Merge Actor with multiple StaticMesh Components into a Single Mesh using Python similar to what MergeActors editor utility does. We are being to merge multiple StaticMesh actors into a single mesh using following function but that function doesn’t have input to provide `components`.

unreal.get_editor_subsystem(unreal.StaticMeshEditorSubsystem).merge_static_mesh_actors(actors_to_merge, merge_options)

Hi Parth Patel,

Unfortunately, there is currently no BP/Python-exported function that allows you to pass individual PrimitiveComponents or StaticMeshComponents for merging. If you need that functionality, I’m afraid you will need to do a small C++ implementation making use of the function IMeshMergeUtilities::MergeComponentsToStaticMesh() from file “IMeshMergeUtilities.h”. This is used internally by the Merge Actors tool, and does allow you to specify individual Primitive Components.

Note that your implementation might need to account for things like syncing the AssetRegistry and ContentBrowser to any newly created assets. You can refer to FMeshMergingTool::RunMerge() from file “MeshMergingTool.cpp” if you want to try to replicate the functionality of the Merge Actors Tool exactly. Your implementation could be made, for example, as a BlueprintFunctionLibrary function inside an Editor Module in your project or a plugin. Here are some snippets to get you started:

// MyEditorLibrary.h
 
#pragma once
#include "Kismet/BlueprintFunctionLibrary.h"
#include "MyEditorLibrary.generated.h"
 
class UPrimitiveComponent;
struct FMeshMergingSettings;
 
UCLASS()
class UMyEditorLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()
public:
	UFUNCTION(BlueprintCallable)
	static void MergePrimitiveComponents (FString PackageName, TArray<UPrimitiveComponent*> ComponentsToMerge, const FMeshMergingSettings& MergeSettings);
};
// MyEditorLibrary.cpp
 
#include "MyEditorLibrary.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "ContentBrowserModule.h"
#include "IContentBrowserSingleton.h"
#include "MeshMergeModule.h"
#include "ObjectTools.h"
 
void UMyEditorLibrary::MergePrimitiveComponents(FString PackageName, TArray<UPrimitiveComponent*> ComponentsToMerge, const FMeshMergingSettings& MergeSettings)
{
	if (ComponentsToMerge.IsEmpty())
		return;
 
	UWorld* World = ComponentsToMerge[0]->GetWorld();
	checkf(World != nullptr, TEXT("Invalid World retrieved from Primitive Components"));
 
	const IMeshMergeUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked<IMeshMergeModule>("MeshMergeUtilities").GetUtilities();
 
	FVector MergedActorLocation;
	TArray<UObject*> AssetsToSync;
	const float ScreenAreaSize = TNumericLimits<float>::Max();
 
	// If the merge destination package already exists, it is possible that the mesh is already used in a scene somewhere, or its materials or even just its textures.
	// Static primitives uniform buffers could become invalid after the operation completes and lead to memory corruption. To avoid it, we force a global reregister.
	if (FindObject<UObject>(nullptr, *PackageName))
	{
		FGlobalComponentReregisterContext GlobalReregister;
		MeshUtilities.MergeComponentsToStaticMesh(ComponentsToMerge, World, MergeSettings, nullptr, nullptr, PackageName, AssetsToSync, MergedActorLocation, ScreenAreaSize, true);
	}
	else
	{
		MeshUtilities.MergeComponentsToStaticMesh(ComponentsToMerge, World, MergeSettings, nullptr, nullptr, PackageName, AssetsToSync, MergedActorLocation, ScreenAreaSize, true);
	}
 
	if (!AssetsToSync.IsEmpty())
	{
		FAssetRegistryModule& AssetRegistry = FModuleManager::Get().LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
 
		for (UObject* AssetToSync : AssetsToSync)
		{
			// MergeComponentsToStaticMesh() will have outered all assets (material instance, textures) to the static mesh package.
			// Move each of them to their own package, so that they show up in the Content Browser
			if (AssetToSync && !AssetToSync->IsA<UStaticMesh>())
			{
				FString AssetName = AssetToSync->GetName();
				FString AssetPackagePath = FPackageName::GetLongPackagePath(AssetToSync->GetPathName());
				FString AssetPackageName = AssetPackagePath / AssetName;
 
				UPackage* AssetPackage = CreatePackage(*AssetPackageName);
				check(AssetPackage);
				AssetPackage->FullyLoad();
				AssetPackage->Modify();
 
				// Replace existing asset by the new one.
				if (UObject* OldAsset = FindObject<UObject>(AssetPackage, *AssetName))
				{
					FName ObjectName = OldAsset->GetFName();
					UObject* Outer = OldAsset->GetOuter();
					OldAsset->Rename(nullptr, GetTransientPackage(), REN_DoNotDirty | REN_DontCreateRedirectors);
 
					// Consolidate or "Replace" the old object with the new object for any living references.
					bool bShowDeleteConfirmation = false;
					TArray<UObject*> OldDataAssetArray = { OldAsset };
					ObjectTools::ConsolidateObjects(AssetToSync, { OldDataAssetArray }, bShowDeleteConfirmation);
				}
 
				AssetToSync->Rename(*AssetName, AssetPackage, REN_DontCreateRedirectors);
				AssetToSync->SetFlags(RF_Public | RF_Standalone);
			}
 
			AssetRegistry.AssetCreated(AssetToSync);
			GEditor->BroadcastObjectReimported(AssetToSync);
		}
 
		//Also notify the content browser that the new assets exists
		FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
		ContentBrowserModule.Get().SyncBrowserToAssets(AssetsToSync, true);
	}
}
// MyEditorUtils.Build.cs
 
using UnrealBuildTool;
 
public class MyEditorUtils : ModuleRules
{
  public MyEditorUtils (ReadOnlyTargetRules Target) : base(Target)
  {
    PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
    
    // (...)
    
    PrivateDependencyModuleNames.AddRange(new string[] {
      "AssetRegistry",
      "ContentBrowser",
      "MeshMergeUtilities",
      "UnrealEd",
    });
  }
}
#Python
unreal.MyEditorLibrary.merge_primitive_components(package_name, components_to_merge, merge_settings)

I hope this is helpful. Please let me know if this solution works for you.

Best regards,

Vitor

Vitor,

Thanks for the detailed response along with code snippet. we will try it out and let you know if have any additional questions or issues.

Thanks again