/* * Copyright (c) <2017> Side Effects Software Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ #include "HoudiniEngine.h" #include "HoudiniApi.h" #include "HoudiniEngineRuntimePrivatePCH.h" #include "HoudiniEngineScheduler.h" #include "HoudiniEngineTask.h" #include "HoudiniEngineTaskInfo.h" #include "HoudiniEngineUtils.h" #include "HoudiniLandscapeUtils.h" #include "HoudiniEngineInstancerUtils.h" #include "HoudiniAsset.h" #include "HoudiniRuntimeSettings.h" #include "HAL/PlatformMisc.h" #include "HAL/PlatformFilemanager.h" #include "Misc/ScopeLock.h" #include "Framework/Application/SlateApplication.h" #include "Materials/Material.h" #include "Internationalization/Internationalization.h" #define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE const FName FHoudiniEngine::HoudiniEngineAppIdentifier = FName( TEXT( "HoudiniEngineApp" ) ); IMPLEMENT_MODULE( FHoudiniEngine, HoudiniEngineRuntime ); DEFINE_LOG_CATEGORY( LogHoudiniEngine ); FHoudiniEngine * FHoudiniEngine::HoudiniEngineInstance = nullptr; FHoudiniEngine::FHoudiniEngine() : HoudiniLogoStaticMesh( nullptr ) , HoudiniDefaultMaterial( nullptr ) , HoudiniBgeoAsset( nullptr ) , HoudiniEngineSchedulerThread( nullptr ) , HoudiniEngineScheduler( nullptr ) , EnableCookingGlobal( true ) , FirstSessionCreated( false ) { Session.type = HAPI_SESSION_MAX; Session.id = -1; } #if WITH_EDITOR TSharedPtr< FSlateDynamicImageBrush > FHoudiniEngine::GetHoudiniLogoBrush() const { return HoudiniLogoBrush; } #endif TWeakObjectPtr FHoudiniEngine::GetHoudiniLogoStaticMesh() const { return HoudiniLogoStaticMesh; } TWeakObjectPtr FHoudiniEngine::GetHoudiniDefaultMaterial() const { return HoudiniDefaultMaterial; } TWeakObjectPtr FHoudiniEngine::GetHoudiniBgeoAsset() const { return HoudiniBgeoAsset; } bool FHoudiniEngine::CheckHapiVersionMismatch() const { return bHAPIVersionMismatch; } const FString & FHoudiniEngine::GetLibHAPILocation() const { return LibHAPILocation; } HAPI_Result FHoudiniEngine::GetHapiState() const { return HAPIState; } void FHoudiniEngine::SetHapiState( HAPI_Result Result ) { HAPIState = Result; } const HAPI_Session * FHoudiniEngine::GetSession() const { return Session.type == HAPI_SESSION_MAX ? nullptr : &Session; } FHoudiniEngine & FHoudiniEngine::Get() { check( FHoudiniEngine::HoudiniEngineInstance ); return *FHoudiniEngine::HoudiniEngineInstance; } bool FHoudiniEngine::IsInitialized() { return FHoudiniEngine::HoudiniEngineInstance != nullptr && FHoudiniEngineUtils::IsInitialized(); } void FHoudiniEngine::StartupModule() { bHAPIVersionMismatch = false; HAPIState = HAPI_RESULT_NOT_INITIALIZED; HOUDINI_LOG_MESSAGE( TEXT( "Starting the Houdini Engine module." ) ); #if WITH_EDITOR // Register settings. if( ISettingsModule * SettingsModule = FModuleManager::GetModulePtr< ISettingsModule >( "Settings" ) ) { SettingsModule->RegisterSettings( "Project", "Plugins", "HoudiniEngine", LOCTEXT( "RuntimeSettingsName", "Houdini Engine" ), LOCTEXT( "RuntimeSettingsDescription", "Configure the HoudiniEngine plugin" ), GetMutableDefault< UHoudiniRuntimeSettings >() ); } // Before starting the module, we need to locate and load HAPI library. { void * HAPILibraryHandle = FHoudiniEngineUtils::LoadLibHAPI( LibHAPILocation ); if ( HAPILibraryHandle ) { FHoudiniApi::InitializeHAPI( HAPILibraryHandle ); } else { // Get platform specific name of libHAPI. FString LibHAPIName = FHoudiniEngineUtils::HoudiniGetLibHAPIName(); HOUDINI_LOG_MESSAGE( TEXT( "Failed locating or loading %s" ), *LibHAPIName ); } } #endif // Create static mesh Houdini logo. HoudiniLogoStaticMesh = LoadObject< UStaticMesh >( nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_LOGO, nullptr, LOAD_None, nullptr ); if ( HoudiniLogoStaticMesh.IsValid() ) HoudiniLogoStaticMesh->AddToRoot(); // Create default material. HoudiniDefaultMaterial = LoadObject< UMaterial >( nullptr, HAPI_UNREAL_RESOURCE_HOUDINI_MATERIAL, nullptr, LOAD_None, nullptr ); if ( HoudiniDefaultMaterial.IsValid() ) HoudiniDefaultMaterial->AddToRoot(); // Create Houdini digital asset which is used for loading the bgeo files. HoudiniBgeoAsset = LoadObject< UHoudiniAsset >( nullptr, HAPI_UNREAL_RESOURCE_BGEO_IMPORT, nullptr, LOAD_None, nullptr ); if ( HoudiniBgeoAsset.IsValid() ) HoudiniBgeoAsset->AddToRoot(); #if WITH_EDITOR if ( !IsRunningCommandlet() && !IsRunningDedicatedServer() ) { // Create Houdini logo brush. const TArray< TSharedRef< IPlugin > > Plugins = IPluginManager::Get().GetDiscoveredPlugins(); for ( auto PluginIt( Plugins.CreateConstIterator() ); PluginIt; ++PluginIt ) { const TSharedRef< IPlugin > & Plugin = *PluginIt; if (Plugin->GetName() != TEXT("HoudiniEngine")) continue; FString Icon128FilePath = Plugin->GetBaseDir() / TEXT("Resources/Icon128.png"); if (FPlatformFileManager::Get().GetPlatformFile().FileExists(*Icon128FilePath)) { const FName BrushName(*Icon128FilePath); const FIntPoint Size = FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); if (Size.X > 0 && Size.Y > 0) { static const int32 ProgressIconSize = 32; HoudiniLogoBrush = MakeShareable(new FSlateDynamicImageBrush( BrushName, FVector2D(ProgressIconSize, ProgressIconSize))); } } break; } } // Build and running versions match, we can perform HAPI initialization. if ( FHoudiniApi::IsHAPIInitialized() ) { // We do not automatically try to start a session when starting up the module now. FirstSessionCreated = false; // Create HAPI scheduler and processing thread. HoudiniEngineScheduler = new FHoudiniEngineScheduler(); HoudiniEngineSchedulerThread = FRunnableThread::Create( HoudiniEngineScheduler, TEXT( "HoudiniTaskCookAsset" ), 0, TPri_Normal ); // Set the default value for pausing houdini engine cooking const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); EnableCookingGlobal = !HoudiniRuntimeSettings->bPauseCookingOnStart; } #endif // Store the instance. FHoudiniEngine::HoudiniEngineInstance = this; } void FHoudiniEngine::ShutdownModule() { HOUDINI_LOG_MESSAGE( TEXT( "Shutting down the Houdini Engine module." ) ); // We no longer need Houdini logo static mesh. if ( HoudiniLogoStaticMesh.IsValid() ) { HoudiniLogoStaticMesh->RemoveFromRoot(); HoudiniLogoStaticMesh = nullptr; } // We no longer need Houdini default material. if ( HoudiniDefaultMaterial.IsValid() ) { HoudiniDefaultMaterial->RemoveFromRoot(); HoudiniDefaultMaterial = nullptr; } // We no longer need Houdini digital asset used for loading bgeo files. if ( HoudiniBgeoAsset.IsValid() ) { HoudiniBgeoAsset->RemoveFromRoot(); HoudiniBgeoAsset = nullptr; } #if WITH_EDITOR // Unregister settings. ISettingsModule * SettingsModule = FModuleManager::GetModulePtr< ISettingsModule >( "Settings" ); if ( SettingsModule ) SettingsModule->UnregisterSettings( "Project", "Plugins", "HoudiniEngine" ); #endif // Do scheduler and thread clean up. if ( HoudiniEngineScheduler ) HoudiniEngineScheduler->Stop(); if ( HoudiniEngineSchedulerThread ) { //HoudiniEngineSchedulerThread->Kill( true ); HoudiniEngineSchedulerThread->WaitForCompletion(); delete HoudiniEngineSchedulerThread; HoudiniEngineSchedulerThread = nullptr; } if ( HoudiniEngineScheduler ) { delete HoudiniEngineScheduler; HoudiniEngineScheduler = nullptr; } // Perform HAPI finalization. if ( FHoudiniApi::IsHAPIInitialized() ) { FHoudiniApi::Cleanup( GetSession() ); FHoudiniApi::CloseSession( GetSession() ); } FHoudiniApi::FinalizeHAPI(); } void FHoudiniEngine::AddTask( const FHoudiniEngineTask & Task ) { if ( HoudiniEngineScheduler ) HoudiniEngineScheduler->AddTask( Task ); FScopeLock ScopeLock( &CriticalSection ); FHoudiniEngineTaskInfo TaskInfo; TaskInfos.Add( Task.HapiGUID, TaskInfo ); } void FHoudiniEngine::AddTaskInfo( const FGuid HapIGUID, const FHoudiniEngineTaskInfo & TaskInfo ) { FScopeLock ScopeLock( &CriticalSection ); TaskInfos.Add( HapIGUID, TaskInfo ); } void FHoudiniEngine::RemoveTaskInfo( const FGuid HapIGUID ) { FScopeLock ScopeLock( &CriticalSection ); TaskInfos.Remove( HapIGUID ); } bool FHoudiniEngine::RetrieveTaskInfo( const FGuid HapIGUID, FHoudiniEngineTaskInfo & TaskInfo ) { FScopeLock ScopeLock( &CriticalSection ); if ( TaskInfos.Contains( HapIGUID ) ) { TaskInfo = TaskInfos[ HapIGUID ]; return true; } return false; } bool FHoudiniEngine::CookNode( HAPI_NodeId AssetId, FHoudiniCookParams& HoudiniCookParams, bool ForceRebuildStaticMesh, bool ForceRecookAll, const TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshesIn, TMap< FHoudiniGeoPartObject, UStaticMesh * > & StaticMeshesOut, TMap< FHoudiniGeoPartObject, TWeakObjectPtr >& LandscapesIn, TMap< FHoudiniGeoPartObject, TWeakObjectPtr >& LandscapesOut, TMap< FHoudiniGeoPartObject, USceneComponent * >& InstancersIn, TMap< FHoudiniGeoPartObject, USceneComponent * >& InstancersOut, USceneComponent* ParentComponent, FTransform & ComponentTransform ) { // TMap< FHoudiniGeoPartObject, UStaticMesh * > CookResultArray; bool bReturn = FHoudiniEngineUtils::CreateStaticMeshesFromHoudiniAsset( AssetId, HoudiniCookParams, ForceRebuildStaticMesh, ForceRecookAll, StaticMeshesIn, CookResultArray, ComponentTransform ); if ( !bReturn ) return false; // Extract the static mesh and the volumes/heightfields from the CookResultArray TArray< FHoudiniGeoPartObject > FoundVolumes; TArray< FHoudiniGeoPartObject > FoundInstancers; for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator Iter( CookResultArray ); Iter; ++Iter ) { const FHoudiniGeoPartObject HoudiniGeoPartObject = Iter.Key(); UStaticMesh * StaticMesh = Iter.Value(); if ( HoudiniGeoPartObject.IsInstancer() ) FoundInstancers.Add( HoudiniGeoPartObject ); else if (HoudiniGeoPartObject.IsPackedPrimitiveInstancer()) FoundInstancers.Add( HoudiniGeoPartObject ); else if (HoudiniGeoPartObject.IsCurve()) continue; else if (HoudiniGeoPartObject.IsVolume()) FoundVolumes.Add( HoudiniGeoPartObject ); else StaticMeshesOut.Add(HoudiniGeoPartObject, StaticMesh); } #if WITH_EDITOR // The meshes are already created but we need to create the landscape too if ( FoundVolumes.Num() > 0 ) { TArray< ALandscapeProxy* > NullLandscapes; if ( !FHoudiniLandscapeUtils::CreateAllLandscapes( HoudiniCookParams, FoundVolumes, LandscapesIn, LandscapesOut, NullLandscapes , -200.0f, 200.0f ) ) HOUDINI_LOG_WARNING( TEXT("FHoudiniEngine::CookNode : Failed to create landscapes!") ); } #endif // And the instancers if ( FoundInstancers.Num() > 0 ) { if ( !FHoudiniEngineInstancerUtils::CreateAllInstancers( HoudiniCookParams, AssetId, FoundInstancers, StaticMeshesOut, ParentComponent, InstancersIn, InstancersOut ) ) { HOUDINI_LOG_WARNING( TEXT( "FHoudiniEngine::CookNode : Failed to create instancers!" ) ); } } if ( StaticMeshesOut.Num() <= 0 && LandscapesOut.Num() <= 0 && InstancersOut.Num() <= 0 ) return false; return true; } void FHoudiniEngine::SetEnableCookingGlobal(const bool& enableCooking) { EnableCookingGlobal = enableCooking; } bool FHoudiniEngine::GetEnableCookingGlobal() { return EnableCookingGlobal; } bool FHoudiniEngine::StartSession( HAPI_Session*& SessionPtr, const bool& StartAutomaticServer, const float& AutomaticServerTimeout, const EHoudiniRuntimeSettingsSessionType& SessionType, const FString& ServerPipeName, const int32& ServerPort, const FString& ServerHost ) { // Indicates that we've tried to start the session once // whether it failed or succeed FirstSessionCreated = true; // HAPI needs to be initialized if ( !FHoudiniApi::IsHAPIInitialized() ) return false; // Only start a new Session if we dont have a valid one if ( HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid( SessionPtr ) ) return true; HAPI_Result SessionResult = HAPI_RESULT_FAILURE; HAPI_ThriftServerOptions ServerOptions; FMemory::Memzero< HAPI_ThriftServerOptions >( ServerOptions ); ServerOptions.autoClose = true; ServerOptions.timeoutMs = AutomaticServerTimeout; auto UpdatePathForServer = [&] { // Modify our PATH so that HARC will find HARS.exe const TCHAR* PathDelimiter = FPlatformMisc::GetPathVarDelimiter(); FString OrigPathVar = FPlatformMisc::GetEnvironmentVariable(TEXT("PATH")); FString ModifiedPath = #if PLATFORM_MAC // On Mac our binaries are split between two folders LibHAPILocation + TEXT( "/../Resources/bin" ) + PathDelimiter + #endif LibHAPILocation + PathDelimiter + OrigPathVar; FPlatformMisc::SetEnvironmentVar( TEXT( "PATH" ), *ModifiedPath ); }; switch ( SessionType ) { case EHoudiniRuntimeSettingsSessionType::HRSST_InProcess: { // As of Unreal 4.19, InProcess sessions are not supported anymore // We create an auto started pipe session instead using default values /* SessionResult = FHoudiniApi::CreateInProcessSession(&this->Session); #if PLATFORM_WINDOWS // Workaround for Houdini libtools setting stdout to binary FWindowsPlatformMisc::SetUTF8Output(); #endif */ UpdatePathForServer(); FHoudiniApi::StartThriftNamedPipeServer( &ServerOptions, TCHAR_TO_UTF8( *ServerPipeName ), nullptr ); SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( SessionPtr, TCHAR_TO_UTF8( *ServerPipeName ) ); } break; case EHoudiniRuntimeSettingsSessionType::HRSST_Socket: { // Try to connect to an existing socket session first SessionResult = FHoudiniApi::CreateThriftSocketSession( SessionPtr, TCHAR_TO_UTF8(*ServerHost), ServerPort); // Start a session and try to connect to it if we failed if (StartAutomaticServer && SessionResult != HAPI_RESULT_SUCCESS) { UpdatePathForServer(); FHoudiniApi::StartThriftSocketServer( &ServerOptions, ServerPort, nullptr ); SessionResult = FHoudiniApi::CreateThriftSocketSession( SessionPtr, TCHAR_TO_UTF8(*ServerHost), ServerPort ); } } break; case EHoudiniRuntimeSettingsSessionType::HRSST_NamedPipe: { // Try to connect to an existing pipe session first SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( SessionPtr, TCHAR_TO_UTF8(*ServerPipeName)); // Start a session and try to connect to it if we failed if (StartAutomaticServer && SessionResult != HAPI_RESULT_SUCCESS) { UpdatePathForServer(); FHoudiniApi::StartThriftNamedPipeServer( &ServerOptions, TCHAR_TO_UTF8( *ServerPipeName ), nullptr ); SessionResult = FHoudiniApi::CreateThriftNamedPipeSession( SessionPtr, TCHAR_TO_UTF8(*ServerPipeName) ); } } break; default: HOUDINI_LOG_ERROR( TEXT( "Unsupported Houdini Engine session type" ) ); break; } if (SessionResult != HAPI_RESULT_SUCCESS || !SessionPtr) return false; return true; } bool FHoudiniEngine::InitializeHAPISession() { // The HAPI stubs needs to be initialized if (!FHoudiniApi::IsHAPIInitialized()) { HOUDINI_LOG_ERROR(TEXT("Failed to initialize HAPI: The Houdini API stubs have not been properly initialized.")); return false; } // We need a Valid Session if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(&Session)) { HOUDINI_LOG_ERROR(TEXT("Failed to initialize HAPI: The session is invalid.")); return false; } // Now, initialize HAPI with the new session // We need to make sure HAPI version is correct. int32 RunningEngineMajor = 0; int32 RunningEngineMinor = 0; int32 RunningEngineApi = 0; // Retrieve version numbers for running Houdini Engine. FHoudiniApi::GetEnvInt( HAPI_ENVINT_VERSION_HOUDINI_ENGINE_MAJOR, &RunningEngineMajor ); FHoudiniApi::GetEnvInt( HAPI_ENVINT_VERSION_HOUDINI_ENGINE_MINOR, &RunningEngineMinor ); FHoudiniApi::GetEnvInt( HAPI_ENVINT_VERSION_HOUDINI_ENGINE_API, &RunningEngineApi ); // Compare defined and running versions. if (RunningEngineMajor != HAPI_VERSION_HOUDINI_ENGINE_MAJOR || RunningEngineMinor != HAPI_VERSION_HOUDINI_ENGINE_MINOR) { // Major or minor HAPI version differs, stop here HOUDINI_LOG_ERROR( TEXT("Starting up the Houdini Engine module failed: built and running versions do not match.")); HOUDINI_LOG_ERROR( TEXT("Defined version: %d.%d.api:%d vs Running version: %d.%d.api:%d"), HAPI_VERSION_HOUDINI_ENGINE_MAJOR, HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API, RunningEngineMajor, RunningEngineMinor, RunningEngineApi); return false; } else if (RunningEngineApi != HAPI_VERSION_HOUDINI_ENGINE_API) { // Major/minor HAPIversions match, but only the API version differs, // Allow the user to continue but warn him of possible instabilities HOUDINI_LOG_WARNING( TEXT("Starting up the Houdini Engine module: built and running API versions do not match.")); HOUDINI_LOG_WARNING( TEXT("Defined version: %d.%d.api:%d vs Running version: %d.%d.api:%d"), HAPI_VERSION_HOUDINI_ENGINE_MAJOR, HAPI_VERSION_HOUDINI_ENGINE_MINOR, HAPI_VERSION_HOUDINI_ENGINE_API, RunningEngineMajor, RunningEngineMinor, RunningEngineApi); HOUDINI_LOG_WARNING( TEXT("This could cause instabilities and crashes when using the Houdini Engine plugin")); } const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); // Default CookOptions HAPI_CookOptions CookOptions; FHoudiniApi::CookOptions_Init(&CookOptions); //FMemory::Memzero< HAPI_CookOptions >( CookOptions ); CookOptions.curveRefineLOD = 8.0f; CookOptions.clearErrorsAndWarnings = true; CookOptions.maxVerticesPerPrimitive = 3; CookOptions.splitGeosByGroup = false; CookOptions.splitGeosByAttribute = false; CookOptions.splitAttrSH = 0; CookOptions.refineCurveToLinear = true; CookOptions.handleBoxPartTypes = false; CookOptions.handleSpherePartTypes = false; CookOptions.splitPointsByVertexAttributes = false; CookOptions.packedPrimInstancingMode = HAPI_PACKEDPRIM_INSTANCING_MODE_FLAT; HAPI_Result Result = FHoudiniApi::Initialize( &Session, &CookOptions, true, HoudiniRuntimeSettings->CookingThreadStackSize, TCHAR_TO_UTF8( *HoudiniRuntimeSettings->HoudiniEnvironmentFiles), TCHAR_TO_UTF8( *HoudiniRuntimeSettings->OtlSearchPath), TCHAR_TO_UTF8( *HoudiniRuntimeSettings->DsoSearchPath), TCHAR_TO_UTF8( *HoudiniRuntimeSettings->ImageDsoSearchPath), TCHAR_TO_UTF8( *HoudiniRuntimeSettings->AudioDsoSearchPath) ); if ( Result != HAPI_RESULT_SUCCESS ) { HOUDINI_LOG_MESSAGE( TEXT("Starting up the Houdini Engine API module failed: %s"), *FHoudiniEngineUtils::GetErrorDescription( Result ) ); return false; } HOUDINI_LOG_MESSAGE( TEXT( "Successfully intialized the Houdini Engine API module." ) ); FHoudiniApi::SetServerEnvString(&Session, HAPI_ENV_CLIENT_NAME, HAPI_UNREAL_CLIENT_NAME ); return true; } bool FHoudiniEngine::StopSession( HAPI_Session*& SessionPtr ) { // HAPI needs to be initialized if ( !FHoudiniApi::IsHAPIInitialized() ) return false; if ( HAPI_RESULT_SUCCESS == FHoudiniApi::IsSessionValid( SessionPtr ) ) { // SessionPtr is valid, clean up and close the session FHoudiniApi::Cleanup( SessionPtr ); FHoudiniApi::CloseSession( SessionPtr ); } return true; } bool FHoudiniEngine::RestartSession() { HAPI_Session* SessionPtr = &Session; // Stop the current session if it is still valid if ( !StopSession( SessionPtr ) ) return false; // Try to reconnect/start a new session const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); if (!StartSession( SessionPtr, true, HoudiniRuntimeSettings->AutomaticServerTimeout, HoudiniRuntimeSettings->SessionType, HoudiniRuntimeSettings->ServerPipeName, HoudiniRuntimeSettings->ServerPort, HoudiniRuntimeSettings->ServerHost)) { HOUDINI_LOG_ERROR(TEXT("Failed to restart the Houdini Engine session")); return false; } // Now initialize HAPI for this session if (!InitializeHAPISession()) return false; return true; } bool FHoudiniEngine::GetFirstSessionCreated() const { return FirstSessionCreated; } #undef LOCTEXT_NAMESPACE