Procedural mesh not saving all of its sections to static mesh

I was able to fix the ‘AddSourceModel’ issue usng the following code for that part in 5.1. You have to add an empty source model and then modify it

// Create a Source Model then set it to variable
    StaticMesh->AddSourceModel();
    FStaticMeshSourceModel& SrcModel = StaticMesh->GetSourceModel(0);
				
    // Add source to new StaticMesh
    SrcModel.BuildSettings.bRecomputeNormals = false;
    SrcModel.BuildSettings.bRecomputeTangents = false;
    SrcModel.BuildSettings.bRemoveDegenerates = false;
    SrcModel.BuildSettings.bUseHighPrecisionTangentBasis = false;
    SrcModel.BuildSettings.bUseFullPrecisionUVs = false;
    SrcModel.BuildSettings.bGenerateLightmapUVs = true;
    SrcModel.BuildSettings.SrcLightmapIndex = 0;
    SrcModel.BuildSettings.DstLightmapIndex = 1;
    SrcModel.RawMeshBulkData->SaveRawMesh(RawMesh);

Hope this helps. Thanks to you all when I needed this!

1 Like

@AnderGraw 's code works but only does the first mesh part, it returns in the loop instead of getting all the mesh sections with materials.
I fixed it and also included @Xanfar001 's stuff for 5.1.
Also separated the save path and the asset name as it’s cleaner.

It works great, I was able to make my mesh slicer thanks for everyone who posted code snippets <3

The code for saving a procedural mesh to static mesh via BFL:

//Hallo from Unreal Forums
UStaticMesh* UWeedFarmerBFL::SaveProcmesh(UProceduralMeshComponent* ProcMesh, FString SavePath, FString Name)
{
	UProceduralMeshComponent* ProcMeshComp = ProcMesh;
	if (ProcMeshComp != nullptr)
	{
		FString PackageName = SavePath;
		FRawMesh RawMesh;
		TArray<UMaterialInterface*> MeshMaterials;
		const int32 NumSections = ProcMeshComp->GetNumSections();
		int32 VertexBase = 0;
		for (int32 SectionIdx = 0; SectionIdx < NumSections; SectionIdx++)
		{
			FProcMeshSection* ProcSection = ProcMeshComp->GetProcMeshSection(SectionIdx);
			// Copy verts
			for (FProcMeshVertex& Vert : ProcSection->ProcVertexBuffer)
			{
				RawMesh.VertexPositions.Add(FVector3f(Vert.Position));
			}
			// Copy 'wedge' info
			int32 NumIndices = ProcSection->ProcIndexBuffer.Num();
			for (int32 IndexIdx = 0; IndexIdx < NumIndices; IndexIdx++)
			{
				int32 Index = ProcSection->ProcIndexBuffer[IndexIdx];
				RawMesh.WedgeIndices.Add(Index + VertexBase);
				FProcMeshVertex& ProcVertex = ProcSection->ProcVertexBuffer[Index];
				FVector3f TangentX = FVector3f(ProcVertex.Tangent.TangentX);
				FVector3f TangentZ = FVector3f(ProcVertex.Normal);
				FVector3f TangentY = FVector3f(
					(TangentX ^ TangentZ).GetSafeNormal() * (ProcVertex.Tangent.bFlipTangentY ? -1.f : 1.f));
				RawMesh.WedgeTangentX.Add(TangentX);
				RawMesh.WedgeTangentY.Add(TangentY);
				RawMesh.WedgeTangentZ.Add(TangentZ);
				RawMesh.WedgeTexCoords[0].Add(FVector2f(ProcVertex.UV0));
				RawMesh.WedgeColors.Add(ProcVertex.Color);
			}
			// copy face info
			int32 NumTris = NumIndices / 3;
			for (int32 TriIdx = 0; TriIdx < NumTris; TriIdx++)
			{
				RawMesh.FaceMaterialIndices.Add(SectionIdx);
				RawMesh.FaceSmoothingMasks.Add(0); // Assume this is ignored as bRecomputeNormals is false
			}
			// Remember material
			MeshMaterials.Add(ProcMeshComp->GetMaterial(SectionIdx));
			// Update offset for creating one big index/vertex buffer
			VertexBase += ProcSection->ProcVertexBuffer.Num();
		}
		// If we got some valid data.
		if (RawMesh.VertexPositions.Num() > 3 && RawMesh.WedgeIndices.Num() > 3)
		{
			// Then find/create it.
			UPackage* Package = CreatePackage(*PackageName);
			check(Package);
			// Create StaticMesh object
			UStaticMesh* StaticMesh = NewObject<UStaticMesh>(Package, FName(*Name), RF_Public | RF_Standalone);
			StaticMesh->InitResources();
			FGuid::NewGuid() = StaticMesh->GetLightingGuid();
			//StaticMesh->GetLightingGuid() = FGuid::NewGuid();
			// Create a Source Model then set it to variable
			StaticMesh->AddSourceModel();
			FStaticMeshSourceModel& SrcModel = StaticMesh->GetSourceModel(0);
			// Add source to new StaticMesh
			SrcModel.BuildSettings.bRecomputeNormals = false;
			SrcModel.BuildSettings.bRecomputeTangents = false;
			SrcModel.BuildSettings.bRemoveDegenerates = false;
			SrcModel.BuildSettings.bUseHighPrecisionTangentBasis = false;
			SrcModel.BuildSettings.bUseFullPrecisionUVs = false;
			SrcModel.BuildSettings.bGenerateLightmapUVs = true;
			SrcModel.BuildSettings.SrcLightmapIndex = 0;
			SrcModel.BuildSettings.DstLightmapIndex = 1;
			SrcModel.RawMeshBulkData->SaveRawMesh(RawMesh);
			// Copy materials to new mesh
			for (UMaterialInterface* Material : MeshMaterials)
			{
				StaticMesh->GetStaticMaterials().Add(FStaticMaterial(Material));
			}
			//Set the Imported version before calling the build
			StaticMesh->ImportVersion = EImportStaticMeshVersion::LastVersion;
			// Build mesh from source
			StaticMesh->Build(false);
			StaticMesh->PostEditChange();
			// Notify asset registry of new asset
			FAssetRegistryModule::AssetCreated(StaticMesh);
			return StaticMesh;
		}
	}
	return nullptr;
}
2 Likes

