🌹 Thank You for UE5!

:zap::zap::zap::zap::zap::zap::zap::zap::zap::zap::zap::zap::zap::zap::zap::zap::zap::zap::zap::zap::zap:
:zap::zap::zap: The Joy of UE5 Is Growing :zap::zap::zap:

:zap::zap::zap::zap::zap::zap::zap::zap::zap::zap::zap::zap::zap::zap::zap::zap::zap::zap::zap:

:heart: :heart: :heart: Dear World, :heart: :heart: :heart:

:heart: :heart: :heart: And Dear Epic, :heart: :heart: :heart:

:heart: :heart: :heart: And Dear Community, :heart: :heart: :heart:

Oh How the Joy of Unreal Engine is Growing! :star2:

zap: Look what We can do in UE5, World! :zap:

We can create our own static mesh assets in Editor, using BP nodes!

Unreal Engine is a Modelling Tool ! <~~~

We Can Truly Make Our Own Worlds Now

:heart: :heart: :heart: Thank You Epic! :heart: :heart: :heart:

I Hope You Enjoy The Video:

With Me Talking About Unreal Engine Modelling Suite!

:zap: The Relevant C++ Code :zap:

:heart: Thank You Epic Devs! :heart:

//~~~ CreateStaticMeshAssetFromDynamicMesh ~~~
#include "GeometryFramework/Public/Components/DynamicMeshComponent.h"

//Runtime
//Engine\Plugins\Runtime\MeshModelingToolset\Source\ModelingComponents\Public\ModelingObjectsCreationAPI.h
#include "ModelingObjectsCreationAPI.h"
 
//Editor
#if WITH_EDITOR
	//"ModelingComponentsEditorOnly" in build.cs ♥ 
	#include "AssetUtils/CreateStaticMeshUtil.h"
#endif
//~~~~ End CreateStaticMeshAssetFromDynamicMesh ~~~

UStaticMesh* UVictoryBPFunctionLibrary::CreateStaticMeshAssetFromDynamicMesh( 
	FString ContentFolderPath,
	UDynamicMeshComponent* DynamicMeshComp, 
	FString& Status, 
	FString& NewAssetFilePath, 
	bool& Success
){
	NewAssetFilePath = "";
	
	#if WITH_EDITOR
		
	//Comp?
	if(!DynamicMeshComp || !DynamicMeshComp->GetDynamicMesh())
	{
		Status = "No valid Dynamic Mesh Component was supplied!";
		Success = false;
		return nullptr;
	}
	
	//No Triangles?
	if(DynamicMeshComp->GetDynamicMesh()->IsEmpty())
	{
		Status = "Dynamic Mesh has no triangles!";
		Success = false;
		return nullptr;
	}
	
	//Make sure to remove extension if user added it ♥ 
	
	//File without any extension
	ContentFolderPath.ReplaceInline(TEXT(".uasset"),TEXT(""));
	  
	//~~~ Create possibly numbered new filename, if supplied exists! ♥ 
	FString FinalRelativePath = "";
	bool FolderTreeCreated = UVictoryBPFunctionLibrary::GenerateUniqueContentRelativeFileName(ContentFolderPath + ".uasset",FinalRelativePath,NewAssetFilePath);
	  
	if(!FolderTreeCreated)
	{
		Status = "Could not create the specified directory tree";
		Success = false;
		return false;
	}
	
	//Remove ext before sending to AssetUtils
	//// Path is now Relative with no extension, possibly with 1,2,3 added for each create event in editor! <3 
	FinalRelativePath.ReplaceInline(TEXT(".uasset"),TEXT(""));
	
	//~~~ End of file path input handling ♥  ~~~
	
	//Create Mesh Base Params
	FCreateMeshObjectParams CreateMeshParams;
	
	CreateMeshParams.BaseName 		= FinalRelativePath;
	
	//~~~ Materials ~~~
	DynamicMeshComp->ValidateMaterialSlots();
	
	for(int32 v = 0; v < DynamicMeshComp->GetNumMaterials() ; v++)
	{
		CreateMeshParams.Materials.Add(DynamicMeshComp->GetMaterial(v));
	}
	 
	//Set from FDynamicMesh3 (the actual mesh herself, not from a MeshDescription)
	CreateMeshParams.SetMesh(DynamicMeshComp->GetMesh());
	
	//Ensure Set to DynamicMesh
	CreateMeshParams.MeshType = ECreateMeshObjectSourceMeshType::DynamicMesh;
	
	//~~~
	//~~~
	//~~~
	
	//~~~~~~~~~~~~~~~~~
	// Code from UE_5.0\Engine\Plugins\Runtime\MeshModelingToolset\Source\ModelingComponentsEditorOnly\PublicEditorModelingObjectsCreationAPI.cpp
	
	//Static Mesh!
	//CreateMeshObjectResult = EditorCreateMeshAPI->CreateStaticMeshAsset(CreateMeshParams);
	
	//Static Asset Options
	UE::AssetUtils::FStaticMeshAssetOptions AssetOptions;
	AssetOptions.NewAssetPath = "/Game/" + CreateMeshParams.BaseName;
	 
	//Ensure no // 
	FPaths::RemoveDuplicateSlashes(AssetOptions.NewAssetPath);
	 
	AssetOptions.NumSourceModels = 1;
	AssetOptions.NumMaterialSlots = CreateMeshParams.Materials.Num();
	
	//Got rid of FilterMaterials part <3 
	AssetOptions.AssetMaterials = (CreateMeshParams.AssetMaterials.Num() == AssetOptions.NumMaterialSlots) 
		? CreateMeshParams.AssetMaterials 
		: CreateMeshParams.Materials;

	AssetOptions.bEnableRecomputeNormals 	= CreateMeshParams.bEnableRecomputeNormals;
	AssetOptions.bEnableRecomputeTangents 	= CreateMeshParams.bEnableRecomputeTangents;
	AssetOptions.bGenerateNaniteEnabledMesh = CreateMeshParams.bEnableNanite;
	AssetOptions.NaniteProxyTrianglePercent = CreateMeshParams.NaniteProxyTrianglePercent;

	AssetOptions.bCreatePhysicsBody 		= CreateMeshParams.bEnableCollision;
	AssetOptions.CollisionType 				= CreateMeshParams.CollisionMode;

	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	//Dynamic Mesh! ♥ 
	FDynamicMesh3* DynamicMesh = &CreateMeshParams.DynamicMesh.GetValue();
	AssetOptions.SourceMeshes.DynamicMeshes.Add(DynamicMesh);
	//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	
	//Static Mesh Result
	UE::AssetUtils::FStaticMeshResults ResultData;
	 
	//==========
	//CREATE!!!
	UE::AssetUtils::ECreateStaticMeshResult AssetResult = UE::AssetUtils::CreateStaticMeshAsset(AssetOptions, ResultData);
	//==========
 
	if (AssetResult != UE::AssetUtils::ECreateStaticMeshResult::Ok)
	{
		Status = "UE::AssetUtils::ECreateStaticMeshResult is ECreateModelingObjectResult::Failed_AssetCreationFailed";
		Success = false;
		return false;
	}
  
	// End of code from PublicEditorModelingObjectsCreationAPI.cpp
	//~~~~~~~~~~~~~~~~~
	
	Status = "Victory!";
	Success = true;
	return ResultData.StaticMesh;
	#endif
	
	Status = "This node is for Editor Builds only, but does create static mesh assets that can ship with your packaged game! ♥ ";
	Success = false;
	return nullptr;
}


