1831 lines
66 KiB
C++
1831 lines
66 KiB
C++
/*
|
|
* Copyright (c) <2021> Side Effects Software Inc.
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* 2. The name of Side Effects Software may not be used to endorse or
|
|
* promote products derived from this software without specific prior
|
|
* written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS
|
|
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
|
|
* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
|
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "HoudiniEngineCommands.h"
|
|
|
|
#include "HoudiniEngineEditorPrivatePCH.h"
|
|
|
|
#include "HoudiniEngine.h"
|
|
#include "HoudiniEngineUtils.h"
|
|
#include "HoudiniEngineBakeUtils.h"
|
|
#include "HoudiniEngineEditorUtils.h"
|
|
#include "HoudiniEngineRuntime.h"
|
|
#include "HoudiniAssetActor.h"
|
|
#include "HoudiniAssetComponent.h"
|
|
#include "HoudiniOutputTranslator.h"
|
|
#include "HoudiniStaticMesh.h"
|
|
#include "HoudiniOutput.h"
|
|
#include "HoudiniEngineStyle.h"
|
|
|
|
#include "DesktopPlatformModule.h"
|
|
#include "Interfaces/IMainFrameModule.h"
|
|
#include "EditorDirectories.h"
|
|
#include "Misc/ScopedSlowTask.h"
|
|
#include "Async/Async.h"
|
|
#include "FileHelpers.h"
|
|
#include "AssetRegistryModule.h"
|
|
#include "Engine/ObjectLibrary.h"
|
|
#include "ObjectTools.h"
|
|
#include "CoreGlobals.h"
|
|
#include "HoudiniEngineOutputStats.h"
|
|
#include "Misc/FeedbackContext.h"
|
|
#include "HAL/FileManager.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "ISettingsModule.h"
|
|
|
|
#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE
|
|
|
|
FDelegateHandle FHoudiniEngineCommands::OnPostSaveWorldRefineProxyMeshesHandle = FDelegateHandle();
|
|
FHoudiniEngineCommands::FOnHoudiniProxyMeshesRefinedDelegate FHoudiniEngineCommands::OnHoudiniProxyMeshesRefinedDelegate = FHoudiniEngineCommands::FOnHoudiniProxyMeshesRefinedDelegate();
|
|
|
|
FHoudiniEngineCommands::FHoudiniEngineCommands()
|
|
: TCommands<FHoudiniEngineCommands> (TEXT("HoudiniEngine"), NSLOCTEXT("Contexts", "HoudiniEngine", "Houdini Engine Plugin"), NAME_None, FHoudiniEngineStyle::GetStyleSetName())
|
|
{
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::RegisterCommands()
|
|
{
|
|
UI_COMMAND(_CreateSession, "Create Session", "Creates a new Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord());
|
|
UI_COMMAND(_ConnectSession, "Connect Session", "Connects to an existing Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord());
|
|
UI_COMMAND(_StopSession, "Stop Session", "Stops the current Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord());
|
|
UI_COMMAND(_RestartSession, "Restart Session", "Restarts the current Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord());
|
|
UI_COMMAND(_OpenSessionSync, "Open Houdini Session Sync", "Opens Houdini with Session Sync and connect to it.", EUserInterfaceActionType::Button, FInputChord());
|
|
UI_COMMAND(_CloseSessionSync, "Close Houdini Session Sync", "Close the Session Sync Houdini.", EUserInterfaceActionType::Button, FInputChord());
|
|
|
|
// Viewport Sync
|
|
UI_COMMAND(_ViewportSyncNone, "Disabled", "Do not sync viewports.", EUserInterfaceActionType::Check, FInputChord());
|
|
UI_COMMAND(_ViewportSyncUnreal, "Sync Unreal to Houdini.", "Sync the Unreal viewport to Houdini's.", EUserInterfaceActionType::Check, FInputChord());
|
|
UI_COMMAND(_ViewportSyncHoudini, "Sync Houdini to Unreal", "Sync the Houdini viewport to Unreal's.", EUserInterfaceActionType::Check, FInputChord());
|
|
UI_COMMAND(_ViewportSyncBoth, "Both", "Sync both Unreal and Houdini's viewport.", EUserInterfaceActionType::Check, FInputChord());
|
|
|
|
// PDG Import Commandlet
|
|
UI_COMMAND(_StartPDGCommandlet, "Start Async Importer", "Start the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Button, FInputChord());
|
|
UI_COMMAND(_StopPDGCommandlet, "Stop Async Importer", "Stops the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Button, FInputChord());
|
|
UI_COMMAND(_IsPDGCommandletEnabled, "Enable Async Importer", "Enables the commandlet that imports PDG BGEO results in the background.", EUserInterfaceActionType::Check, FInputChord());
|
|
|
|
UI_COMMAND(_InstallInfo, "Installation Info", "Display information on the current Houdini Engine installation", EUserInterfaceActionType::Button, FInputChord());
|
|
UI_COMMAND(_PluginSettings, "PluginSettings", "Displays the Houdini Engine plugin settings", EUserInterfaceActionType::Button, FInputChord());
|
|
|
|
UI_COMMAND(_OpenInHoudini, "Open scene in Houdini", "Opens the current Houdini scene in Houdini.", EUserInterfaceActionType::Button, FInputChord(EKeys::O, EModifierKey::Control | EModifierKey::Alt));
|
|
UI_COMMAND(_SaveHIPFile, "Save Houdini scene (HIP)", "Saves a .hip file of the current Houdini scene.", EUserInterfaceActionType::Button, FInputChord());
|
|
|
|
UI_COMMAND(_OnlineDoc, "Online Documentation", "Go to the plugin's online documentation.", EUserInterfaceActionType::Button, FInputChord());
|
|
UI_COMMAND(_OnlineForum, "Online Forum", "Go to the plugin's online forum.", EUserInterfaceActionType::Button, FInputChord());
|
|
UI_COMMAND(_ReportBug, "Report a bug", "Report a bug for Houdini Engine for Unreal plugin.", EUserInterfaceActionType::Button, FInputChord());
|
|
|
|
UI_COMMAND(_CookAll, "Recook All", "Recooks all Houdini Assets Actors in the current level.", EUserInterfaceActionType::Button, FInputChord());
|
|
UI_COMMAND(_CookSelected, "Recook Selection", "Recooks selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::C, EModifierKey::Control | EModifierKey::Alt));
|
|
|
|
UI_COMMAND(_RebuildAll, "Rebuild All", "Rebuilds all Houdini Assets Actors in the current level.", EUserInterfaceActionType::Button, FInputChord());
|
|
UI_COMMAND(_RebuildSelected, "Rebuild Selection", "Rebuilds selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::R, EModifierKey::Control | EModifierKey::Alt));
|
|
|
|
UI_COMMAND(_BakeAll, "Bake And Replace All Houdini Assets", "Bakes and replaces with blueprints all Houdini Assets in the scene.", EUserInterfaceActionType::Button, FInputChord());
|
|
UI_COMMAND(_BakeSelected, "Bake And Replace Selection", "Bakes and replaces with blueprints selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord(EKeys::B, EModifierKey::Control | EModifierKey::Alt));
|
|
|
|
UI_COMMAND(_RefineAll, "Refine all Houdini Proxy Meshes To Static Meshes", "Builds and replaces all Houdini proxy meshes with UStaticMesh instances.", EUserInterfaceActionType::Button, FInputChord());
|
|
UI_COMMAND(_RefineSelected, "Refine selected Houdini Proxy Meshes To Static Meshes", "Builds and replaces selected Houdini proxy meshes with UStaticMesh instances.", EUserInterfaceActionType::Button, FInputChord());
|
|
|
|
UI_COMMAND(_CleanUpTempFolder, "Clean Houdini Engine Temp Folder", "Deletes the unused temporary files in the Temporary Cook Folder.", EUserInterfaceActionType::Button, FInputChord());
|
|
UI_COMMAND(_PauseAssetCooking, "Pause Houdini Engine Cooking", "When activated, prevents Houdini Engine from cooking assets until unpaused.", EUserInterfaceActionType::Check, FInputChord(EKeys::P, EModifierKey::Control | EModifierKey::Alt));
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::SaveHIPFile()
|
|
{
|
|
if (!FHoudiniEngine::IsInitialized() || FHoudiniEngine::Get().GetSession() == nullptr)
|
|
{
|
|
HOUDINI_LOG_ERROR(TEXT("Cannot save the Houdini scene, the Houdini Engine session hasn't been started."));
|
|
return;
|
|
}
|
|
|
|
IDesktopPlatform * DesktopPlatform = FDesktopPlatformModule::Get();
|
|
if (!DesktopPlatform || !FHoudiniEngineUtils::IsInitialized())
|
|
return;
|
|
|
|
TArray< FString > SaveFilenames;
|
|
bool bSaved = false;
|
|
void * ParentWindowWindowHandle = NULL;
|
|
|
|
IMainFrameModule & MainFrameModule = FModuleManager::LoadModuleChecked< IMainFrameModule >(TEXT("MainFrame"));
|
|
const TSharedPtr< SWindow > & MainFrameParentWindow = MainFrameModule.GetParentWindow();
|
|
if (MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid())
|
|
ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle();
|
|
|
|
bSaved = DesktopPlatform->SaveFileDialog(
|
|
ParentWindowWindowHandle,
|
|
NSLOCTEXT("SaveHIPFile", "SaveHIPFile", "Saves a .hip file of the current Houdini scene.").ToString(),
|
|
*(FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_EXPORT)),
|
|
TEXT(""),
|
|
TEXT("Houdini HIP file|*.hip"),
|
|
EFileDialogFlags::None,
|
|
SaveFilenames);
|
|
|
|
if (bSaved && SaveFilenames.Num())
|
|
{
|
|
// Add a slate notification
|
|
FString Notification = TEXT("Saving internal Houdini scene...");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// ... and a log message
|
|
HOUDINI_LOG_MESSAGE(TEXT("Saved Houdini scene to %s"), *SaveFilenames[0]);
|
|
|
|
// Get first path.
|
|
std::string HIPPathConverted(TCHAR_TO_UTF8(*SaveFilenames[0]));
|
|
|
|
// Save HIP file through Engine.
|
|
FHoudiniApi::SaveHIPFile(FHoudiniEngine::Get().GetSession(), HIPPathConverted.c_str(), false);
|
|
}
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::OpenInHoudini()
|
|
{
|
|
if(!FHoudiniEngine::IsInitialized() || FHoudiniEngine::Get().GetSession() == nullptr)
|
|
{
|
|
HOUDINI_LOG_ERROR(TEXT("Cannot open the scene in Houdini, the Houdini Engine session hasn't been started."));
|
|
return;
|
|
}
|
|
|
|
// First, saves the current scene as a hip file
|
|
// Creates a proper temporary file name
|
|
FString UserTempPath = FPaths::CreateTempFilename(
|
|
FPlatformProcess::UserTempDir(),
|
|
TEXT("HoudiniEngine"), TEXT(".hip"));
|
|
|
|
// Save HIP file through Engine.
|
|
std::string TempPathConverted(TCHAR_TO_UTF8(*UserTempPath));
|
|
FHoudiniApi::SaveHIPFile(
|
|
FHoudiniEngine::Get().GetSession(),
|
|
TempPathConverted.c_str(), false);
|
|
|
|
if (!FPaths::FileExists(UserTempPath))
|
|
return;
|
|
|
|
// Add a slate notification
|
|
FString Notification = TEXT("Opening scene in Houdini...");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// Add quotes to the path to avoid issues with spaces
|
|
UserTempPath = TEXT("\"") + UserTempPath + TEXT("\"");
|
|
|
|
// Then open the hip file in Houdini
|
|
FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation();
|
|
FString HoudiniExecutable = FHoudiniEngine::Get().GetHoudiniExecutable();
|
|
FString HoudiniLocation = LibHAPILocation + TEXT("//") + HoudiniExecutable;
|
|
|
|
FProcHandle ProcHandle = FPlatformProcess::CreateProc(
|
|
*HoudiniLocation,
|
|
*UserTempPath,
|
|
true, false, false,
|
|
nullptr, 0,
|
|
FPlatformProcess::UserTempDir(),
|
|
nullptr, nullptr);
|
|
|
|
if (!ProcHandle.IsValid())
|
|
{
|
|
// Try with the steam version executable instead
|
|
HoudiniLocation = LibHAPILocation + TEXT("//hindie.steam");
|
|
|
|
ProcHandle = FPlatformProcess::CreateProc(
|
|
*HoudiniLocation,
|
|
*UserTempPath,
|
|
true, false, false,
|
|
nullptr, 0,
|
|
FPlatformProcess::UserTempDir(),
|
|
nullptr, nullptr);
|
|
|
|
if (!ProcHandle.IsValid())
|
|
{
|
|
HOUDINI_LOG_ERROR(TEXT("Failed to open scene in Houdini."));
|
|
}
|
|
}
|
|
|
|
// ... and a log message
|
|
HOUDINI_LOG_MESSAGE(TEXT("Opened scene in Houdini."));
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::ReportBug()
|
|
{
|
|
FPlatformProcess::LaunchURL(HAPI_UNREAL_BUG_REPORT_URL, nullptr, nullptr);
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::ShowInstallInfo()
|
|
{
|
|
// TODO
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::ShowPluginSettings()
|
|
{
|
|
FModuleManager::LoadModuleChecked<ISettingsModule>("Settings").ShowViewer(FName("Project"), FName("Plugins"), FName("HoudiniEngine"));
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::OnlineDocumentation()
|
|
{
|
|
FPlatformProcess::LaunchURL(HAPI_UNREAL_ONLINE_DOC_URL, nullptr, nullptr);
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::OnlineForum()
|
|
{
|
|
FPlatformProcess::LaunchURL(HAPI_UNREAL_ONLINE_FORUM_URL, nullptr, nullptr);
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::CleanUpTempFolder()
|
|
{
|
|
// TODO: Improve me! slow now that we also have SM saved in the temp directory
|
|
// Due to the ref, we probably iterate a little too much, and should maybe do passes following the order of refs:
|
|
// mesh first, then materials, then textures.
|
|
// have a look at UWrangleContentCommandlet as well
|
|
|
|
// Add a slate notification
|
|
FString Notification = TEXT("Cleaning up Houdini Engine temporary folder...");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
GWarn->BeginSlowTask(LOCTEXT("CleanUpTemp", "Cleaning up the Houdini Engine Temp Folder"), false, false);
|
|
|
|
// Get the default temp cook folder
|
|
FString TempCookFolder = FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder();
|
|
|
|
TArray<FString> TempCookFolders;
|
|
TempCookFolders.Add(FHoudiniEngineRuntime::Get().GetDefaultTemporaryCookFolder());
|
|
for (TObjectIterator<UHoudiniAssetComponent> It; It; ++It)
|
|
{
|
|
FString CookFolder = It->TemporaryCookFolder.Path;
|
|
if (CookFolder.IsEmpty())
|
|
continue;
|
|
|
|
TempCookFolders.AddUnique(CookFolder);
|
|
}
|
|
|
|
// The Asset registry will help us finding if the content of the asset is referenced
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
|
|
|
int32 DeletedCount = 0;
|
|
bool bDidDeleteAsset = true;
|
|
while (bDidDeleteAsset)
|
|
{
|
|
// To correctly clean the temp folder, we need to iterate multiple times, because some of the temp assets
|
|
// might be referenced by other temp assets.. (ie Textures are referenced by Materials)
|
|
// We'll stop looking for assets to delete when no deletion occured.
|
|
bDidDeleteAsset = false;
|
|
|
|
TArray<FAssetData> AssetDataList;
|
|
for (auto& TempFolder : TempCookFolders)
|
|
{
|
|
// The Object library will list all UObjects found in the TempFolder
|
|
auto ObjectLibrary = UObjectLibrary::CreateLibrary(UObject::StaticClass(), false, true);
|
|
ObjectLibrary->LoadAssetDataFromPath(TempFolder);
|
|
|
|
// Get all the assets found in the TEMP folder
|
|
TArray<FAssetData> CurrentAssetDataList;
|
|
ObjectLibrary->GetAssetDataList(CurrentAssetDataList);
|
|
|
|
AssetDataList.Append(CurrentAssetDataList);
|
|
}
|
|
|
|
// All the assets we're going to delete
|
|
TArray<FAssetData> AssetDataToDelete;
|
|
for (FAssetData Data : AssetDataList)
|
|
{
|
|
UPackage* CurrentPackage = Data.GetPackage();
|
|
if (!CurrentPackage || CurrentPackage->IsPendingKill())
|
|
continue;
|
|
|
|
// Do not try to delete the package if it's referenced anywhere
|
|
TArray<FName> ReferenceNames;
|
|
AssetRegistryModule.Get().GetReferencers(CurrentPackage->GetFName(), ReferenceNames, EAssetRegistryDependencyType::All);
|
|
if (ReferenceNames.Num() > 0)
|
|
continue;
|
|
|
|
bool bAssetDataSafeToDelete = true;
|
|
TArray<FAssetData> AssetsInPackage;
|
|
AssetRegistryModule.Get().GetAssetsByPackageName(CurrentPackage->GetFName(), AssetsInPackage);
|
|
for (const auto& AssetInfo : AssetsInPackage)
|
|
{
|
|
// Check if the objects contained in the package are referenced by something that won't be garbage collected (*including* the undo buffer)
|
|
UObject* AssetInPackage = AssetInfo.GetAsset();
|
|
if (!AssetInPackage || AssetInPackage->IsPendingKill())
|
|
continue;
|
|
|
|
FReferencerInformationList ReferencesIncludingUndo;
|
|
bool bReferencedInMemoryOrUndoStack = IsReferenced(AssetInPackage, GARBAGE_COLLECTION_KEEPFLAGS, EInternalObjectFlags::GarbageCollectionKeepFlags, true, &ReferencesIncludingUndo);
|
|
if (!bReferencedInMemoryOrUndoStack)
|
|
continue;
|
|
|
|
// We do have external references, check if the external references are in our ObjectToDelete list
|
|
// If they are, we can delete the asset because its references are going to be deleted as well.
|
|
for (auto ExtRef : ReferencesIncludingUndo.ExternalReferences)
|
|
{
|
|
UObject* Outer = ExtRef.Referencer->GetOuter();
|
|
if (!Outer || Outer->IsPendingKill())
|
|
continue;
|
|
|
|
bool bOuterFound = false;
|
|
for (auto DataToDelete : AssetDataToDelete)
|
|
{
|
|
if (DataToDelete.GetPackage() == Outer)
|
|
{
|
|
bOuterFound = true;
|
|
break;
|
|
}
|
|
else if (DataToDelete.GetAsset() == Outer)
|
|
{
|
|
bOuterFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// We have at least one reference that's not going to be deleted, we have to keep the asset
|
|
if (!bOuterFound)
|
|
{
|
|
bAssetDataSafeToDelete = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bAssetDataSafeToDelete)
|
|
AssetDataToDelete.Add(Data);
|
|
}
|
|
|
|
// Nothing to delete
|
|
if (AssetDataToDelete.Num() <= 0)
|
|
break;
|
|
|
|
int32 CurrentDeleted = ObjectTools::DeleteAssets(AssetDataToDelete, false);
|
|
|
|
if (CurrentDeleted > 0)
|
|
{
|
|
DeletedCount += CurrentDeleted;
|
|
bDidDeleteAsset = true;
|
|
}
|
|
}
|
|
|
|
|
|
// Now, go through all the directories in the temp directories and delete all the empty ones
|
|
IFileManager& FM = IFileManager::Get();
|
|
// Lambda that parses a directory recursively and returns true if it is empty
|
|
auto IsEmptyFolder = [&FM](FString PathToDeleteOnDisk)
|
|
{
|
|
struct FEmptyFolderVisitor : public IPlatformFile::FDirectoryVisitor
|
|
{
|
|
bool bIsEmpty;
|
|
FEmptyFolderVisitor()
|
|
: bIsEmpty(true)
|
|
{
|
|
}
|
|
|
|
virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override
|
|
{
|
|
if (!bIsDirectory)
|
|
{
|
|
bIsEmpty = false;
|
|
return false; // abort searching
|
|
}
|
|
|
|
return true; // continue searching
|
|
}
|
|
};
|
|
|
|
// Look for files on disk in case the folder contains things not tracked by the asset registry
|
|
FEmptyFolderVisitor EmptyFolderVisitor;
|
|
IFileManager::Get().IterateDirectoryRecursively(*PathToDeleteOnDisk, EmptyFolderVisitor);
|
|
return EmptyFolderVisitor.bIsEmpty;
|
|
};
|
|
|
|
// Iterates on all the temporary cook directories recursively,
|
|
// And keep not of all the empty directories
|
|
FString TempCookPathOnDisk;
|
|
TArray<FString> FoldersToDelete;
|
|
if (FPackageName::TryConvertLongPackageNameToFilename(TempCookFolder, TempCookPathOnDisk))
|
|
{
|
|
FM.IterateDirectoryRecursively(*TempCookPathOnDisk, [&FM, &FoldersToDelete, &IsEmptyFolder](const TCHAR* InFilenameOrDirectory, const bool InIsDirectory) -> bool
|
|
{
|
|
// Skip Files
|
|
if (!InIsDirectory)
|
|
return true;
|
|
|
|
FString CurrentDirectoryPath = FString(InFilenameOrDirectory);
|
|
if (IsEmptyFolder(CurrentDirectoryPath))
|
|
FoldersToDelete.Add(CurrentDirectoryPath);
|
|
|
|
// keep iterating
|
|
return true;
|
|
});
|
|
}
|
|
|
|
int32 DeletedDirectories = 0;
|
|
for (auto& FolderPath : FoldersToDelete)
|
|
{
|
|
FString PathToDelete;
|
|
if (!FPackageName::TryConvertFilenameToLongPackageName(FolderPath, PathToDelete))
|
|
continue;
|
|
|
|
if (IFileManager::Get().DeleteDirectory(*FolderPath, false, true))
|
|
{
|
|
AssetRegistryModule.Get().RemovePath(PathToDelete);
|
|
DeletedDirectories++;
|
|
}
|
|
}
|
|
|
|
GWarn->EndSlowTask();
|
|
|
|
// Add a slate notification
|
|
Notification = TEXT("Deleted ") + FString::FromInt(DeletedCount) + TEXT(" temporary files and ") + FString::FromInt(DeletedDirectories) + TEXT(" directories.");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// ... and a log message
|
|
HOUDINI_LOG_MESSAGE(TEXT("Deleted %d temporary files and %d directories."), DeletedCount, DeletedDirectories);
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::BakeAllAssets()
|
|
{
|
|
// Add a slate notification
|
|
FString Notification = TEXT("Baking all assets in the current level...");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// Bakes and replaces with blueprints all Houdini Assets in the current level
|
|
int32 BakedCount = 0;
|
|
for (TObjectIterator<UHoudiniAssetComponent> Itr; Itr; ++Itr)
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = *Itr;
|
|
if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())
|
|
{
|
|
HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - Invalid Houdini Asset Component"));
|
|
continue;
|
|
}
|
|
|
|
if (!HoudiniAssetComponent->IsComponentValid())
|
|
{
|
|
FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName();
|
|
if (AssetName != "Default__HoudiniAssetActor")
|
|
HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - %s is invalid"), *AssetName);
|
|
continue;
|
|
}
|
|
|
|
// If component is not cooking or instancing, we can bake blueprint.
|
|
if (HoudiniAssetComponent->IsInstantiatingOrCooking())
|
|
{
|
|
FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName();
|
|
HOUDINI_LOG_ERROR(TEXT("Failed to bake a Houdini Asset in the scene! - %s is actively instantiating or cooking"), *AssetName);
|
|
continue;
|
|
}
|
|
|
|
bool bSuccess = false;
|
|
bool BakeToBlueprints = true;
|
|
if (BakeToBlueprints)
|
|
{
|
|
// if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr)
|
|
// bSuccess = true;
|
|
FHoudiniEngineOutputStats BakeStats;
|
|
TArray<UPackage*> PackagesToSave;
|
|
TArray<UBlueprint*> Blueprints;
|
|
const bool bInReplaceAssets = true;
|
|
bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(HoudiniAssetComponent, bInReplaceAssets, HoudiniAssetComponent->bRecenterBakedActors, BakeStats, Blueprints, PackagesToSave);
|
|
FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave);
|
|
|
|
if (bSuccess)
|
|
{
|
|
// Instantiate blueprints in component's level, then remove houdini asset actor
|
|
bSuccess = false;
|
|
ULevel* Level = HoudiniAssetComponent->GetComponentLevel();
|
|
if (IsValid(Level))
|
|
{
|
|
UWorld* World = Level->GetWorld();
|
|
if (IsValid(World))
|
|
{
|
|
FActorSpawnParameters SpawnParams;
|
|
SpawnParams.OverrideLevel = Level;
|
|
FTransform Transform = HoudiniAssetComponent->GetComponentTransform();
|
|
for (UBlueprint* Blueprint : Blueprints)
|
|
{
|
|
if (!IsValid(Blueprint))
|
|
continue;
|
|
World->SpawnActor(Blueprint->GetBlueprintClass(), &Transform, SpawnParams);
|
|
}
|
|
|
|
FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent);
|
|
bSuccess = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO: this used to have a way to not select in v1
|
|
// if (FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(HoudiniAssetComponent))
|
|
// bSuccess = true;
|
|
const bool bReplaceActors = true;
|
|
const bool bReplaceAssets = true;
|
|
if (FHoudiniEngineBakeUtils::BakeHoudiniActorToActors(HoudiniAssetComponent, bReplaceActors, bReplaceAssets, HoudiniAssetComponent->bRecenterBakedActors))
|
|
{
|
|
bSuccess = true;
|
|
FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent);
|
|
}
|
|
}
|
|
|
|
if (bSuccess)
|
|
BakedCount++;
|
|
}
|
|
|
|
// Add a slate notification
|
|
Notification = TEXT("Baked ") + FString::FromInt(BakedCount) + TEXT(" Houdini assets.");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// ... and a log message
|
|
HOUDINI_LOG_MESSAGE(TEXT("Baked all %d Houdini assets in the current level."), BakedCount);
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::PauseAssetCooking()
|
|
{
|
|
// Revert the global flag
|
|
bool bCurrentCookingEnabled = !FHoudiniEngine::Get().IsCookingEnabled();
|
|
FHoudiniEngine::Get().SetCookingEnabled(bCurrentCookingEnabled);
|
|
|
|
// We need to refresh UI when pause cooking. Set refresh UI counter to be the number of current registered HACs.
|
|
if (!bCurrentCookingEnabled)
|
|
FHoudiniEngine::Get().SetUIRefreshCountWhenPauseCooking( FHoudiniEngineRuntime::Get().GetRegisteredHoudiniComponentCount() );
|
|
|
|
// Add a slate notification
|
|
FString Notification = TEXT("Houdini Engine cooking paused");
|
|
if (bCurrentCookingEnabled)
|
|
Notification = TEXT("Houdini Engine cooking resumed");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// ... and a log message
|
|
if (bCurrentCookingEnabled)
|
|
HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine cooking resumed."));
|
|
else
|
|
HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine cooking paused."));
|
|
|
|
if (!bCurrentCookingEnabled)
|
|
return;
|
|
|
|
/*
|
|
// If we are unpausing, tick each asset component to "update" them
|
|
for (TObjectIterator<UHoudiniAssetComponent> Itr; Itr; ++Itr)
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = *Itr;
|
|
if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() || !HoudiniAssetComponent->IsValidLowLevel())
|
|
{
|
|
HOUDINI_LOG_ERROR(TEXT("Failed to cook a Houdini Asset in the scene!"));
|
|
continue;
|
|
}
|
|
|
|
HoudiniAssetComponent->StartHoudiniTicking();
|
|
}
|
|
*/
|
|
}
|
|
|
|
bool
|
|
FHoudiniEngineCommands::IsAssetCookingPaused()
|
|
{
|
|
return !FHoudiniEngine::Get().IsCookingEnabled();
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::RecookSelection()
|
|
{
|
|
// Get current world selection
|
|
TArray<UObject*> WorldSelection;
|
|
int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true);
|
|
if (SelectedHoudiniAssets <= 0)
|
|
{
|
|
HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner"));
|
|
return;
|
|
}
|
|
|
|
// Add a slate notification
|
|
FString Notification = TEXT("Cooking selected Houdini Assets...");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// Iterates over the selection and cook the assets if they're in a valid state
|
|
int32 CookedCount = 0;
|
|
for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++)
|
|
{
|
|
AHoudiniAssetActor * HoudiniAssetActor = Cast<AHoudiniAssetActor>(WorldSelection[Idx]);
|
|
if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill())
|
|
continue;
|
|
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent();
|
|
if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())
|
|
continue;
|
|
|
|
HoudiniAssetComponent->MarkAsNeedCook();
|
|
CookedCount++;
|
|
}
|
|
|
|
// Add a slate notification
|
|
Notification = TEXT("Re-cooking ") + FString::FromInt(CookedCount) + TEXT(" Houdini assets.");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// ... and a log message
|
|
HOUDINI_LOG_MESSAGE(TEXT("Re-cooking %d selected Houdini assets."), CookedCount);
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::RecookAllAssets()
|
|
{
|
|
// Add a slate notification
|
|
FString Notification = TEXT("Cooking all assets in the current level...");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// Bakes and replaces with blueprints all Houdini Assets in the current level
|
|
int32 CookedCount = 0;
|
|
for (TObjectIterator<UHoudiniAssetComponent> Itr; Itr; ++Itr)
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = *Itr;
|
|
if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())
|
|
continue;
|
|
|
|
HoudiniAssetComponent->MarkAsNeedCook();
|
|
CookedCount++;
|
|
}
|
|
|
|
// Add a slate notification
|
|
Notification = TEXT("Re-cooked ") + FString::FromInt(CookedCount) + TEXT(" Houdini assets.");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// ... and a log message
|
|
HOUDINI_LOG_MESSAGE(TEXT("Re-cooked %d Houdini assets in the current level."), CookedCount);
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::RebuildAllAssets()
|
|
{
|
|
// Add a slate notification
|
|
FString Notification = TEXT("Re-building all assets in the current level...");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// Bakes and replaces with blueprints all Houdini Assets in the current level
|
|
int32 RebuiltCount = 0;
|
|
for (TObjectIterator<UHoudiniAssetComponent> Itr; Itr; ++Itr)
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = *Itr;
|
|
if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())
|
|
continue;
|
|
|
|
HoudiniAssetComponent->MarkAsNeedRebuild();
|
|
RebuiltCount++;
|
|
}
|
|
|
|
// Add a slate notification
|
|
Notification = TEXT("Rebuilt ") + FString::FromInt(RebuiltCount) + TEXT(" Houdini assets.");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// ... and a log message
|
|
HOUDINI_LOG_MESSAGE(TEXT("Rebuilt %d Houdini assets in the current level."), RebuiltCount);
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::RebuildSelection()
|
|
{
|
|
// Get current world selection
|
|
TArray<UObject*> WorldSelection;
|
|
int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true);
|
|
if (SelectedHoudiniAssets <= 0)
|
|
{
|
|
HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner"));
|
|
return;
|
|
}
|
|
|
|
// Add a slate notification
|
|
FString Notification = TEXT("Rebuilding selected Houdini Assets...");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// Iterates over the selection and rebuilds the assets if they're in a valid state
|
|
int32 RebuiltCount = 0;
|
|
for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++)
|
|
{
|
|
AHoudiniAssetActor * HoudiniAssetActor = Cast<AHoudiniAssetActor>(WorldSelection[Idx]);
|
|
if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill())
|
|
continue;
|
|
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent();
|
|
if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())// || !HoudiniAssetComponent->IsComponentValid())
|
|
continue;
|
|
|
|
HoudiniAssetComponent->MarkAsNeedRebuild();
|
|
RebuiltCount++;
|
|
}
|
|
|
|
// Add a slate notification
|
|
Notification = TEXT("Rebuilt ") + FString::FromInt(RebuiltCount) + TEXT(" Houdini assets.");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// ... and a log message
|
|
HOUDINI_LOG_MESSAGE(TEXT("Rebuilt %d selected Houdini assets."), RebuiltCount);
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::BakeSelection()
|
|
{
|
|
// Get current world selection
|
|
TArray<UObject*> WorldSelection;
|
|
int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true);
|
|
if (SelectedHoudiniAssets <= 0)
|
|
{
|
|
HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner"));
|
|
return;
|
|
}
|
|
|
|
// Add a slate notification
|
|
FString Notification = TEXT("Baking selected Houdini Asset Actors in the current level...");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// Iterates over the selection and rebuilds the assets if they're in a valid state
|
|
int32 BakedCount = 0;
|
|
for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++)
|
|
{
|
|
AHoudiniAssetActor * HoudiniAssetActor = Cast<AHoudiniAssetActor>(WorldSelection[Idx]);
|
|
if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill())
|
|
continue;
|
|
|
|
UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent();
|
|
if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())
|
|
{
|
|
HOUDINI_LOG_ERROR(TEXT("Failed to export a Houdini Asset in the scene!"));
|
|
continue;
|
|
}
|
|
|
|
if (!HoudiniAssetComponent->IsComponentValid())
|
|
{
|
|
FString AssetName = HoudiniAssetComponent->GetOuter() ? HoudiniAssetComponent->GetOuter()->GetName() : HoudiniAssetComponent->GetName();
|
|
HOUDINI_LOG_ERROR(TEXT("Failed to export Houdini Asset: %s in the scene!"), *AssetName);
|
|
continue;
|
|
}
|
|
|
|
// If component is not cooking or instancing, we can bake blueprint.
|
|
if (!HoudiniAssetComponent->IsInstantiatingOrCooking())
|
|
{
|
|
// if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr)
|
|
// BakedCount++;
|
|
// if (FHoudiniEngineBakeUtils::ReplaceWithBlueprint(HoudiniAssetComponent) != nullptr)
|
|
// bSuccess = true;
|
|
FHoudiniEngineOutputStats BakeStats;
|
|
TArray<UPackage*> PackagesToSave;
|
|
TArray<UBlueprint*> Blueprints;
|
|
const bool bReplaceAssets = true;
|
|
const bool bSuccess = FHoudiniEngineBakeUtils::BakeBlueprints(HoudiniAssetComponent, bReplaceAssets, HoudiniAssetComponent->bRecenterBakedActors, BakeStats, Blueprints, PackagesToSave);
|
|
FHoudiniEngineBakeUtils::SaveBakedPackages(PackagesToSave);
|
|
|
|
if (bSuccess)
|
|
{
|
|
// Instantiate blueprints in component's level, then remove houdini asset actor
|
|
ULevel* Level = HoudiniAssetComponent->GetComponentLevel();
|
|
if (IsValid(Level))
|
|
{
|
|
UWorld* World = Level->GetWorld();
|
|
if (IsValid(World))
|
|
{
|
|
FActorSpawnParameters SpawnParams;
|
|
SpawnParams.OverrideLevel = Level;
|
|
FTransform Transform = HoudiniAssetComponent->GetComponentTransform();
|
|
for (UBlueprint* Blueprint : Blueprints)
|
|
{
|
|
if (!IsValid(Blueprint))
|
|
continue;
|
|
World->SpawnActor(Blueprint->GetBlueprintClass(), &Transform, SpawnParams);
|
|
}
|
|
|
|
FHoudiniEngineBakeUtils::DeleteBakedHoudiniAssetActor(HoudiniAssetComponent);
|
|
BakedCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add a slate notification
|
|
Notification = TEXT("Baked ") + FString::FromInt(BakedCount) + TEXT(" Houdini assets.");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// ... and a log message
|
|
HOUDINI_LOG_MESSAGE(TEXT("Baked all %d Houdini assets in the current level."), BakedCount);
|
|
}
|
|
|
|
// Recentre HoudiniAsset actors' pivots to their input / cooked static-mesh average centre.
|
|
void FHoudiniEngineCommands::RecentreSelection()
|
|
{
|
|
/*
|
|
#if WITH_EDITOR
|
|
//Get current world selection
|
|
TArray<UObject*> WorldSelection;
|
|
int32 SelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true);
|
|
if (SelectedHoudiniAssets <= 0)
|
|
{
|
|
HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner"));
|
|
return;
|
|
}
|
|
|
|
// Add a slate notification
|
|
FString Notification = TEXT("Recentering selected Houdini Assets...");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// Iterates over the selection and cook the assets if they're in a valid state
|
|
int32 RecentreCount = 0;
|
|
for (int32 Idx = 0; Idx < SelectedHoudiniAssets; Idx++)
|
|
{
|
|
AHoudiniAssetActor * HoudiniAssetActor = Cast<AHoudiniAssetActor>(WorldSelection[Idx]);
|
|
if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill())
|
|
continue;
|
|
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent();
|
|
if (!HoudiniAssetComponent || !HoudiniAssetComponent->IsComponentValid())
|
|
continue;
|
|
|
|
// Get the average centre of all the created Static Meshes
|
|
FVector AverageBoundsCentre = FVector::ZeroVector;
|
|
int32 NumBounds = 0;
|
|
const FVector CurrentLocation = HoudiniAssetComponent->GetComponentLocation();
|
|
{
|
|
//Check Static Meshes
|
|
TArray<UStaticMesh*> StaticMeshes;
|
|
StaticMeshes.Reserve(16);
|
|
HoudiniAssetComponent->GetAllUsedStaticMeshes(StaticMeshes);
|
|
|
|
//Get average centre of all the static meshes.
|
|
for (const UStaticMesh* pMesh : StaticMeshes)
|
|
{
|
|
if (!pMesh)
|
|
continue;
|
|
|
|
//to world space
|
|
AverageBoundsCentre += (pMesh->GetBounds().Origin + CurrentLocation);
|
|
NumBounds++;
|
|
}
|
|
}
|
|
|
|
//Check Inputs
|
|
if (0 == NumBounds)
|
|
{
|
|
const TArray< UHoudiniInput* >& AssetInputs = HoudiniAssetComponent->Inputs;
|
|
for (const UHoudiniInput* pInput : AssetInputs)
|
|
{
|
|
if (!pInput || pInput->IsPendingKill())
|
|
continue;
|
|
|
|
// to world space
|
|
FBox Bounds = pInput->GetInputBounds(CurrentLocation);
|
|
if (Bounds.IsValid)
|
|
{
|
|
AverageBoundsCentre += Bounds.GetCenter();
|
|
NumBounds++;
|
|
}
|
|
}
|
|
}
|
|
|
|
//if we have more than one, get the average centre
|
|
if (NumBounds > 1)
|
|
{
|
|
AverageBoundsCentre /= (float)NumBounds;
|
|
}
|
|
|
|
//if we need to move...
|
|
float fDist = FVector::DistSquared(CurrentLocation, AverageBoundsCentre);
|
|
if (NumBounds && fDist > 1.0f)
|
|
{
|
|
// Move actor to average centre and recook
|
|
// This will refresh the static mesh under the HoudiniAssestComponent ( undoing the translation ).
|
|
HoudiniAssetActor->SetActorLocation(AverageBoundsCentre, false, nullptr, ETeleportType::TeleportPhysics);
|
|
|
|
// Recook now the houdini-static-mesh has a new origin
|
|
HoudiniAssetComponent->StartTaskAssetCookingManual();
|
|
RecentreCount++;
|
|
}
|
|
}
|
|
|
|
if (RecentreCount)
|
|
{
|
|
// UE4 Editor doesn't refresh the translation-handles until they are re-selected, confusing the user, deselect the objects.
|
|
GEditor->SelectNone(true, false);
|
|
}
|
|
|
|
// Add a slate notification
|
|
Notification = TEXT("Re-centred ") + FString::FromInt(RecentreCount) + TEXT(" Houdini assets.");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// ... and a log message
|
|
HOUDINI_LOG_MESSAGE(TEXT("Re-centred %d selected Houdini assets."), RecentreCount);
|
|
|
|
#endif //WITH_EDITOR
|
|
*/
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::OpenSessionSync()
|
|
{
|
|
//if (!FHoudiniEngine::IsInitialized())
|
|
// return;
|
|
|
|
if (!FHoudiniEngine::Get().StopSession())
|
|
{
|
|
// StopSession returns false only if Houdini is not initialized
|
|
HOUDINI_LOG_ERROR(TEXT("Failed to start Session Sync - HAPI Not initialized"));
|
|
return;
|
|
}
|
|
|
|
// Get the runtime settings to get the session/type and settings
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault<UHoudiniRuntimeSettings>();
|
|
|
|
EHoudiniRuntimeSettingsSessionType SessionType = HoudiniRuntimeSettings->SessionType;
|
|
FString ServerPipeName = HoudiniRuntimeSettings->ServerPipeName;
|
|
int32 ServerPort = HoudiniRuntimeSettings->ServerPort;
|
|
|
|
FString SessionSyncArgs = TEXT("-hess=");
|
|
if (SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe)
|
|
{
|
|
// Add the -hess=pipe:hapi argument
|
|
SessionSyncArgs += TEXT("pipe:") + ServerPipeName;
|
|
}
|
|
else if (SessionType == EHoudiniRuntimeSettingsSessionType::HRSST_Socket)
|
|
{
|
|
// Add the -hess=port:9090 argument
|
|
SessionSyncArgs += TEXT("port:") + FString::FromInt(ServerPort);
|
|
}
|
|
else
|
|
{
|
|
// Invalid session type
|
|
HOUDINI_LOG_ERROR(TEXT("Failed to start Session Sync - Invalid session type"));
|
|
return;
|
|
}
|
|
|
|
// Add a slate notification
|
|
FString Notification = TEXT("Opening Houdini Session Sync...");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// ... and a log message
|
|
HOUDINI_LOG_MESSAGE(TEXT("Opening Houdini Session Sync."));
|
|
|
|
// Only launch Houdini in Session sync if we havent started it already!
|
|
FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle();
|
|
if (!FPlatformProcess::IsProcRunning(PreviousHESS))
|
|
{
|
|
// Start houdini with the -hess commandline args
|
|
const FString LibHAPILocation = FHoudiniEngine::Get().GetLibHAPILocation();
|
|
# if PLATFORM_MAC
|
|
const FString HoudiniExeLocationRelativeToLibHAPI = TEXT("/../Resources/bin");
|
|
# elif PLATFORM_LINUX
|
|
const FString HoudiniExeLocationRelativeToLibHAPI = TEXT("/../bin");
|
|
# elif PLATFORM_WINDOWS
|
|
const FString HoudiniExeLocationRelativeToLibHAPI;
|
|
# else
|
|
// Treat an unknown platform the same as Windows for now
|
|
const FString HoudiniExeLocationRelativeToLibHAPI;
|
|
# endif
|
|
|
|
FString HoudiniExecutable = FHoudiniEngine::Get().GetHoudiniExecutable();
|
|
FString HoudiniLocation = LibHAPILocation + HoudiniExeLocationRelativeToLibHAPI + TEXT("/") + HoudiniExecutable;
|
|
HOUDINI_LOG_MESSAGE(TEXT("Path to houdini executable: %s"), *HoudiniLocation);
|
|
FProcHandle HESSHandle = FPlatformProcess::CreateProc(
|
|
*HoudiniLocation,
|
|
*SessionSyncArgs,
|
|
true, false, false,
|
|
nullptr, 0,
|
|
FPlatformProcess::UserTempDir(),
|
|
nullptr, nullptr);
|
|
|
|
if (!HESSHandle.IsValid())
|
|
{
|
|
// Try with the steam version executable instead
|
|
HoudiniLocation = LibHAPILocation + HoudiniExeLocationRelativeToLibHAPI + TEXT("/hindie.steam");
|
|
HOUDINI_LOG_MESSAGE(TEXT("Path to hindie.steam executable: %s"), *HoudiniLocation);
|
|
|
|
HESSHandle = FPlatformProcess::CreateProc(
|
|
*HoudiniLocation,
|
|
*SessionSyncArgs,
|
|
true, false, false,
|
|
nullptr, 0,
|
|
FPlatformProcess::UserTempDir(),
|
|
nullptr, nullptr);
|
|
|
|
if (!HESSHandle.IsValid())
|
|
{
|
|
HOUDINI_LOG_ERROR(TEXT("Failed to launch Houdini in Session Sync mode."));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Keep track of the SessionSync ProcHandle
|
|
FHoudiniEngine::Get().SetHESSProcHandle(HESSHandle);
|
|
}
|
|
|
|
// Start an Async task to connect to Session Sync
|
|
Async(EAsyncExecution::TaskGraphMainThread, [SessionType, ServerPipeName, ServerPort]()
|
|
{
|
|
// Use a timeout to avoid waiting indefinitely for H to start in session sync mode
|
|
const double Timeout = 180.0; // 3min
|
|
const double StartTimestamp = FPlatformTime::Seconds();
|
|
|
|
FString ServerHost = TEXT("localhost");
|
|
while (!FHoudiniEngine::Get().SessionSyncConnect(SessionType, ServerPipeName, ServerHost, ServerPort))
|
|
{
|
|
// Houdini might not be done loading, sleep for one second
|
|
FPlatformProcess::Sleep(1);
|
|
|
|
// Check for the timeout
|
|
if (FPlatformTime::Seconds() - StartTimestamp > Timeout)
|
|
{
|
|
// ... and a log message
|
|
HOUDINI_LOG_ERROR(TEXT("Failed to start SessionSync - Timeout..."));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Initialize HAPI with this session
|
|
if (!FHoudiniEngine::Get().InitializeHAPISession())
|
|
{
|
|
FHoudiniEngine::Get().StopTicking();
|
|
return false;
|
|
}
|
|
|
|
// Notify all HACs that they need to instantiate in the new session
|
|
MarkAllHACsAsNeedInstantiation();
|
|
|
|
// Start ticking
|
|
FHoudiniEngine::Get().StartTicking();
|
|
|
|
// Add a slate notification
|
|
FString Notification = TEXT("Succesfully connected to Session Sync...");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// ... and a log message
|
|
HOUDINI_LOG_MESSAGE(TEXT("Succesfully connected to Session Sync..."));
|
|
|
|
return true;
|
|
});
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::CloseSessionSync()
|
|
{
|
|
if (!FHoudiniEngine::Get().StopSession())
|
|
{
|
|
// StopSession returns false only if Houdini is not initialized
|
|
HOUDINI_LOG_ERROR(TEXT("Failed to stop Session Sync - HAPI Not initialized"));
|
|
return;
|
|
}
|
|
|
|
// Add a slate notification
|
|
FString Notification = TEXT("Stopping Houdini Session Sync...");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// ... and a log message
|
|
HOUDINI_LOG_MESSAGE(TEXT("Stopping Houdini Session Sync."));
|
|
|
|
// Stop Houdini Session sync if it is still running!
|
|
FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle();
|
|
if (FPlatformProcess::IsProcRunning(PreviousHESS))
|
|
{
|
|
FPlatformProcess::TerminateProc(PreviousHESS, true);
|
|
}
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::SetViewportSync(const int32& ViewportSync)
|
|
{
|
|
if (ViewportSync == 1)
|
|
{
|
|
FHoudiniEngine::Get().SetSyncViewportEnabled(true);
|
|
FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(true);
|
|
FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(false);
|
|
}
|
|
else if (ViewportSync == 2)
|
|
{
|
|
FHoudiniEngine::Get().SetSyncViewportEnabled(true);
|
|
FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(false);
|
|
FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(true);
|
|
}
|
|
else if (ViewportSync == 3)
|
|
{
|
|
FHoudiniEngine::Get().SetSyncViewportEnabled(true);
|
|
FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(true);
|
|
FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(true);
|
|
}
|
|
else
|
|
{
|
|
FHoudiniEngine::Get().SetSyncViewportEnabled(false);
|
|
FHoudiniEngine::Get().SetSyncHoudiniViewportEnabled(false);
|
|
FHoudiniEngine::Get().SetSyncUnrealViewportEnabled(false);
|
|
}
|
|
}
|
|
|
|
int32
|
|
FHoudiniEngineCommands::GetViewportSync()
|
|
{
|
|
if(!FHoudiniEngine::Get().IsSyncViewportEnabled())
|
|
return 0;
|
|
|
|
bool bSyncH = FHoudiniEngine::Get().IsSyncHoudiniViewportEnabled();
|
|
bool bSyncU = FHoudiniEngine::Get().IsSyncUnrealViewportEnabled();
|
|
if (bSyncH && !bSyncU)
|
|
return 1;
|
|
else if (!bSyncH && bSyncU)
|
|
return 2;
|
|
else if (bSyncH && bSyncU)
|
|
return 3;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::RestartSession()
|
|
{
|
|
// Restart the current Houdini Engine Session
|
|
if (!FHoudiniEngine::Get().RestartSession())
|
|
return;
|
|
|
|
// We've successfully restarted the Houdini Engine session,
|
|
// We now need to notify all the HoudiniAssetComponent that they need to re instantiate
|
|
// themselves in the new Houdini engine session.
|
|
MarkAllHACsAsNeedInstantiation();
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::CreateSession()
|
|
{
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
|
|
|
|
// Restart the current Houdini Engine Session
|
|
if (!FHoudiniEngine::Get().CreateSession(HoudiniRuntimeSettings->SessionType))
|
|
return;
|
|
|
|
// We've successfully created the Houdini Engine session,
|
|
// We now need to notify all the HoudiniAssetComponent that they need to re instantiate
|
|
// themselves in the new Houdini engine session.
|
|
MarkAllHACsAsNeedInstantiation();
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::ConnectSession()
|
|
{
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
|
|
|
|
// Restart the current Houdini Engine Session
|
|
if (!FHoudiniEngine::Get().ConnectSession(HoudiniRuntimeSettings->SessionType))
|
|
return;
|
|
|
|
// We've successfully connected to a Houdini Engine session,
|
|
// We now need to notify all the HoudiniAssetComponent that they need to re instantiate
|
|
// themselves in the new Houdini engine session.
|
|
MarkAllHACsAsNeedInstantiation();
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::MarkAllHACsAsNeedInstantiation()
|
|
{
|
|
// Notify all the HoudiniAssetComponents that they need to re instantiate themselves in the new Houdini engine session.
|
|
for (TObjectIterator<UHoudiniAssetComponent> Itr; Itr; ++Itr)
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = *Itr;
|
|
if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())
|
|
continue;
|
|
|
|
HoudiniAssetComponent->MarkAsNeedInstantiation();
|
|
}
|
|
}
|
|
|
|
bool
|
|
FHoudiniEngineCommands::IsSessionValid()
|
|
{
|
|
return FHoudiniEngine::IsInitialized();
|
|
}
|
|
|
|
bool
|
|
FHoudiniEngineCommands::IsSessionSyncProcessValid()
|
|
{
|
|
// Only launch Houdini in Session sync if we havent started it already!
|
|
FProcHandle PreviousHESS = FHoudiniEngine::Get().GetHESSProcHandle();
|
|
return FPlatformProcess::IsProcRunning(PreviousHESS);
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::StopSession()
|
|
{
|
|
// Restart the current Houdini Engine Session
|
|
if (!FHoudiniEngine::Get().StopSession())
|
|
{
|
|
// StopSession returns false only if Houdini is not initialized
|
|
HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session - HAPI Not initialized"));
|
|
}
|
|
else
|
|
{
|
|
HOUDINI_LOG_MESSAGE(TEXT("Houdini Engine session stopped."));
|
|
}
|
|
}
|
|
|
|
EHoudiniProxyRefineRequestResult
|
|
FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshes(bool bOnlySelectedActors, bool bSilent, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE)
|
|
{
|
|
// Get current world selection
|
|
TArray<UObject*> WorldSelection;
|
|
int32 NumSelectedHoudiniAssets = 0;
|
|
if (bOnlySelectedActors)
|
|
{
|
|
NumSelectedHoudiniAssets = FHoudiniEngineEditorUtils::GetWorldSelection(WorldSelection, true);
|
|
if (NumSelectedHoudiniAssets <= 0)
|
|
{
|
|
HOUDINI_LOG_MESSAGE(TEXT("No Houdini Assets selected in the world outliner"));
|
|
return EHoudiniProxyRefineRequestResult::Invalid;
|
|
}
|
|
}
|
|
|
|
// Add a slate notification
|
|
FString Notification = TEXT("Refining Houdini proxy meshes to static meshes...");
|
|
// FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// First find the components that have meshes that we must refine
|
|
TArray<UHoudiniAssetComponent*> ComponentsToRefine;
|
|
TArray<UHoudiniAssetComponent*> ComponentsToCook;
|
|
// Components that would be candidates for refinement/cooking, but have errors
|
|
TArray<UHoudiniAssetComponent*> SkippedComponents;
|
|
if (bOnlySelectedActors)
|
|
{
|
|
for (int32 Index = 0; Index < NumSelectedHoudiniAssets; ++Index)
|
|
{
|
|
AHoudiniAssetActor * HoudiniAssetActor = Cast<AHoudiniAssetActor>(WorldSelection[Index]);
|
|
if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill())
|
|
continue;
|
|
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent();
|
|
if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())
|
|
continue;
|
|
|
|
// Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and
|
|
// flags passed to the function.
|
|
TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (TObjectIterator<UHoudiniAssetComponent> Itr; Itr; ++Itr)
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = *Itr;
|
|
if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())
|
|
continue;
|
|
|
|
if (bOnPreSaveWorld && OnPreSaveWorld && OnPreSaveWorld != HoudiniAssetComponent->GetWorld())
|
|
continue;
|
|
|
|
// Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and
|
|
// flags passed to the function.
|
|
TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents);
|
|
}
|
|
}
|
|
|
|
return RefineTriagedHoudiniProxyMesehesToStaticMeshes(
|
|
ComponentsToRefine,
|
|
ComponentsToCook,
|
|
SkippedComponents,
|
|
bSilent,
|
|
bRefineAll,
|
|
bOnPreSaveWorld,
|
|
OnPreSaveWorld,
|
|
bOnPreBeginPIE
|
|
);
|
|
}
|
|
|
|
EHoudiniProxyRefineRequestResult
|
|
FHoudiniEngineCommands::RefineHoudiniProxyMeshActorArrayToStaticMeshes(const TArray<AHoudiniAssetActor*>& InActorsToRefine, bool bSilent)
|
|
{
|
|
const bool bRefineAll = true;
|
|
const bool bOnPreSaveWorld = false;
|
|
UWorld* OnPreSaveWorld = nullptr;
|
|
const bool bOnPreBeginPIE = false;
|
|
|
|
// First find the components that have meshes that we must refine
|
|
TArray<UHoudiniAssetComponent*> ComponentsToRefine;
|
|
TArray<UHoudiniAssetComponent*> ComponentsToCook;
|
|
// Components that would be candidates for refinement/cooking, but have errors
|
|
TArray<UHoudiniAssetComponent*> SkippedComponents;
|
|
for (const AHoudiniAssetActor* HoudiniAssetActor : InActorsToRefine)
|
|
{
|
|
if (!HoudiniAssetActor || HoudiniAssetActor->IsPendingKill())
|
|
continue;
|
|
|
|
UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent();
|
|
if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())
|
|
continue;
|
|
|
|
// Check if we should consider this component for proxy mesh refinement or cooking, based on its settings and
|
|
// flags passed to the function.
|
|
TriageHoudiniAssetComponentsForProxyMeshRefinement(HoudiniAssetComponent, bRefineAll, bOnPreSaveWorld, OnPreSaveWorld, bOnPreBeginPIE, ComponentsToRefine, ComponentsToCook, SkippedComponents);
|
|
}
|
|
|
|
return RefineTriagedHoudiniProxyMesehesToStaticMeshes(
|
|
ComponentsToRefine,
|
|
ComponentsToCook,
|
|
SkippedComponents,
|
|
bSilent,
|
|
bRefineAll,
|
|
bOnPreSaveWorld,
|
|
OnPreSaveWorld,
|
|
bOnPreBeginPIE
|
|
);
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::StartPDGCommandlet()
|
|
{
|
|
FHoudiniEngine::Get().StartPDGCommandlet();
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::StopPDGCommandlet()
|
|
{
|
|
FHoudiniEngine::Get().StopPDGCommandlet();
|
|
}
|
|
|
|
bool
|
|
FHoudiniEngineCommands::IsPDGCommandletRunningOrConnected()
|
|
{
|
|
return FHoudiniEngine::Get().IsPDGCommandletRunningOrConnected();
|
|
}
|
|
|
|
bool
|
|
FHoudiniEngineCommands::IsPDGCommandletEnabled()
|
|
{
|
|
const UHoudiniRuntimeSettings* const Settings = GetDefault<UHoudiniRuntimeSettings>();
|
|
if (IsValid(Settings))
|
|
{
|
|
return Settings->bPDGAsyncCommandletImportEnabled;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
FHoudiniEngineCommands::SetPDGCommandletEnabled(bool InEnabled)
|
|
{
|
|
UHoudiniRuntimeSettings* const Settings = GetMutableDefault<UHoudiniRuntimeSettings>();
|
|
if (IsValid(Settings))
|
|
{
|
|
Settings->bPDGAsyncCommandletImportEnabled = InEnabled;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::TriageHoudiniAssetComponentsForProxyMeshRefinement(UHoudiniAssetComponent* InHAC, bool bRefineAll, bool bOnPreSaveWorld, UWorld *OnPreSaveWorld, bool bOnPreBeginPIE, TArray<UHoudiniAssetComponent*> &OutToRefine, TArray<UHoudiniAssetComponent*> &OutToCook, TArray<UHoudiniAssetComponent*> &OutSkipped)
|
|
{
|
|
if (!InHAC || InHAC->IsPendingKill())
|
|
return;
|
|
|
|
// Make sure that the component's World and Owner are valid
|
|
AActor *Owner = InHAC->GetOwner();
|
|
if (!Owner || Owner->IsPendingKill())
|
|
return;
|
|
|
|
UWorld *World = InHAC->GetWorld();
|
|
if (!World || World->IsPendingKill())
|
|
return;
|
|
|
|
if (bOnPreSaveWorld && OnPreSaveWorld && OnPreSaveWorld != World)
|
|
return;
|
|
|
|
// Check if we should consider this component for proxy mesh refinement based on its settings and
|
|
// flags passed to the function
|
|
if (bRefineAll ||
|
|
(bOnPreSaveWorld && InHAC->IsProxyStaticMeshRefinementOnPreSaveWorldEnabled()) ||
|
|
(bOnPreBeginPIE && InHAC->IsProxyStaticMeshRefinementOnPreBeginPIEEnabled()))
|
|
{
|
|
TArray<UPackage*> ProxyMeshPackagesToSave;
|
|
TArray<UHoudiniAssetComponent*> ComponentsWithProxiesToSave;
|
|
|
|
if (InHAC->HasAnyCurrentProxyOutput())
|
|
{
|
|
// Get the state of the asset and check if it is cooked
|
|
// If it is not cook, request a cook. We can only build the UStaticMesh
|
|
// if the data from the cook is available
|
|
// If the state is not pre-cook, or None (cooked), then the state is invalid,
|
|
// log an error and skip the component
|
|
bool bNeedsRebuildOrDelete = false;
|
|
bool bUnsupportedState = false;
|
|
const bool bCookedDataAvailable = InHAC->IsHoudiniCookedDataAvailable(bNeedsRebuildOrDelete, bUnsupportedState);
|
|
if (bCookedDataAvailable)
|
|
{
|
|
OutToRefine.Add(InHAC);
|
|
ComponentsWithProxiesToSave.Add(InHAC);
|
|
}
|
|
else if (!bUnsupportedState && !bNeedsRebuildOrDelete)
|
|
{
|
|
InHAC->MarkAsNeedCook();
|
|
// Force the output of the cook to be directly created as a UStaticMesh and not a proxy
|
|
InHAC->SetNoProxyMeshNextCookRequested(true);
|
|
OutToCook.Add(InHAC);
|
|
ComponentsWithProxiesToSave.Add(InHAC);
|
|
}
|
|
else
|
|
{
|
|
OutSkipped.Add(InHAC);
|
|
const EHoudiniAssetState AssetState = InHAC->GetAssetState();
|
|
HOUDINI_LOG_ERROR(TEXT("Could not refine %s, the asset is in an unsupported state: %s"), *(InHAC->GetPathName()), *(UEnum::GetValueAsString(AssetState)));
|
|
}
|
|
}
|
|
else if (InHAC->HasAnyProxyOutput())
|
|
{
|
|
// If the HAC has non-current proxies, destroy them
|
|
// TODO: Make this its own command?
|
|
const uint32 NumOutputs = InHAC->GetNumOutputs();
|
|
for (uint32 Index = 0; Index < NumOutputs; ++Index)
|
|
{
|
|
UHoudiniOutput *Output = InHAC->GetOutputAt(Index);
|
|
if (!Output || Output->IsPendingKill())
|
|
continue;
|
|
|
|
TMap<FHoudiniOutputObjectIdentifier, FHoudiniOutputObject>& OutputObjects = Output->GetOutputObjects();
|
|
for (auto& CurrentPair : OutputObjects)
|
|
{
|
|
FHoudiniOutputObject& CurrentOutputObject = CurrentPair.Value;
|
|
if (!CurrentOutputObject.bProxyIsCurrent)
|
|
{
|
|
// The proxy is not current, delete it and its component
|
|
USceneComponent* FoundProxyComponent = Cast<USceneComponent>(CurrentOutputObject.ProxyComponent);
|
|
if (FoundProxyComponent && !FoundProxyComponent->IsPendingKill())
|
|
{
|
|
// Remove from the HoudiniAssetActor
|
|
if (FoundProxyComponent->GetOwner())
|
|
FoundProxyComponent->GetOwner()->RemoveOwnedComponent(FoundProxyComponent);
|
|
|
|
FoundProxyComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform);
|
|
FoundProxyComponent->UnregisterComponent();
|
|
FoundProxyComponent->DestroyComponent();
|
|
}
|
|
|
|
UObject* ProxyObject = CurrentOutputObject.ProxyObject;
|
|
if (!ProxyObject || ProxyObject->IsPendingKill())
|
|
continue;
|
|
|
|
ProxyObject->MarkPendingKill();
|
|
ProxyObject->MarkPackageDirty();
|
|
UPackage* const Package = ProxyObject->GetOutermost();//GetPackage();
|
|
if (IsValid(Package))
|
|
ProxyMeshPackagesToSave.Add(Package);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (UHoudiniAssetComponent* const HAC : ComponentsWithProxiesToSave)
|
|
{
|
|
const uint32 NumOutputs = HAC->GetNumOutputs();
|
|
for (uint32 Index = 0; Index < NumOutputs; ++Index)
|
|
{
|
|
UHoudiniOutput *Output = HAC->GetOutputAt(Index);
|
|
if (!Output || Output->IsPendingKill())
|
|
continue;
|
|
|
|
TMap<FHoudiniOutputObjectIdentifier, FHoudiniOutputObject>& OutputObjects = Output->GetOutputObjects();
|
|
for (auto& CurrentPair : OutputObjects)
|
|
{
|
|
FHoudiniOutputObject& CurrentOutputObject = CurrentPair.Value;
|
|
if (CurrentOutputObject.bProxyIsCurrent && CurrentOutputObject.ProxyObject)
|
|
{
|
|
UPackage* const Package = CurrentOutputObject.ProxyObject->GetOutermost();//GetPackage();
|
|
if (IsValid(Package) && Package->IsDirty())
|
|
ProxyMeshPackagesToSave.Add(Package);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ProxyMeshPackagesToSave.Num() > 0)
|
|
{
|
|
TryCollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS);
|
|
FEditorFileUtils::PromptForCheckoutAndSave(ProxyMeshPackagesToSave, true, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
EHoudiniProxyRefineRequestResult
|
|
FHoudiniEngineCommands::RefineTriagedHoudiniProxyMesehesToStaticMeshes(
|
|
const TArray<UHoudiniAssetComponent*>& InComponentsToRefine,
|
|
const TArray<UHoudiniAssetComponent*>& InComponentsToCook,
|
|
const TArray<UHoudiniAssetComponent*>& InSkippedComponents,
|
|
bool bInSilent,
|
|
bool bInRefineAll,
|
|
bool bInOnPreSaveWorld,
|
|
UWorld* InOnPreSaveWorld,
|
|
bool bInOnPrePIEBeginPlay)
|
|
{
|
|
// Slate notification text
|
|
FString Notification = TEXT("Refining Houdini proxy meshes to static meshes...");
|
|
|
|
const uint32 NumComponentsToCook = InComponentsToCook.Num();
|
|
const uint32 NumComponentsToRefine = InComponentsToRefine.Num();
|
|
const uint32 NumComponentsToProcess = NumComponentsToCook + NumComponentsToRefine;
|
|
|
|
TArray<UHoudiniAssetComponent*> SuccessfulComponents;
|
|
TArray<UHoudiniAssetComponent*> FailedComponents;
|
|
TArray<UHoudiniAssetComponent*> SkippedComponents(InSkippedComponents);
|
|
|
|
if (NumComponentsToProcess > 0)
|
|
{
|
|
// The task progress pointer is potentially going to be shared with a background thread and tasks
|
|
// on the main thread, so make it thread safe
|
|
TSharedPtr<FSlowTask, ESPMode::ThreadSafe> TaskProgress = MakeShareable(new FSlowTask((float)NumComponentsToProcess, FText::FromString(Notification)));
|
|
TaskProgress->Initialize();
|
|
if (!bInSilent)
|
|
TaskProgress->MakeDialog(/*bShowCancelButton=*/true);
|
|
|
|
// Iterate over the components for which we can build UStaticMesh, and build the meshes
|
|
bool bCancelled = false;
|
|
for (uint32 ComponentIndex = 0; ComponentIndex < NumComponentsToRefine; ++ComponentIndex)
|
|
{
|
|
UHoudiniAssetComponent* HoudiniAssetComponent = InComponentsToRefine[ComponentIndex];
|
|
TaskProgress->EnterProgressFrame(1.0f);
|
|
const bool bDestroyProxies = true;
|
|
FHoudiniOutputTranslator::BuildStaticMeshesOnHoudiniProxyMeshOutputs(HoudiniAssetComponent, bDestroyProxies);
|
|
|
|
SuccessfulComponents.Add(HoudiniAssetComponent);
|
|
|
|
bCancelled = TaskProgress->ShouldCancel();
|
|
if (bCancelled)
|
|
{
|
|
for (uint32 SkippedIndex = ComponentIndex + 1; SkippedIndex < NumComponentsToRefine; ++SkippedIndex)
|
|
{
|
|
SkippedComponents.Add(InComponentsToRefine[ComponentIndex]);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bCancelled && NumComponentsToCook > 0)
|
|
{
|
|
for (UHoudiniAssetComponent* const HAC : InComponentsToCook)
|
|
{
|
|
SkippedComponents.Add(HAC);
|
|
}
|
|
}
|
|
|
|
if (NumComponentsToCook > 0 && !bCancelled)
|
|
{
|
|
// Now use an async task to check on the progress of the cooking components
|
|
Async(EAsyncExecution::Thread, [InComponentsToCook, TaskProgress, NumComponentsToProcess, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents]() {
|
|
RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(
|
|
InComponentsToCook, TaskProgress, NumComponentsToProcess, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents);
|
|
});
|
|
|
|
// We have to wait for cook(s) before completing refinement
|
|
return EHoudiniProxyRefineRequestResult::PendingCooks;
|
|
}
|
|
else
|
|
{
|
|
RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(
|
|
NumComponentsToProcess, TaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents);
|
|
|
|
// We didn't have to cook anything, so refinement is complete.
|
|
return EHoudiniProxyRefineRequestResult::Refined;
|
|
}
|
|
}
|
|
|
|
// Nothing to refine
|
|
return EHoudiniProxyRefineRequestResult::None;
|
|
}
|
|
|
|
|
|
void
|
|
FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesWithCookInBackgroundThread(const TArray<UHoudiniAssetComponent*>& InComponentsToCook, TSharedPtr<FSlowTask, ESPMode::ThreadSafe> InTaskProgress, const uint32 InNumComponentsToProcess, bool bInOnPreSaveWorld, UWorld *InOnPreSaveWorld, const TArray<UHoudiniAssetComponent*> &InSuccessfulComponents, const TArray<UHoudiniAssetComponent*> &InFailedComponents, const TArray<UHoudiniAssetComponent*> &InSkippedComponents)
|
|
{
|
|
// Copy to a double linked list so that we can loop through
|
|
// to check progress of each component and remove it easily
|
|
// if it has completed/failed
|
|
TDoubleLinkedList<UHoudiniAssetComponent*> CookList;
|
|
for (UHoudiniAssetComponent *HAC : InComponentsToCook)
|
|
{
|
|
CookList.AddTail(HAC);
|
|
}
|
|
|
|
// Add the successfully cooked components to the incoming successful components (previously refined)
|
|
TArray<UHoudiniAssetComponent*> SuccessfulComponents(InSuccessfulComponents);
|
|
TArray<UHoudiniAssetComponent*> FailedComponents(InFailedComponents);
|
|
TArray<UHoudiniAssetComponent*> SkippedComponents(InSkippedComponents);
|
|
|
|
bool bCancelled = false;
|
|
uint32 NumFailedToCook = 0;
|
|
while (CookList.Num() > 0 && !bCancelled)
|
|
{
|
|
TDoubleLinkedList<UHoudiniAssetComponent*>::TDoubleLinkedListNode *Node = CookList.GetHead();
|
|
while (Node && !bCancelled)
|
|
{
|
|
TDoubleLinkedList<UHoudiniAssetComponent*>::TDoubleLinkedListNode *Next = Node->GetNextNode();
|
|
UHoudiniAssetComponent* HAC = Node->GetValue();
|
|
|
|
if (HAC && !HAC->IsPendingKill())
|
|
{
|
|
const EHoudiniAssetState State = HAC->GetAssetState();
|
|
const EHoudiniAssetStateResult ResultState = HAC->GetAssetStateResult();
|
|
bool bUpdateProgress = false;
|
|
if (State == EHoudiniAssetState::None)
|
|
{
|
|
// Cooked, count as success, remove node
|
|
CookList.RemoveNode(Node);
|
|
SuccessfulComponents.Add(HAC);
|
|
bUpdateProgress = true;
|
|
}
|
|
else if (ResultState != EHoudiniAssetStateResult::None && ResultState != EHoudiniAssetStateResult::Working)
|
|
{
|
|
// Failed, remove node
|
|
HOUDINI_LOG_ERROR(TEXT("Failed to cook %s to obtain static mesh."), *(HAC->GetPathName()));
|
|
CookList.RemoveNode(Node);
|
|
FailedComponents.Add(HAC);
|
|
bUpdateProgress = true;
|
|
NumFailedToCook++;
|
|
}
|
|
|
|
if (bUpdateProgress && InTaskProgress.IsValid())
|
|
{
|
|
// Update progress only on the main thread, and check for cancellation request
|
|
bCancelled = Async(EAsyncExecution::TaskGraphMainThread, [InTaskProgress]() {
|
|
InTaskProgress->EnterProgressFrame(1.0f);
|
|
return InTaskProgress->ShouldCancel();
|
|
}).Get();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SkippedComponents.Add(HAC);
|
|
CookList.RemoveNode(Node);
|
|
}
|
|
|
|
Node = Next;
|
|
}
|
|
FPlatformProcess::Sleep(0.01f);
|
|
}
|
|
|
|
if (bCancelled)
|
|
{
|
|
HOUDINI_LOG_WARNING(TEXT("Mesh refinement cancelled while waiting for %d components to cook."), CookList.Num());
|
|
// Mark any remaining HACs in the cook list as skipped
|
|
TDoubleLinkedList<UHoudiniAssetComponent*>::TDoubleLinkedListNode* Node = CookList.GetHead();
|
|
while (Node)
|
|
{
|
|
TDoubleLinkedList<UHoudiniAssetComponent*>::TDoubleLinkedListNode* const Next = Node->GetNextNode();
|
|
UHoudiniAssetComponent* HAC = Node->GetValue();
|
|
if (HAC)
|
|
SkippedComponents.Add(HAC);
|
|
CookList.RemoveNode(Node);
|
|
Node = Next;
|
|
}
|
|
}
|
|
|
|
// Cooking is done, or failed, display the notifications on the main thread
|
|
Async(EAsyncExecution::TaskGraphMainThread, [InNumComponentsToProcess, InTaskProgress, bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents]() {
|
|
RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(InNumComponentsToProcess, InTaskProgress.Get(), bCancelled, bInOnPreSaveWorld, InOnPreSaveWorld, SuccessfulComponents, FailedComponents, SkippedComponents);
|
|
});
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::RefineHoudiniProxyMeshesToStaticMeshesNotifyDone(const uint32 InNumTotalComponents, FSlowTask* const InTaskProgress, const bool bCancelled, const bool bOnPreSaveWorld, UWorld* const InOnPreSaveWorld, const TArray<UHoudiniAssetComponent*> &InSuccessfulComponents, const TArray<UHoudiniAssetComponent*> &InFailedComponents, const TArray<UHoudiniAssetComponent*> &InSkippedComponents)
|
|
{
|
|
FString Notification;
|
|
const uint32 NumSkippedComponents = InSkippedComponents.Num();
|
|
const uint32 NumFailedToCook = InFailedComponents.Num();
|
|
if (NumSkippedComponents + NumFailedToCook > 0)
|
|
{
|
|
if (bCancelled)
|
|
{
|
|
Notification = FString::Printf(TEXT("Refinement cancelled after completing %d / %d components. The remaining components were skipped, in an invalid state, or could not be cooked. See the log for details."), NumSkippedComponents + NumFailedToCook, InNumTotalComponents);
|
|
}
|
|
else
|
|
{
|
|
Notification = FString::Printf(TEXT("Failed to refine %d / %d components, the components were in an invalid state, and were either not cooked or could not be cooked. See the log for details."), NumSkippedComponents + NumFailedToCook, InNumTotalComponents);
|
|
}
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
HOUDINI_LOG_ERROR(TEXT("%s"), *Notification);
|
|
}
|
|
else if (InNumTotalComponents > 0)
|
|
{
|
|
Notification = TEXT("Done: Refining Houdini proxy meshes to static meshes.");
|
|
// FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
HOUDINI_LOG_MESSAGE(TEXT("%s"), *Notification);
|
|
}
|
|
if (InTaskProgress)
|
|
{
|
|
InTaskProgress->Destroy();
|
|
}
|
|
if (bOnPreSaveWorld && InSuccessfulComponents.Num() > 0)
|
|
{
|
|
FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineCommands::GetOnPostSaveWorldRefineProxyMeshesHandle();
|
|
if (OnPostSaveWorldHandle.IsValid())
|
|
{
|
|
if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle))
|
|
OnPostSaveWorldHandle.Reset();
|
|
}
|
|
|
|
// Save the dirty static meshes in InSuccessfulComponents OnPostSaveWorld
|
|
// TODO: Remove? This may not be necessary now as we save all dirty temporary cook data in PostSaveWorld() already (Static Meshes, Materials...)
|
|
OnPostSaveWorldHandle = FEditorDelegates::PostSaveWorld.AddLambda([InSuccessfulComponents, bOnPreSaveWorld, InOnPreSaveWorld](uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess) {
|
|
if (bOnPreSaveWorld && InOnPreSaveWorld && InOnPreSaveWorld != InWorld)
|
|
return;
|
|
|
|
RefineProxyMeshesHandleOnPostSaveWorld(InSuccessfulComponents, InSaveFlags, InWorld, bInSuccess);
|
|
|
|
FDelegateHandle& OnPostSaveWorldHandle = FHoudiniEngineCommands::GetOnPostSaveWorldRefineProxyMeshesHandle();
|
|
if (OnPostSaveWorldHandle.IsValid())
|
|
{
|
|
if (FEditorDelegates::PostSaveWorld.Remove(OnPostSaveWorldHandle))
|
|
OnPostSaveWorldHandle.Reset();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Broadcast refinement result per HAC
|
|
for (UHoudiniAssetComponent* const HAC : InSuccessfulComponents)
|
|
{
|
|
if (OnHoudiniProxyMeshesRefinedDelegate.IsBound())
|
|
OnHoudiniProxyMeshesRefinedDelegate.Broadcast(HAC, EHoudiniProxyRefineResult::Success);
|
|
}
|
|
for (UHoudiniAssetComponent* const HAC : InFailedComponents)
|
|
{
|
|
if (OnHoudiniProxyMeshesRefinedDelegate.IsBound())
|
|
OnHoudiniProxyMeshesRefinedDelegate.Broadcast(HAC, EHoudiniProxyRefineResult::Failed);
|
|
}
|
|
for (UHoudiniAssetComponent* const HAC : InSkippedComponents)
|
|
{
|
|
if (OnHoudiniProxyMeshesRefinedDelegate.IsBound())
|
|
OnHoudiniProxyMeshesRefinedDelegate.Broadcast(HAC, EHoudiniProxyRefineResult::Skipped);
|
|
}
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::RefineProxyMeshesHandleOnPostSaveWorld(const TArray<UHoudiniAssetComponent*> &InSuccessfulComponents, uint32 InSaveFlags, UWorld* InWorld, bool bInSuccess)
|
|
{
|
|
TArray<UPackage*> PackagesToSave;
|
|
|
|
for (UHoudiniAssetComponent* HAC : InSuccessfulComponents)
|
|
{
|
|
if (!HAC || HAC->IsPendingKill())
|
|
continue;
|
|
|
|
const int32 NumOutputs = HAC->GetNumOutputs();
|
|
for (int32 Index = 0; Index < NumOutputs; ++Index)
|
|
{
|
|
UHoudiniOutput *Output = HAC->GetOutputAt(Index);
|
|
if (!Output || Output->IsPendingKill())
|
|
continue;
|
|
|
|
if (Output->GetType() != EHoudiniOutputType::Mesh)
|
|
continue;
|
|
|
|
for (auto &OutputObjectPair : Output->GetOutputObjects())
|
|
{
|
|
UObject *Obj = OutputObjectPair.Value.OutputObject;
|
|
if (!Obj || Obj->IsPendingKill())
|
|
continue;
|
|
|
|
UStaticMesh *SM = Cast<UStaticMesh>(Obj);
|
|
if (!SM)
|
|
continue;
|
|
|
|
UPackage *Package = SM->GetOutermost();
|
|
if (!Package || Package->IsPendingKill())
|
|
continue;
|
|
|
|
if (Package->IsDirty() && Package->IsFullyLoaded() && Package != GetTransientPackage())
|
|
{
|
|
PackagesToSave.Add(Package);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
UEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, true);
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|
|
|