I think this addresses to another usecase but it shouldn’t work on packaged runtime.

I used this code (based on yours) and it worked on packaged runtime.

    if (IsValid(In_Pmc) == false)
    {
        return false;
    }
    
    FName PMC_Name = FName(*In_Pmc->GetName());
    UStaticMesh* StaticMesh = NewObject<UStaticMesh>(GetTransientPackage(), PMC_Name, EObjectFlags::RF_Transient);
    StaticMesh->bAllowCPUAccess = true;
    StaticMesh->NeverStream = true;
    StaticMesh->InitResources();
    StaticMesh->SetLightingGuid();

    FMeshDescription PMC_Description = BuildMeshDescription(In_Pmc);
    UStaticMeshDescription* SM_Description = StaticMesh->CreateStaticMeshDescription();
    SM_Description->SetMeshDescription(PMC_Description);
    StaticMesh->BuildFromStaticMeshDescriptions({ SM_Description }, false);
    
    // Collision
    StaticMesh->CalculateExtendedBounds();
    StaticMesh->SetBodySetup(In_Pmc->ProcMeshBodySetup);

#if WITH_EDITOR
    
    StaticMesh->PostEditChange();

#endif

    StaticMesh->MarkPackageDirty();

    Out_Sm = StaticMesh;

    return true;

but the problem is I can’t use transcluent materials with it. I just deleted all material oriented codes and just set material from static mesh component with blueprint. Transcluent ones dissapear completely. There is collision and static mesh is not null but I think there is a problem with buffers.

2 Likes

Have you resolved this issue? I have also encountered the same issue and cannot package the Windows platform properly

// Create StaticMesh object
UStaticMesh* StaticMesh = NewObject();

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

	FMeshDescription MeshDescription = BuildMeshDescription(ProceduralMeshComponent);
	FStaticMeshAttributes StaticMeshAttributes(MeshDescription);
	StaticMeshAttributes.Register();

	// Build the static mesh render data, one FMeshDescription* per LOD.
	TArray<const FMeshDescription*> MeshDescriptionPtrs;
	MeshDescriptionPtrs.Emplace(&MeshDescription);

	UStaticMesh::FBuildMeshDescriptionsParams BuildParams;
	BuildParams.bAllowCpuAccess = true;
	BuildParams.bBuildSimpleCollision = false;

	StaticMesh->BuildFromMeshDescriptions(MeshDescriptionPtrs, BuildParams);

	// Collision
	StaticMesh->SetBodySetup(ProceduralMeshComponent->ProcMeshBodySetup);

	//// SIMPLE COLLISION
	if (!ProceduralMeshComponent->bUseComplexAsSimpleCollision)
	{
		StaticMesh->CreateBodySetup();
		UBodySetup* NewBodySetup = StaticMesh->GetBodySetup();
		NewBodySetup->BodySetupGuid = FGuid::NewGuid();
		NewBodySetup->AggGeom.ConvexElems = ProceduralMeshComponent->ProcMeshBodySetup->AggGeom.ConvexElems;
		NewBodySetup->bGenerateMirroredCollision = false;
		NewBodySetup->bDoubleSidedGeometry = true;
		NewBodySetup->CollisionTraceFlag = CTF_UseDefault;
		NewBodySetup->CreatePhysicsMeshes();
	}

it works in 4.27 runtime.

1 Like