bool UVictoryBPFunctionLibrary::GenerateUniqueContentRelativeFileName(FString ContentRelativeFilePath, FString& ContentRelativeNewFileName, FString& AbsolutePath, bool CreateFolderTree)
{
	//UE User-Input Assistance (inline) ♥ 
	FPaths::NormalizeFilename(ContentRelativeFilePath);
	FPaths::RemoveDuplicateSlashes(ContentRelativeFilePath);
	
	FString AbsContentPath = FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir());
	
	//Extension
	FString FileExt		=  FPaths::GetExtension(ContentRelativeFilePath, true); //include .
	
	//File without any extension
	FString AssetFile 	= FPaths::GetBaseFilename(ContentRelativeFilePath);
	
	//Everything but the file
	FString BasePath 	= FPaths::GetPath(ContentRelativeFilePath);
	
	
	if(ContentRelativeFilePath.Contains("/"))
	{	
		//Absolute Path
		BasePath = AbsContentPath + BasePath; 
		 
		if(CreateFolderTree)
		{
			//Folder?
			if(!FPlatformFileManager::Get().GetPlatformFile().CreateDirectoryTree(*BasePath))
			{ 
				//Info out to user about what was attempted
				AbsolutePath = BasePath;
				return false;
				//~~~~~~~~~~~~~~~~~~~~~~
			}
		}
	}
	
	//~~~
	// Make path with extension
	if(BasePath != "")
	{
		AbsolutePath = BasePath + "/" + AssetFile;
	}
	else
	{
		AbsolutePath = AbsContentPath + AssetFile;
	}
	 
	//Check if file exists already, increment int as needed, ♥ 
	 
	//Absolute Path + File, Still No Extension yet
	BasePath 		= AbsolutePath;
	  
	int32 FileNameInt 	= 1;
	AbsolutePath 		= BasePath + FileExt;
	while(FPlatformFileManager::Get().GetPlatformFile().FileExists( *AbsolutePath))
	{
		FileNameInt++;
		AbsolutePath = BasePath + FString::FromInt(FileNameInt) + FileExt;
	}
	
	FString Left;
	
	//Make Relative
	AbsolutePath.Split(TEXT("/Content/"),&Left,&ContentRelativeNewFileName);
	
	return true;
}

:sparkling_heart: Have Fun Creating Static Mesh Assets Using Your Own Custom Modeling Tools :sparkling_heart:

And Modeling to Your Creative Heart’s Content in the Level Viewport! :sparkling_heart:

:heart:

PS: You will want to enable this plugin for use with the new content in the Victory Plugin Content Folder:

(The content demoed in the video is zipped inside Victory Plugin Content Folder so you can make sure you have Geometry Script Plugin enabled first)

PSS: More info: