Save Any Dynamic Mesh as a New Static Mesh Asset in Editor Builds
In this complimentary C++ → BP Library Plugin
(17 min video showing you exactly how to use the new content included with Victory Plugin!)
(Dynamic Mesh Actor + Spline Component + Victory Plugin = New Static Mesh Asset!)
Dear Community,
I’ve added a new node to Victory Plugin so you can easily save out as a new Static Mesh Asset, any Dynamic Mesh you create using any of your own custom logic in constructor script to make really fancy shapes!
Yes!!!
So you really can make your own modelling tools inside of Unreal Engine, using BP constructor script, and then make new Static Mesh Assets that will ship with your game!
Create Your Own Static Mesh Asset Modeling Tools!
EX: Dynamic Mesh + Spline Component Modeling Tool
I show you an example of what I mean in the video, creating a Spline Mesh Modeling Tool in BP, a BP which you can find in the Content Folder of the Victory Plugin, along with the example assets!
Please note this BP node is Editor Only, I explain more in the video!
The Relevant C++ Code
Because I let you choose any asset path with your game’s content folder for your new Static Mesh Asset, create the folder structure for you, handle the case of if the asset path is already in use, there is some extra file handling code, but the rest is the utilization of the new MeshModelingToolset plugin that exists in UE5!
Thank You Epic Devs!
//~~~ 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;
}
Have Fun Creating Static Mesh Assets Using Your Own Custom Modeling Tools
And Modeling to Your Creative Heart’s Content in the Level Viewport!
Geometry Script Plugin (Comes with UE5)
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)
Download Link
PSS: You can download the Victory BP Plugin Here!