Setting the material of a UStaticMesh built from Mesh Descriptions at runtime

I’ve been working on a neat project trying to use UE as the front-end for a 3D slicer ( for 3D Printing ).

So far my rendering procedure has been:

  1. Read in an *.stl file
  2. Create ProceduralMesh
  3. Recreate the ProceduralMesh for every duplicate/clone.

This was getting ridiculously slow at scale so I implemented the code below ( inspired by this post from newohrim [ many many thanks!! ] ) to generate static meshes that could be instantiated more efficiently.

#include "STLActor.h"
#include "ProceduralMeshComponent.h"
#include "ProceduralMeshConversion.h"
#include "StaticMeshDescription.h"
#include "Engine/StaticMesh.h"
#include "Components/StaticMeshComponent.h"


UStaticMesh* ASTLActor::BuildStaticMesh(UProceduralMeshComponent* ProcMesh)
{
	if (!ProcMesh)
		return nullptr;

	FMeshDescription MeshDescription = BuildMeshDescription(ProcMesh);

	// If we got some valid data.
	if (MeshDescription.Polygons().Num() > 0)
	{
		// Create StaticMesh object
		UStaticMesh* StaticMesh = NewObject<UStaticMesh>(ProcMesh/*Package, MeshName, RF_Public | RF_Standalone*/);
		StaticMesh->InitResources();

		StaticMesh->SetLightingGuid();

		// Add source to new StaticMesh
		auto Desc = StaticMesh->CreateStaticMeshDescription();
		Desc->SetMeshDescription(MeshDescription);
		// buildSimpleCol = false, cause it creates box collision based on mesh bounds or whatever :(
		StaticMesh->BuildFromStaticMeshDescriptions({ Desc }, false);

		//// SIMPLE COLLISION
		if (!ProcMesh->bUseComplexAsSimpleCollision)
		{
			StaticMesh->CreateBodySetup();
			UBodySetup* NewBodySetup = StaticMesh->GetBodySetup();
			NewBodySetup->BodySetupGuid = FGuid::NewGuid();
			NewBodySetup->AggGeom.ConvexElems = ProcMesh->ProcMeshBodySetup->AggGeom.ConvexElems;
			NewBodySetup->bGenerateMirroredCollision = false;
			NewBodySetup->bDoubleSidedGeometry = false;
			// Play around with the flag below if you struggle with collision not working
			NewBodySetup->CollisionTraceFlag = CTF_UseDefault; 
			NewBodySetup->CreatePhysicsMeshes();
		}

		
		//// MATERIALS
		TSet<UMaterialInterface*> UniqueMaterials;
		const int32 NumSections = ProcMesh->GetNumSections();
		for (int32 SectionIdx = 0; SectionIdx < NumSections; SectionIdx++)
		{
			FProcMeshSection *ProcSection =
				ProcMesh->GetProcMeshSection(SectionIdx);
			UMaterialInterface *Material = ProcMesh->GetMaterial(SectionIdx);
			UniqueMaterials.Add(Material);
		}
		// Copy materials to new mesh
		for (auto* Material : UniqueMaterials)
		{
		
			StaticMesh->GetStaticMaterials().Add(FStaticMaterial(Material));
			StaticMesh->AddMaterial(Material);
			StaticMesh->SetMaterial(0, Material);
			
		}
	

		// Uncallable in game runtime
		// StaticMesh->Build(false);

		return StaticMesh;
	}

	return nullptr;
}

This code succeeds in creating a static mesh, but I have been unable to set its material so it just renders with the default material even after trying to set the material in blueprints.

Does anyone know if I’m missing something, or what’s going on here?
Is there a way to set what material gets set by default?

Thanks for any help or suggestions!

P.S. I’m considering making this project opensource if there is interest in collaborating. Cheers!

This method of trying to create a UStaticMesh at runtime turned out to be a dead-end for me. I would recommend checking out RuntimeMesh on github. They have a marketplace plugin, but support for >UE 4.25 is limited to installation from github.

If that sounds intimidating… I would still give it a shot their directions are easy to follow.

anyways, Cheers!

I have the same problem and I think it is about this code in \UE_5.1\Engine\Source\Runtime\Engine\Private\StaticMesh.cpp\BuildFromMeshDescriptions (BuildFromStaticMeshDescriptions uses that function to operate)

#if WITH_EDITOR
	else
	{
		Build(true);
	}

	for (int32 LODIndex = 0; LODIndex < NewNumLODs; LODIndex++)
	{
		FStaticMeshLODResources& LODResources = GetRenderData()->LODResources[LODIndex];
		for (int32 SectionIndex = 0; SectionIndex < LODResources.Sections.Num(); SectionIndex++)
		{
			const FStaticMeshSection& StaticMeshSection = LODResources.Sections[SectionIndex];
			FMeshSectionInfo SectionInfo;
			SectionInfo.MaterialIndex = StaticMeshSection.MaterialIndex;
			SectionInfo.bEnableCollision = StaticMeshSection.bEnableCollision;
			SectionInfo.bCastShadow = StaticMeshSection.bCastShadow;
			GetSectionInfoMap().Set(LODIndex, SectionIndex, SectionInfo);
		}
	}
#endif

This is the correct answer, but GetSectionInfoMap() is only work on editor.
So i set section’s materialIndex directly

FStaticMeshLODResourcesArray& LODResources = StaticMesh->GetRenderData()->LODResources;
if (LODResources.Num() > 0)
{
	FStaticMeshSectionArray& Sections = LODResources[0].Sections;
	if (Sections.Num() > 0)
	{
		Sections[0].MaterialIndex = 0;
	}
}