2134 lines
81 KiB
C++
2134 lines
81 KiB
C++
/*
|
|
* 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 "HoudiniEngineEditor.h"
|
|
|
|
#include "HoudiniApi.h"
|
|
#include "HoudiniEngineEditorPrivatePCH.h"
|
|
#include "HoudiniEngineUtils.h"
|
|
#include "HoudiniAsset.h"
|
|
#include "HoudiniEngine.h"
|
|
#include "HoudiniAssetActor.h"
|
|
#include "HoudiniAssetComponent.h"
|
|
#include "HoudiniHandleComponentVisualizer.h"
|
|
#include "HoudiniHandleComponent.h"
|
|
#include "HoudiniSplineComponentVisualizer.h"
|
|
#include "HoudiniSplineComponent.h"
|
|
#include "HoudiniAssetComponentDetails.h"
|
|
#include "HoudiniRuntimeSettingsDetails.h"
|
|
#include "HoudiniAssetTypeActions.h"
|
|
#include "HoudiniAssetBroker.h"
|
|
#include "HoudiniAssetActorFactory.h"
|
|
#include "HoudiniEngineBakeUtils.h"
|
|
|
|
#include "UnrealEdGlobals.h"
|
|
#include "Editor/UnrealEdEngine.h"
|
|
#include "Engine/ObjectLibrary.h"
|
|
#include "EditorDirectories.h"
|
|
#include "Styling/SlateStyleRegistry.h"
|
|
#include "SHoudiniToolPalette.h"
|
|
#include "IPlacementModeModule.h"
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "AssetRegistryModule.h"
|
|
#include "Engine/Selection.h"
|
|
#include "ContentBrowserModule.h"
|
|
#include "IContentBrowserSingleton.h"
|
|
#include "SlateOptMacros.h"
|
|
#include "HAL/FileManager.h"
|
|
#include "HAL/PlatformFilemanager.h"
|
|
#include "Dom/JsonObject.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "Serialization/JsonReader.h"
|
|
#include "Serialization/JsonSerializer.h"
|
|
#include "Interfaces/IPluginManager.h"
|
|
|
|
#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE
|
|
|
|
#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ )
|
|
#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ )
|
|
#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( StyleSet->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ )
|
|
#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ )
|
|
#define TTF_CORE_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToCoreContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ )
|
|
#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ )
|
|
#define OTF_CORE_FONT( RelativePath, ... ) FSlateFontInfo( StyleSet->RootToCoreContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ )
|
|
|
|
TSharedPtr<FSlateStyleSet> FHoudiniEngineStyle::StyleSet = nullptr;
|
|
|
|
TSharedPtr<class ISlateStyle>
|
|
FHoudiniEngineStyle::Get()
|
|
{
|
|
return StyleSet;
|
|
}
|
|
|
|
FName
|
|
FHoudiniEngineStyle::GetStyleSetName()
|
|
{
|
|
static FName HoudiniStyleName(TEXT("HoudiniEngineStyle"));
|
|
return HoudiniStyleName;
|
|
}
|
|
|
|
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
|
|
void
|
|
FHoudiniEngineStyle::Initialize()
|
|
{
|
|
// Only register the StyleSet once
|
|
if ( StyleSet.IsValid() )
|
|
return;
|
|
|
|
StyleSet = MakeShareable( new FSlateStyleSet( GetStyleSetName() ) );
|
|
StyleSet->SetContentRoot(FPaths::EngineContentDir() / TEXT("Editor/Slate"));
|
|
StyleSet->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate"));
|
|
|
|
// Note, these sizes are in Slate Units.
|
|
// Slate Units do NOT have to map to pixels.
|
|
const FVector2D Icon5x16(5.0f, 16.0f);
|
|
const FVector2D Icon8x4(8.0f, 4.0f);
|
|
const FVector2D Icon8x8(8.0f, 8.0f);
|
|
const FVector2D Icon10x10(10.0f, 10.0f);
|
|
const FVector2D Icon12x12(12.0f, 12.0f);
|
|
const FVector2D Icon12x16(12.0f, 16.0f);
|
|
const FVector2D Icon14x14(14.0f, 14.0f);
|
|
const FVector2D Icon16x16(16.0f, 16.0f);
|
|
const FVector2D Icon20x20(20.0f, 20.0f);
|
|
const FVector2D Icon22x22(22.0f, 22.0f);
|
|
const FVector2D Icon24x24(24.0f, 24.0f);
|
|
const FVector2D Icon25x25(25.0f, 25.0f);
|
|
const FVector2D Icon32x32(32.0f, 32.0f);
|
|
const FVector2D Icon40x40(40.0f, 40.0f);
|
|
const FVector2D Icon64x64(64.0f, 64.0f);
|
|
const FVector2D Icon36x24(36.0f, 24.0f);
|
|
const FVector2D Icon128x128(128.0f, 128.0f);
|
|
|
|
static FString IconsDir = FHoudiniEngineEditor::GetHoudiniEnginePluginDir() / TEXT("Content/Icons/");
|
|
StyleSet->Set(
|
|
"HoudiniEngine.HoudiniEngineLogo",
|
|
new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16));
|
|
StyleSet->Set(
|
|
"ClassIcon.HoudiniAssetActor",
|
|
new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16));
|
|
StyleSet->Set(
|
|
"HoudiniEngine.HoudiniEngineLogo40",
|
|
new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_40.png"), Icon40x40));
|
|
|
|
StyleSet->Set(
|
|
"ClassIcon.HoudiniAsset",
|
|
new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16));
|
|
|
|
StyleSet->Set(
|
|
"ClassThumbnail.HoudiniAsset",
|
|
new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_128.png"), Icon64x64));
|
|
|
|
//FSlateImageBrush* HoudiniLogo16 = new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16);
|
|
StyleSet->Set("HoudiniEngine.SaveHIPFile", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16));
|
|
StyleSet->Set("HoudiniEngine.ReportBug", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16));
|
|
StyleSet->Set("HoudiniEngine.OpenInHoudini", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16));
|
|
StyleSet->Set("HoudiniEngine.CleanUpTempFolder", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16));
|
|
StyleSet->Set("HoudiniEngine.BakeAllAssets", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16));
|
|
StyleSet->Set("HoudiniEngine.PauseAssetCooking", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16));
|
|
StyleSet->Set("HoudiniEngine.RestartSession", new FSlateImageBrush(IconsDir + TEXT("icon_houdini_logo_16.png"), Icon16x16));
|
|
|
|
// We need some colors from Editor Style & this is the only way to do this at the moment
|
|
const FSlateColor DefaultForeground = FEditorStyle::GetSlateColor("DefaultForeground");
|
|
const FSlateColor InvertedForeground = FEditorStyle::GetSlateColor("InvertedForeground");
|
|
const FSlateColor SelectorColor = FEditorStyle::GetSlateColor("SelectorColor");
|
|
const FSlateColor SelectionColor = FEditorStyle::GetSlateColor("SelectionColor");
|
|
const FSlateColor SelectionColor_Inactive = FEditorStyle::GetSlateColor("SelectionColor_Inactive");
|
|
|
|
const FTableRowStyle &NormalTableRowStyle = FEditorStyle::Get().GetWidgetStyle<FTableRowStyle>("TableView.Row");
|
|
StyleSet->Set(
|
|
"HoudiniEngine.TableRow", FTableRowStyle( NormalTableRowStyle)
|
|
.SetEvenRowBackgroundBrush( FSlateNoResource() )
|
|
.SetEvenRowBackgroundHoveredBrush( IMAGE_BRUSH( "Common/Selection", Icon8x8, FLinearColor( 1.0f, 1.0f, 1.0f, 0.1f ) ) )
|
|
.SetOddRowBackgroundBrush( FSlateNoResource() )
|
|
.SetOddRowBackgroundHoveredBrush( IMAGE_BRUSH( "Common/Selection", Icon8x8, FLinearColor( 1.0f, 1.0f, 1.0f, 0.1f ) ) )
|
|
.SetSelectorFocusedBrush( BORDER_BRUSH( "Common/Selector", FMargin( 4.f / 16.f ), SelectorColor ) )
|
|
.SetActiveBrush( IMAGE_BRUSH( "Common/Selection", Icon8x8, SelectionColor ) )
|
|
.SetActiveHoveredBrush( IMAGE_BRUSH( "Common/Selection", Icon8x8, SelectionColor ) )
|
|
.SetInactiveBrush( IMAGE_BRUSH( "Common/Selection", Icon8x8, SelectionColor_Inactive ) )
|
|
.SetInactiveHoveredBrush( IMAGE_BRUSH( "Common/Selection", Icon8x8, SelectionColor_Inactive ) )
|
|
.SetTextColor( DefaultForeground )
|
|
.SetSelectedTextColor( InvertedForeground )
|
|
);
|
|
|
|
// Normal Text
|
|
const FTextBlockStyle& NormalText = FEditorStyle::Get().GetWidgetStyle<FTextBlockStyle>("NormalText");
|
|
StyleSet->Set(
|
|
"HoudiniEngine.ThumbnailText", FTextBlockStyle(NormalText)
|
|
.SetFont(TTF_CORE_FONT("Fonts/Roboto-Regular", 9))
|
|
.SetColorAndOpacity(FSlateColor::UseForeground())
|
|
.SetShadowOffset(FVector2D::ZeroVector)
|
|
.SetShadowColorAndOpacity(FLinearColor::Black)
|
|
.SetHighlightColor(FLinearColor(0.02f, 0.3f, 0.0f))
|
|
.SetHighlightShape(BOX_BRUSH("Common/TextBlockHighlightShape", FMargin(3.f / 8.f)))
|
|
);
|
|
|
|
StyleSet->Set("HoudiniEngine.ThumbnailShadow", new BOX_BRUSH("ContentBrowser/ThumbnailShadow", FMargin(4.0f / 64.0f)));
|
|
StyleSet->Set("HoudiniEngine.ThumbnailBackground", new IMAGE_BRUSH("Common/ClassBackground_64x", FVector2D(64.f, 64.f), FLinearColor(0.75f, 0.75f, 0.75f, 1.0f)));
|
|
|
|
// Register Slate style.
|
|
FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get());
|
|
};
|
|
|
|
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
|
|
|
|
#undef IMAGE_BRUSH
|
|
#undef BOX_BRUSH
|
|
#undef BORDER_BRUSH
|
|
#undef TTF_FONT
|
|
#undef TTF_CORE_FONT
|
|
#undef OTF_FONT
|
|
#undef OTF_CORE_FONT
|
|
|
|
void
|
|
FHoudiniEngineStyle::Shutdown()
|
|
{
|
|
if (StyleSet.IsValid())
|
|
{
|
|
FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get());
|
|
ensure(StyleSet.IsUnique());
|
|
StyleSet.Reset();
|
|
}
|
|
}
|
|
|
|
|
|
const FName
|
|
FHoudiniEngineEditor::HoudiniEngineEditorAppIdentifier = FName( TEXT( "HoudiniEngineEditorApp" ) );
|
|
|
|
IMPLEMENT_MODULE( FHoudiniEngineEditor, HoudiniEngineEditor );
|
|
DEFINE_LOG_CATEGORY( LogHoudiniEngineEditor );
|
|
|
|
FHoudiniEngineEditor *
|
|
FHoudiniEngineEditor::HoudiniEngineEditorInstance = nullptr;
|
|
|
|
FHoudiniEngineEditor &
|
|
FHoudiniEngineEditor::Get()
|
|
{
|
|
return *HoudiniEngineEditorInstance;
|
|
}
|
|
|
|
bool
|
|
FHoudiniEngineEditor::IsInitialized()
|
|
{
|
|
return FHoudiniEngineEditor::HoudiniEngineEditorInstance != nullptr;
|
|
}
|
|
|
|
FHoudiniEngineEditor::FHoudiniEngineEditor()
|
|
: CurrentHoudiniToolDirIndex(-1),
|
|
LastHoudiniAssetComponentUndoObject( nullptr )
|
|
{}
|
|
|
|
void
|
|
FHoudiniEngineEditor::StartupModule()
|
|
{
|
|
HOUDINI_LOG_MESSAGE( TEXT( "Starting the Houdini Engine Editor module." ) );
|
|
|
|
// Create style set.
|
|
FHoudiniEngineStyle::Initialize();
|
|
|
|
// Register asset type actions.
|
|
RegisterAssetTypeActions();
|
|
|
|
// Register asset brokers.
|
|
RegisterAssetBrokers();
|
|
|
|
// Register component visualizers.
|
|
RegisterComponentVisualizers();
|
|
|
|
// Register detail presenters.
|
|
RegisterDetails();
|
|
|
|
// Register actor factories.
|
|
RegisterActorFactories();
|
|
|
|
// Extends the file menu.
|
|
ExtendMenu();
|
|
|
|
// Extend the World Outliner Menu
|
|
AddLevelViewportMenuExtender();
|
|
|
|
// Adds the custom console commands
|
|
RegisterConsoleCommands();
|
|
|
|
// Register global undo / redo callbacks.
|
|
RegisterForUndo();
|
|
|
|
#ifdef HOUDINI_MODE
|
|
// Register editor modes
|
|
RegisterModes();
|
|
#endif
|
|
|
|
RegisterPlacementModeExtensions();
|
|
|
|
// Store the instance.
|
|
FHoudiniEngineEditor::HoudiniEngineEditorInstance = this;
|
|
|
|
HOUDINI_LOG_MESSAGE( TEXT("Houdini Engine Editor module startup complete." ) );
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::ShutdownModule()
|
|
{
|
|
HOUDINI_LOG_MESSAGE( TEXT( "Shutting down the Houdini Engine Editor module." ) );
|
|
|
|
// Remove the level viewport Menu extender%
|
|
RemoveLevelViewportMenuExtender();
|
|
|
|
// Unregister asset type actions.
|
|
UnregisterAssetTypeActions();
|
|
|
|
// Unregister asset brokers.
|
|
UnregisterAssetBrokers();
|
|
|
|
// Unregister detail presenters.
|
|
UnregisterDetails();
|
|
|
|
// Unregister our component visualizers.
|
|
UnregisterComponentVisualizers();
|
|
|
|
// Unregister global undo / redo callbacks.
|
|
UnregisterForUndo();
|
|
|
|
#ifdef HOUDINI_MODE
|
|
UnregisterModes();
|
|
#endif
|
|
|
|
UnregisterPlacementModeExtensions();
|
|
|
|
// Unregister the styleset
|
|
FHoudiniEngineStyle::Shutdown();
|
|
|
|
HOUDINI_LOG_MESSAGE( TEXT("Houdini Engine Editor module shutdown complete." ) );
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::RegisterComponentVisualizers()
|
|
{
|
|
if ( GUnrealEd )
|
|
{
|
|
if ( !SplineComponentVisualizer.IsValid() )
|
|
{
|
|
SplineComponentVisualizer = MakeShareable( new FHoudiniSplineComponentVisualizer );
|
|
|
|
GUnrealEd->RegisterComponentVisualizer(
|
|
UHoudiniSplineComponent::StaticClass()->GetFName(),
|
|
SplineComponentVisualizer
|
|
);
|
|
|
|
SplineComponentVisualizer->OnRegister();
|
|
}
|
|
|
|
if ( !HandleComponentVisualizer.IsValid() )
|
|
{
|
|
HandleComponentVisualizer = MakeShareable( new FHoudiniHandleComponentVisualizer );
|
|
|
|
GUnrealEd->RegisterComponentVisualizer(
|
|
UHoudiniHandleComponent::StaticClass()->GetFName(),
|
|
HandleComponentVisualizer
|
|
);
|
|
|
|
HandleComponentVisualizer->OnRegister();
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::UnregisterComponentVisualizers()
|
|
{
|
|
if ( GUnrealEd )
|
|
{
|
|
if ( SplineComponentVisualizer.IsValid() )
|
|
GUnrealEd->UnregisterComponentVisualizer( UHoudiniSplineComponent::StaticClass()->GetFName() );
|
|
|
|
if ( HandleComponentVisualizer.IsValid() )
|
|
GUnrealEd->UnregisterComponentVisualizer( UHoudiniHandleComponent::StaticClass()->GetFName() );
|
|
}
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::RegisterDetails()
|
|
{
|
|
FPropertyEditorModule & PropertyModule = FModuleManager::LoadModuleChecked< FPropertyEditorModule >( "PropertyEditor" );
|
|
|
|
// Register details presenter for our component type and runtime settings.
|
|
PropertyModule.RegisterCustomClassLayout(
|
|
TEXT( "HoudiniAssetComponent" ),
|
|
FOnGetDetailCustomizationInstance::CreateStatic( &FHoudiniAssetComponentDetails::MakeInstance ) );
|
|
PropertyModule.RegisterCustomClassLayout(
|
|
TEXT( "HoudiniRuntimeSettings" ),
|
|
FOnGetDetailCustomizationInstance::CreateStatic( &FHoudiniRuntimeSettingsDetails::MakeInstance ) );
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::UnregisterDetails()
|
|
{
|
|
if ( FModuleManager::Get().IsModuleLoaded( "PropertyEditor" ) )
|
|
{
|
|
FPropertyEditorModule & PropertyModule =
|
|
FModuleManager::LoadModuleChecked<FPropertyEditorModule>( "PropertyEditor" );
|
|
|
|
PropertyModule.UnregisterCustomClassLayout( TEXT( "HoudiniAssetComponent" ) );
|
|
PropertyModule.UnregisterCustomClassLayout( TEXT( "HoudiniRuntimeSettings" ) );
|
|
}
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::RegisterAssetTypeActions()
|
|
{
|
|
// Create and register asset type actions for Houdini asset.
|
|
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked< FAssetToolsModule >( "AssetTools" ).Get();
|
|
RegisterAssetTypeAction( AssetTools, MakeShareable( new FHoudiniAssetTypeActions() ) );
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::UnregisterAssetTypeActions()
|
|
{
|
|
// Unregister asset type actions we have previously registered.
|
|
if ( FModuleManager::Get().IsModuleLoaded( "AssetTools" ) )
|
|
{
|
|
IAssetTools & AssetTools = FModuleManager::GetModuleChecked< FAssetToolsModule >( "AssetTools" ).Get();
|
|
|
|
for ( int32 Index = 0; Index < AssetTypeActions.Num(); ++Index )
|
|
AssetTools.UnregisterAssetTypeActions( AssetTypeActions[ Index ].ToSharedRef() );
|
|
|
|
AssetTypeActions.Empty();
|
|
}
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::RegisterAssetTypeAction( IAssetTools & AssetTools, TSharedRef< IAssetTypeActions > Action )
|
|
{
|
|
AssetTools.RegisterAssetTypeActions( Action );
|
|
AssetTypeActions.Add( Action );
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::RegisterAssetBrokers()
|
|
{
|
|
// Create and register broker for Houdini asset.
|
|
HoudiniAssetBroker = MakeShareable( new FHoudiniAssetBroker() );
|
|
FComponentAssetBrokerage::RegisterBroker( HoudiniAssetBroker, UHoudiniAssetComponent::StaticClass(), true, true );
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::UnregisterAssetBrokers()
|
|
{
|
|
if ( UObjectInitialized() )
|
|
{
|
|
// Unregister broker.
|
|
FComponentAssetBrokerage::UnregisterBroker( HoudiniAssetBroker );
|
|
}
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::RegisterActorFactories()
|
|
{
|
|
if ( GEditor )
|
|
{
|
|
UHoudiniAssetActorFactory * HoudiniAssetActorFactory =
|
|
NewObject< UHoudiniAssetActorFactory >( GetTransientPackage(), UHoudiniAssetActorFactory::StaticClass() );
|
|
|
|
GEditor->ActorFactories.Add( HoudiniAssetActorFactory );
|
|
}
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::ExtendMenu()
|
|
{
|
|
if ( !IsRunningCommandlet() )
|
|
{
|
|
// We need to add/bind the UI Commands to their functions first
|
|
BindMenuCommands();
|
|
|
|
// Extend main menu, we will add Houdini section.
|
|
MainMenuExtender = MakeShareable( new FExtender );
|
|
MainMenuExtender->AddMenuExtension(
|
|
"FileLoadAndSave", EExtensionHook::After, HEngineCommands,
|
|
FMenuExtensionDelegate::CreateRaw( this, &FHoudiniEngineEditor::AddHoudiniMenuExtension ) );
|
|
FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked< FLevelEditorModule >( "LevelEditor" );
|
|
LevelEditorModule.GetMenuExtensibilityManager()->AddExtender( MainMenuExtender );
|
|
}
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::AddHoudiniMenuExtension( FMenuBuilder & MenuBuilder )
|
|
{
|
|
MenuBuilder.BeginSection( "Houdini", LOCTEXT( "HoudiniLabel", "Houdini Engine" ) );
|
|
|
|
MenuBuilder.AddMenuEntry( FHoudiniEngineCommands::Get().OpenInHoudini );
|
|
MenuBuilder.AddMenuEntry( FHoudiniEngineCommands::Get().SaveHIPFile );
|
|
MenuBuilder.AddMenuEntry( FHoudiniEngineCommands::Get().ReportBug );
|
|
MenuBuilder.AddMenuEntry( FHoudiniEngineCommands::Get().CleanUpTempFolder );
|
|
MenuBuilder.AddMenuEntry( FHoudiniEngineCommands::Get().BakeAllAssets );
|
|
MenuBuilder.AddMenuEntry( FHoudiniEngineCommands::Get().PauseAssetCooking );
|
|
MenuBuilder.AddMenuEntry( FHoudiniEngineCommands::Get().RestartSession);
|
|
|
|
MenuBuilder.EndSection();
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::BindMenuCommands()
|
|
{
|
|
HEngineCommands = MakeShareable( new FUICommandList );
|
|
|
|
FHoudiniEngineCommands::Register();
|
|
const FHoudiniEngineCommands& Commands = FHoudiniEngineCommands::Get();
|
|
|
|
HEngineCommands->MapAction(
|
|
Commands.OpenInHoudini,
|
|
FExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::OpenInHoudini ),
|
|
FCanExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::CanOpenInHoudini ) );
|
|
|
|
HEngineCommands->MapAction(
|
|
Commands.SaveHIPFile,
|
|
FExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::SaveHIPFile ),
|
|
FCanExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::CanSaveHIPFile ) );
|
|
|
|
HEngineCommands->MapAction(
|
|
Commands.ReportBug,
|
|
FExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::ReportBug ),
|
|
FCanExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::CanReportBug ) );
|
|
|
|
HEngineCommands->MapAction(
|
|
Commands.CleanUpTempFolder,
|
|
FExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::CleanUpTempFolder ),
|
|
FCanExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::CanCleanUpTempFolder ) );
|
|
|
|
HEngineCommands->MapAction(
|
|
Commands.BakeAllAssets,
|
|
FExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::BakeAllAssets ),
|
|
FCanExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::CanBakeAllAssets ) );
|
|
|
|
HEngineCommands->MapAction(
|
|
Commands.PauseAssetCooking,
|
|
FExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::PauseAssetCooking ),
|
|
FCanExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::CanPauseAssetCooking ),
|
|
FIsActionChecked::CreateRaw( this, &FHoudiniEngineEditor::IsAssetCookingPaused ) );
|
|
|
|
HEngineCommands->MapAction(
|
|
Commands.RestartSession,
|
|
FExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::RestartSession),
|
|
FCanExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::CanRestartSession ) );
|
|
|
|
// Non menu command (used for shortcuts only)
|
|
HEngineCommands->MapAction(
|
|
Commands.CookSelec,
|
|
FExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::RecookSelection ),
|
|
FCanExecuteAction() );
|
|
|
|
HEngineCommands->MapAction(
|
|
Commands.RebuildSelec,
|
|
FExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::RebuildSelection ),
|
|
FCanExecuteAction() );
|
|
|
|
HEngineCommands->MapAction(
|
|
Commands.BakeSelec,
|
|
FExecuteAction::CreateRaw( this, &FHoudiniEngineEditor::BakeSelection ),
|
|
FCanExecuteAction() );
|
|
|
|
// Append the command to the editor module
|
|
FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked< FLevelEditorModule >("LevelEditor");
|
|
LevelEditorModule.GetGlobalLevelEditorActions()->Append(HEngineCommands.ToSharedRef());
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::RegisterConsoleCommands()
|
|
{
|
|
// Register corresponding console commands
|
|
static FAutoConsoleCommand CCmdOpen = FAutoConsoleCommand(
|
|
TEXT("Houdini.Open"),
|
|
TEXT("Open the scene in Houdini."),
|
|
FConsoleCommandDelegate::CreateRaw( this, &FHoudiniEngineEditor::OpenInHoudini ) );
|
|
|
|
static FAutoConsoleCommand CCmdSave = FAutoConsoleCommand(
|
|
TEXT("Houdini.Save"),
|
|
TEXT("Save the current Houdini scene to a hip file."),
|
|
FConsoleCommandDelegate::CreateRaw(this, &FHoudiniEngineEditor::SaveHIPFile ) );
|
|
|
|
static FAutoConsoleCommand CCmdBake = FAutoConsoleCommand(
|
|
TEXT("Houdini.BakeAll"),
|
|
TEXT("Bakes and replaces with blueprints all Houdini Asset Actors in the current level."),
|
|
FConsoleCommandDelegate::CreateRaw(this, &FHoudiniEngineEditor::BakeAllAssets ) );
|
|
|
|
static FAutoConsoleCommand CCmdClean = FAutoConsoleCommand(
|
|
TEXT("Houdini.Clean"),
|
|
TEXT("Cleans up unused/unreferenced Houdini Engine temporary files."),
|
|
FConsoleCommandDelegate::CreateRaw(this, &FHoudiniEngineEditor::CleanUpTempFolder ) );
|
|
|
|
static FAutoConsoleCommand CCmdPause = FAutoConsoleCommand(
|
|
TEXT( "Houdini.Pause"),
|
|
TEXT( "Pauses Houdini Engine Asset cooking." ),
|
|
FConsoleCommandDelegate::CreateRaw( this, &FHoudiniEngineEditor::PauseAssetCooking ) );
|
|
|
|
// Additional console only commands
|
|
static FAutoConsoleCommand CCmdCookAll = FAutoConsoleCommand(
|
|
TEXT("Houdini.CookAll"),
|
|
TEXT("Re-cooks all Houdini Engine Asset Actors in the current level."),
|
|
FConsoleCommandDelegate::CreateRaw( this, &FHoudiniEngineEditor::RecookAllAssets ) );
|
|
|
|
static FAutoConsoleCommand CCmdRebuildAll = FAutoConsoleCommand(
|
|
TEXT("Houdini.RebuildAll"),
|
|
TEXT("Rebuilds all Houdini Engine Asset Actors in the current level."),
|
|
FConsoleCommandDelegate::CreateRaw( this, &FHoudiniEngineEditor::RebuildAllAssets ) );
|
|
|
|
static FAutoConsoleCommand CCmdCookSelec = FAutoConsoleCommand(
|
|
TEXT("Houdini.Cook"),
|
|
TEXT("Re-cooks selected Houdini Asset Actors in the current level."),
|
|
FConsoleCommandDelegate::CreateRaw( this, &FHoudiniEngineEditor::RecookSelection ) );
|
|
|
|
static FAutoConsoleCommand CCmdRebuildSelec = FAutoConsoleCommand(
|
|
TEXT("Houdini.Rebuild"),
|
|
TEXT("Rebuilds selected Houdini Asset Actors in the current level."),
|
|
FConsoleCommandDelegate::CreateRaw( this, &FHoudiniEngineEditor::RebuildSelection ) );
|
|
|
|
static FAutoConsoleCommand CCmdBakeSelec = FAutoConsoleCommand(
|
|
TEXT("Houdini.Bake"),
|
|
TEXT("Bakes and replaces with blueprints selected Houdini Asset Actors in the current level."),
|
|
FConsoleCommandDelegate::CreateRaw( this, &FHoudiniEngineEditor::BakeSelection ) );
|
|
|
|
static FAutoConsoleCommand CCmdRestartSession = FAutoConsoleCommand(
|
|
TEXT("Houdini.RestartSession"),
|
|
TEXT("Restart the current Houdini Session."),
|
|
FConsoleCommandDelegate::CreateRaw(this, &FHoudiniEngineEditor::RestartSession ) );
|
|
}
|
|
|
|
bool
|
|
FHoudiniEngineEditor::CanSaveHIPFile() const
|
|
{
|
|
return FHoudiniEngine::IsInitialized();
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::SaveHIPFile()
|
|
{
|
|
IDesktopPlatform * DesktopPlatform = FDesktopPlatformModule::Get();
|
|
if ( DesktopPlatform && FHoudiniEngineUtils::IsInitialized() )
|
|
{
|
|
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 );
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
FHoudiniEngineEditor::CanOpenInHoudini() const
|
|
{
|
|
return FHoudiniEngine::IsInitialized();
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::OpenInHoudini()
|
|
{
|
|
if ( !FHoudiniEngine::IsInitialized() )
|
|
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 );
|
|
|
|
// ... and a log message
|
|
HOUDINI_LOG_MESSAGE( TEXT("Opened scene in Houdini.") );
|
|
|
|
// 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 HoudiniLocation = LibHAPILocation + TEXT("//houdini");
|
|
FPlatformProcess::CreateProc(
|
|
*HoudiniLocation,
|
|
*UserTempPath,
|
|
true, false, false,
|
|
nullptr, 0,
|
|
FPlatformProcess::UserTempDir(),
|
|
nullptr, nullptr );
|
|
|
|
// Unfortunately, LaunchFileInDefaultExternalApplication doesn't seem to be working properly
|
|
//FPlatformProcess::LaunchFileInDefaultExternalApplication( UserTempPath.GetCharArray().GetData(), nullptr, ELaunchVerb::Open );
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::ReportBug()
|
|
{
|
|
FPlatformProcess::LaunchURL( HAPI_UNREAL_BUG_REPORT_URL, nullptr, nullptr );
|
|
}
|
|
|
|
bool
|
|
FHoudiniEngineEditor::CanReportBug() const
|
|
{
|
|
return FHoudiniEngine::IsInitialized();
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::RegisterForUndo()
|
|
{
|
|
if ( GUnrealEd )
|
|
GUnrealEd->RegisterForUndo( this );
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::UnregisterForUndo()
|
|
{
|
|
if ( GUnrealEd )
|
|
GUnrealEd->UnregisterForUndo( this );
|
|
}
|
|
|
|
/** Registers placement mode extensions. */
|
|
void
|
|
FHoudiniEngineEditor::RegisterPlacementModeExtensions()
|
|
{
|
|
// Load custom houdini tools
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
|
|
check( HoudiniRuntimeSettings );
|
|
|
|
if ( HoudiniRuntimeSettings->bHidePlacementModeHoudiniTools )
|
|
return;
|
|
|
|
FPlacementCategoryInfo Info(
|
|
LOCTEXT( "HoudiniCategoryName", "Houdini Engine" ),
|
|
"HoudiniEngine",
|
|
TEXT( "PMHoudiniEngine" ),
|
|
25
|
|
);
|
|
Info.CustomGenerator = []() -> TSharedRef<SWidget> { return SNew( SHoudiniToolPalette ); };
|
|
|
|
IPlacementModeModule::Get().RegisterPlacementCategory( Info );
|
|
}
|
|
|
|
FString
|
|
FHoudiniEngineEditor::GetDefaultHoudiniToolIcon()
|
|
{
|
|
return FHoudiniEngineEditor::GetHoudiniEnginePluginDir() / TEXT("Content/Icons/icon_houdini_logo_40.png");
|
|
}
|
|
|
|
FString
|
|
FHoudiniEngineEditor::GetHoudiniEnginePluginDir()
|
|
{
|
|
FString EnginePluginDir = FPaths::EnginePluginsDir() / TEXT("Runtime/HoudiniEngine");
|
|
if ( FPaths::DirectoryExists(EnginePluginDir) )
|
|
return EnginePluginDir;
|
|
|
|
FString ProjectPluginDir = FPaths::ProjectPluginsDir() / TEXT("Runtime/HoudiniEngine");
|
|
if ( FPaths::DirectoryExists(ProjectPluginDir) )
|
|
return ProjectPluginDir;
|
|
|
|
TSharedPtr<IPlugin> HoudiniPlugin = IPluginManager::Get().FindPlugin(TEXT("HoudiniEngine"));
|
|
FString PluginBaseDir = HoudiniPlugin.IsValid() ? HoudiniPlugin->GetBaseDir() : EnginePluginDir;
|
|
if ( FPaths::DirectoryExists(PluginBaseDir) )
|
|
return PluginBaseDir;
|
|
|
|
HOUDINI_LOG_WARNING(TEXT("Could not find the Houdini Engine plugin's directory"));
|
|
|
|
return EnginePluginDir;
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::UnregisterPlacementModeExtensions()
|
|
{
|
|
if ( IPlacementModeModule::IsAvailable() )
|
|
{
|
|
IPlacementModeModule::Get().UnregisterPlacementCategory( "HoudiniEngine" );
|
|
}
|
|
|
|
HoudiniTools.Empty();
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
FHoudiniEngineEditor::MatchesContext(const FTransactionContext& InContext, const TArray<TPair<UObject*, FTransactionObjectEvent>>& TransactionObjects) const
|
|
{
|
|
if (InContext.Context == TEXT(HOUDINI_MODULE_EDITOR) || InContext.Context == TEXT(HOUDINI_MODULE_RUNTIME))
|
|
{
|
|
// Check if we care about the undo/redo
|
|
for (const TPair<UObject*, FTransactionObjectEvent>& TransactionObjectPair : TransactionObjects)
|
|
{
|
|
LastHoudiniAssetComponentUndoObject = Cast< UHoudiniAssetComponent >(TransactionObjectPair.Key);
|
|
if ( LastHoudiniAssetComponentUndoObject )
|
|
return true;
|
|
}
|
|
}
|
|
|
|
LastHoudiniAssetComponentUndoObject = nullptr;
|
|
return false;
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::PostUndo( bool bSuccess )
|
|
{
|
|
if ( LastHoudiniAssetComponentUndoObject && bSuccess )
|
|
{
|
|
LastHoudiniAssetComponentUndoObject->UpdateEditorProperties( false );
|
|
LastHoudiniAssetComponentUndoObject = nullptr;
|
|
}
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::PostRedo( bool bSuccess )
|
|
{
|
|
if ( LastHoudiniAssetComponentUndoObject && bSuccess )
|
|
{
|
|
LastHoudiniAssetComponentUndoObject->UpdateEditorProperties( false );
|
|
LastHoudiniAssetComponentUndoObject = nullptr;
|
|
}
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::CleanUpTempFolder()
|
|
{
|
|
// Add a slate notification
|
|
FString Notification = TEXT("Cleaning up Houdini Engine temporary folder");
|
|
FHoudiniEngineUtils::CreateSlateNotification( Notification );
|
|
|
|
// Get Runtime settings to get the Temp Cook Folder
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
|
|
if (!HoudiniRuntimeSettings)
|
|
return;
|
|
|
|
FString TempCookFolder = HoudiniRuntimeSettings->TemporaryCookFolder.ToString();
|
|
|
|
// 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;
|
|
|
|
// The Object library will list all UObjects found in the TempFolder
|
|
auto ObjectLibrary = UObjectLibrary::CreateLibrary( UObject::StaticClass(), false, true );
|
|
ObjectLibrary->LoadAssetDataFromPath( TempCookFolder );
|
|
|
|
// Get all the assets found in the TEMP folder
|
|
TArray<FAssetData> AssetDataList;
|
|
ObjectLibrary->GetAssetDataList( AssetDataList );
|
|
|
|
// 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 );
|
|
/*
|
|
// DO NOT FORCE DELETE!!
|
|
// This will actually cause more problems than it solves
|
|
if ( CurrentDeleted <= 0 )
|
|
{
|
|
// Normal deletion failed... Try to force delete the objects?
|
|
TArray<UObject*> ObjectsToDelete;
|
|
for (int i = 0; i < AssetDataToDelete.Num(); i++)
|
|
{
|
|
const FAssetData& AssetData = AssetDataToDelete[i];
|
|
UObject *ObjectToDelete = AssetData.GetAsset();
|
|
// Assets can be loaded even when their underlying type/class no longer exists...
|
|
if ( ObjectToDelete && !ObjectToDelete->IsPendingKill() )
|
|
{
|
|
ObjectsToDelete.Add(ObjectToDelete);
|
|
}
|
|
}
|
|
|
|
CurrentDeleted = ObjectTools::ForceDeleteObjects(ObjectsToDelete, false);
|
|
}
|
|
*/
|
|
|
|
if ( CurrentDeleted > 0 )
|
|
{
|
|
DeletedCount += CurrentDeleted;
|
|
bDidDeleteAsset = true;
|
|
}
|
|
}
|
|
|
|
// Add a slate notification
|
|
Notification = TEXT("Deleted ") + FString::FromInt( DeletedCount ) + TEXT(" temporary files.");
|
|
FHoudiniEngineUtils::CreateSlateNotification( Notification );
|
|
|
|
// ... and a log message
|
|
HOUDINI_LOG_MESSAGE( TEXT("Deleted %d temporary files."), DeletedCount );
|
|
}
|
|
|
|
bool
|
|
FHoudiniEngineEditor::CanCleanUpTempFolder() const
|
|
{
|
|
return FHoudiniEngine::IsInitialized();
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::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::ReplaceHoudiniActorWithBlueprint(HoudiniAssetComponent))
|
|
bSuccess = true;
|
|
}
|
|
else
|
|
{
|
|
if (FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(HoudiniAssetComponent, false))
|
|
bSuccess = true;
|
|
}
|
|
|
|
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 );
|
|
}
|
|
|
|
bool
|
|
FHoudiniEngineEditor::CanBakeAllAssets() const
|
|
{
|
|
if ( !FHoudiniEngine::IsInitialized() )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::PauseAssetCooking()
|
|
{
|
|
// Revert the global flag
|
|
bool CurrentEnableCookingGlobal = !FHoudiniEngine::Get().GetEnableCookingGlobal();
|
|
FHoudiniEngine::Get().SetEnableCookingGlobal( CurrentEnableCookingGlobal );
|
|
|
|
// Add a slate notification
|
|
FString Notification = TEXT("Houdini Engine cooking paused");
|
|
if ( CurrentEnableCookingGlobal )
|
|
Notification = TEXT("Houdini Engine cooking resumed");
|
|
FHoudiniEngineUtils::CreateSlateNotification( Notification );
|
|
|
|
// ... and a log message
|
|
if ( CurrentEnableCookingGlobal )
|
|
HOUDINI_LOG_MESSAGE( TEXT("Houdini Engine cooking resumed.") );
|
|
else
|
|
HOUDINI_LOG_MESSAGE( TEXT("Houdini Engine cooking paused.") );
|
|
|
|
if ( !CurrentEnableCookingGlobal )
|
|
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
|
|
FHoudiniEngineEditor::CanPauseAssetCooking()
|
|
{
|
|
if ( !FHoudiniEngine::IsInitialized() )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
FHoudiniEngineEditor::IsAssetCookingPaused()
|
|
{
|
|
return !FHoudiniEngine::Get().GetEnableCookingGlobal();
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::RecookSelection()
|
|
{
|
|
// Get current world selection
|
|
TArray<UObject*> WorldSelection;
|
|
int32 SelectedHoudiniAssets = FHoudiniEngineEditor::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() || !HoudiniAssetComponent->IsComponentValid() )
|
|
continue;
|
|
|
|
HoudiniAssetComponent->StartTaskAssetCookingManual();
|
|
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 selected Houdini assets."), CookedCount );
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::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() || !HoudiniAssetComponent->IsComponentValid())
|
|
continue;
|
|
|
|
HoudiniAssetComponent->StartTaskAssetCookingManual();
|
|
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
|
|
FHoudiniEngineEditor::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() || !HoudiniAssetComponent->IsComponentValid() )
|
|
continue;
|
|
|
|
HoudiniAssetComponent->StartTaskAssetRebuildManual();
|
|
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
|
|
FHoudiniEngineEditor::RebuildSelection()
|
|
{
|
|
// Get current world selection
|
|
TArray<UObject*> WorldSelection;
|
|
int32 SelectedHoudiniAssets = FHoudiniEngineEditor::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->StartTaskAssetRebuildManual();
|
|
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
|
|
FHoudiniEngineEditor::BakeSelection()
|
|
{
|
|
// Get current world selection
|
|
TArray<UObject*> WorldSelection;
|
|
int32 SelectedHoudiniAssets = FHoudiniEngineEditor::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::ReplaceHoudiniActorWithBlueprint( 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 FHoudiniEngineEditor::RecentreSelection()
|
|
{
|
|
#if WITH_EDITOR
|
|
//Get current world selection
|
|
TArray<UObject*> WorldSelection;
|
|
int32 SelectedHoudiniAssets = FHoudiniEngineEditor::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< UHoudiniAssetInput* >& AssetInputs = HoudiniAssetComponent->GetInputs();
|
|
for (const UHoudiniAssetInput* 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
|
|
}
|
|
|
|
int32
|
|
FHoudiniEngineEditor::GetContentBrowserSelection( TArray< UObject* >& ContentBrowserSelection )
|
|
{
|
|
ContentBrowserSelection.Empty();
|
|
|
|
// Get the current Content browser selection
|
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked< FContentBrowserModule >( "ContentBrowser" );
|
|
TArray<FAssetData> SelectedAssets;
|
|
ContentBrowserModule.Get().GetSelectedAssets( SelectedAssets );
|
|
|
|
for( int32 n = 0; n < SelectedAssets.Num(); n++ )
|
|
{
|
|
// Get the current object
|
|
UObject * Object = SelectedAssets[ n ].GetAsset();
|
|
if ( !Object || Object->IsPendingKill() )
|
|
continue;
|
|
|
|
// Only static meshes are supported
|
|
if ( Object->GetClass() != UStaticMesh::StaticClass() )
|
|
continue;
|
|
|
|
ContentBrowserSelection.Add( Object );
|
|
}
|
|
|
|
return ContentBrowserSelection.Num();
|
|
}
|
|
|
|
int32
|
|
FHoudiniEngineEditor::GetWorldSelection( TArray< UObject* >& WorldSelection, bool bHoudiniAssetActorsOnly )
|
|
{
|
|
WorldSelection.Empty();
|
|
|
|
// Get the current editor selection
|
|
if ( GEditor )
|
|
{
|
|
USelection* SelectedActors = GEditor->GetSelectedActors();
|
|
if ( SelectedActors && !SelectedActors->IsPendingKill() )
|
|
{
|
|
for ( FSelectionIterator It( *SelectedActors ); It; ++It )
|
|
{
|
|
AActor * Actor = Cast< AActor >( *It );
|
|
if ( !Actor && Actor->IsPendingKill() )
|
|
continue;
|
|
|
|
// Ignore the SkySphere?
|
|
FString ClassName = Actor->GetClass() ? Actor->GetClass()->GetName() : FString();
|
|
if ( ClassName == TEXT( "BP_Sky_Sphere_C" ) )
|
|
continue;
|
|
|
|
// We're normally only selecting actors with StaticMeshComponents and SplineComponents
|
|
// Heightfields? Filter here or later? also allow HoudiniAssets?
|
|
WorldSelection.Add( Actor );
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// If we only want Houdini Actors...
|
|
if ( bHoudiniAssetActorsOnly )
|
|
{
|
|
// ... remove all but them
|
|
for ( int32 Idx = WorldSelection.Num() - 1; Idx >= 0; Idx-- )
|
|
{
|
|
AHoudiniAssetActor * HoudiniAssetActor = Cast<AHoudiniAssetActor>( WorldSelection[ Idx ] );
|
|
if ( !HoudiniAssetActor || HoudiniAssetActor->IsPendingKill() )
|
|
WorldSelection.RemoveAt( Idx );
|
|
}
|
|
}
|
|
|
|
return WorldSelection.Num();
|
|
}
|
|
|
|
bool
|
|
FHoudiniEngineEditor::CanRestartSession() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::RestartSession()
|
|
{
|
|
// Add a slate notification
|
|
FString Notification = TEXT("Restarting Current Houdini Session");
|
|
FHoudiniEngineUtils::CreateSlateNotification( Notification );
|
|
|
|
// Restart the current Houdini Engine Session
|
|
bool bSuccess = FHoudiniEngine::Get().RestartSession();
|
|
|
|
// Notify all the HoudiniAssetComponent that they need to reinstantiate themselves in the new session.
|
|
for ( TObjectIterator<UHoudiniAssetComponent> Itr; Itr; ++Itr )
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = *Itr;
|
|
if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() )
|
|
continue;
|
|
|
|
HoudiniAssetComponent->NotifyAssetNeedsToBeReinstantiated();
|
|
}
|
|
|
|
// Add a slate notification
|
|
if ( bSuccess )
|
|
Notification = TEXT("Houdini Session successfully restarted.");
|
|
else
|
|
Notification = TEXT("Failed to restart the current Houdini Session.");
|
|
FHoudiniEngineUtils::CreateSlateNotification(Notification);
|
|
|
|
// ... and a log message
|
|
//HOUDINI_LOG_MESSAGE( *Notification );
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::AddLevelViewportMenuExtender()
|
|
{
|
|
FLevelEditorModule& LevelEditorModule = FModuleManager::Get().LoadModuleChecked<FLevelEditorModule>( "LevelEditor" );
|
|
auto& MenuExtenders = LevelEditorModule.GetAllLevelViewportContextMenuExtenders();
|
|
|
|
MenuExtenders.Add( FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors::CreateRaw( this, &FHoudiniEngineEditor::GetLevelViewportContextMenuExtender ) );
|
|
LevelViewportExtenderHandle = MenuExtenders.Last().GetHandle();
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::RemoveLevelViewportMenuExtender()
|
|
{
|
|
if ( LevelViewportExtenderHandle.IsValid() )
|
|
{
|
|
FLevelEditorModule* LevelEditorModule = FModuleManager::Get().GetModulePtr<FLevelEditorModule>("LevelEditor");
|
|
if ( LevelEditorModule )
|
|
{
|
|
typedef FLevelEditorModule::FLevelViewportMenuExtender_SelectedActors DelegateType;
|
|
LevelEditorModule->GetAllLevelViewportContextMenuExtenders().RemoveAll(
|
|
[=]( const DelegateType& In ) { return In.GetHandle() == LevelViewportExtenderHandle; } );
|
|
}
|
|
}
|
|
}
|
|
|
|
TSharedRef<FExtender>
|
|
FHoudiniEngineEditor::GetLevelViewportContextMenuExtender( const TSharedRef<FUICommandList> CommandList, const TArray<AActor*> InActors )
|
|
{
|
|
TSharedRef<FExtender> Extender = MakeShareable(new FExtender);
|
|
|
|
// Build an array of the HoudiniAssets corresponding to the selected actors
|
|
TArray< TWeakObjectPtr< UHoudiniAsset > > HoudiniAssets;
|
|
TArray< TWeakObjectPtr< AHoudiniAssetActor > > HoudiniAssetActors;
|
|
for ( auto CurrentActor : InActors )
|
|
{
|
|
AHoudiniAssetActor * HoudiniAssetActor = Cast<AHoudiniAssetActor>( CurrentActor );
|
|
if ( !HoudiniAssetActor || HoudiniAssetActor->IsPendingKill() )
|
|
continue;
|
|
|
|
HoudiniAssetActors.Add( HoudiniAssetActor );
|
|
|
|
UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent();
|
|
if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() )
|
|
continue;
|
|
|
|
HoudiniAssets.AddUnique( HoudiniAssetComponent->GetHoudiniAsset() );
|
|
}
|
|
|
|
if ( HoudiniAssets.Num() > 0 )
|
|
{
|
|
// Add the Asset menu extension
|
|
if ( AssetTypeActions.Num() > 0 )
|
|
{
|
|
// Add the menu extensions via our HoudiniAssetTypeActions
|
|
FHoudiniAssetTypeActions * HATA = static_cast<FHoudiniAssetTypeActions*>( AssetTypeActions[0].Get() );
|
|
if ( HATA )
|
|
Extender = HATA->AddLevelEditorMenuExtenders( HoudiniAssets );
|
|
}
|
|
}
|
|
|
|
if ( HoudiniAssetActors.Num() > 0 )
|
|
{
|
|
// Add some actor menu extensions
|
|
FLevelEditorModule& LevelEditor = FModuleManager::GetModuleChecked<FLevelEditorModule>( TEXT("LevelEditor") );
|
|
TSharedRef<FUICommandList> LevelEditorCommandBindings = LevelEditor.GetGlobalLevelEditorActions();
|
|
Extender->AddMenuExtension(
|
|
"ActorControl",
|
|
EExtensionHook::After,
|
|
LevelEditorCommandBindings,
|
|
FMenuExtensionDelegate::CreateLambda( [ this, HoudiniAssetActors ]( FMenuBuilder& MenuBuilder )
|
|
{
|
|
MenuBuilder.AddMenuEntry(
|
|
NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Recentre", "Recentre selected"),
|
|
NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RecentreTooltip", "Recentres the selected Houdini Asset Actors pivots to their input/cooked static mesh average centre."),
|
|
FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.HoudiniEngineLogo"),
|
|
FUIAction(
|
|
FExecuteAction::CreateRaw(this, &FHoudiniEngineEditor::RecentreSelection),
|
|
FCanExecuteAction::CreateLambda([=] { return (HoudiniAssetActors.Num() > 0); })
|
|
)
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Recook", "Recook selected"),
|
|
NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RecookTooltip", "Forces a recook on the selected Houdini Asset Actors."),
|
|
FSlateIcon( FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.HoudiniEngineLogo"),
|
|
FUIAction(
|
|
FExecuteAction::CreateRaw(this, &FHoudiniEngineEditor::RecookSelection ),
|
|
FCanExecuteAction::CreateLambda([=] { return ( HoudiniAssetActors.Num() > 0 ); })
|
|
)
|
|
);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_Rebuild", "Rebuild selected"),
|
|
NSLOCTEXT("HoudiniAssetLevelViewportContextActions", "HoudiniActor_RebuildTooltip", "Rebuilds selected Houdini Asset Actors in the current level."),
|
|
FSlateIcon( FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.HoudiniEngineLogo"),
|
|
FUIAction(
|
|
FExecuteAction::CreateRaw(this, &FHoudiniEngineEditor::RebuildSelection ),
|
|
FCanExecuteAction::CreateLambda([=] { return ( HoudiniAssetActors.Num() > 0 ); })
|
|
)
|
|
);
|
|
} )
|
|
);
|
|
}
|
|
|
|
return Extender;
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::GetAllHoudiniToolDirectories( TArray<FHoudiniToolDirectory>& HoudiniToolsDirectoryArray ) const
|
|
{
|
|
HoudiniToolsDirectoryArray.Empty();
|
|
|
|
// Read the default tools from the $HFS/engine/tool folder
|
|
FDirectoryPath DefaultToolPath;
|
|
FString HFSPath = FPaths::GetPath(FHoudiniEngine::Get().GetLibHAPILocation());
|
|
FString DefaultPath = FPaths::Combine(HFSPath, TEXT("engine"));
|
|
DefaultPath = FPaths::Combine(DefaultPath, TEXT("tools"));
|
|
|
|
FHoudiniToolDirectory ToolDir;
|
|
ToolDir.Name = TEXT("Default");
|
|
ToolDir.Path.Path = DefaultPath;
|
|
ToolDir.ContentDirID = TEXT("Default");
|
|
|
|
HoudiniToolsDirectoryArray.Add( ToolDir );
|
|
|
|
// Append all the custom tools directory from the runtime settings
|
|
UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetMutableDefault< UHoudiniRuntimeSettings >();
|
|
if ( !HoudiniRuntimeSettings )
|
|
return;
|
|
|
|
// We have to make sure all Custom tool dir have a ContentDirID
|
|
bool NeedToSave = false;
|
|
for (int32 n = 0; n < HoudiniRuntimeSettings->CustomHoudiniToolsLocation.Num(); n++ )
|
|
{
|
|
if (!HoudiniRuntimeSettings->CustomHoudiniToolsLocation[n].ContentDirID.IsEmpty())
|
|
continue;
|
|
|
|
// Generate a new Directory ID for that directory
|
|
HoudiniRuntimeSettings->CustomHoudiniToolsLocation[n].ContentDirID = ObjectTools::SanitizeObjectName(
|
|
HoudiniRuntimeSettings->CustomHoudiniToolsLocation[n].Name + TEXT(" ") + FGuid::NewGuid().ToString() );
|
|
|
|
NeedToSave = true;
|
|
}
|
|
|
|
if ( NeedToSave )
|
|
HoudiniRuntimeSettings->SaveConfig();
|
|
|
|
// Add all the custom tool paths
|
|
HoudiniToolsDirectoryArray.Append( HoudiniRuntimeSettings->CustomHoudiniToolsLocation );
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::GetHoudiniToolDirectories(const int32& SelectedDirIndex, TArray<FHoudiniToolDirectory>& HoudiniToolsDirectoryArray) const
|
|
{
|
|
HoudiniToolsDirectoryArray.Empty();
|
|
|
|
// Get All the houdini tool directories
|
|
GetAllHoudiniToolDirectories(HoudiniToolsDirectoryArray);
|
|
|
|
// Only keep the selected one
|
|
// -1 indicates we want all directories
|
|
if ( SelectedDirIndex >= 0 )
|
|
{
|
|
for (int32 n = HoudiniToolsDirectoryArray.Num() - 1; n >= 0; n--)
|
|
{
|
|
if (n != CurrentHoudiniToolDirIndex)
|
|
HoudiniToolsDirectoryArray.RemoveAt(n);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::UpdateHoudiniToolList(const int32 SelectedDir/*=-1*/)
|
|
{
|
|
CurrentHoudiniToolDirIndex = SelectedDir;
|
|
|
|
// Clean up the existing tool list
|
|
HoudiniTools.Empty();
|
|
|
|
// Get All the houdini tool directories
|
|
TArray<FHoudiniToolDirectory> HoudiniToolsDirectoryArray;
|
|
GetHoudiniToolDirectories( SelectedDir, HoudiniToolsDirectoryArray );
|
|
|
|
// Add the tools for all the directories
|
|
for ( int32 n = 0; n < HoudiniToolsDirectoryArray.Num(); n++ )
|
|
{
|
|
FHoudiniToolDirectory CurrentDir = HoudiniToolsDirectoryArray[n];
|
|
bool isDefault = ( (n == 0) && (CurrentHoudiniToolDirIndex <= 0) );
|
|
UpdateHoudiniToolList( CurrentDir, isDefault );
|
|
}
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineEditor::UpdateHoudiniToolList(const FHoudiniToolDirectory& HoudiniToolsDirectory, const bool& isDefault)
|
|
{
|
|
FString ToolDirPath = HoudiniToolsDirectory.Path.Path;
|
|
if ( ToolDirPath.IsEmpty() )
|
|
return;
|
|
|
|
// We need to either load or create a new HoudiniAsset from the tool's HDA
|
|
FString ToolPath = TEXT("/Game/HoudiniEngine/Tools/");
|
|
ToolPath = FPaths::Combine(ToolPath, HoudiniToolsDirectory.ContentDirID );
|
|
ToolPath = ObjectTools::SanitizeObjectPath(ToolPath);
|
|
|
|
// List all the json files in the current directory
|
|
TArray<FString> JSONFiles;
|
|
IFileManager::Get().FindFiles(JSONFiles, *ToolDirPath, TEXT(".json"));
|
|
for ( const FString& CurrentJsonFile : JSONFiles )
|
|
{
|
|
FString CurrentToolName;
|
|
EHoudiniToolType CurrentToolType;
|
|
EHoudiniToolSelectionType CurrentToolSelectionType;
|
|
FString CurrentToolToolTip;
|
|
FFilePath CurrentToolIconPath;
|
|
FFilePath CurrentToolAssetPath;
|
|
FString CurrentToolHelpURL;
|
|
|
|
// Extract the Tool info from the JSON file
|
|
FString CurrentJsonFilePath = ToolDirPath / CurrentJsonFile;
|
|
if ( !GetHoudiniToolDescriptionFromJSON(
|
|
CurrentJsonFilePath, CurrentToolName, CurrentToolType, CurrentToolSelectionType,
|
|
CurrentToolToolTip, CurrentToolIconPath, CurrentToolAssetPath, CurrentToolHelpURL))
|
|
continue;
|
|
|
|
FText ToolName = FText::FromString(CurrentToolName);
|
|
FText ToolTip = FText::FromString(CurrentToolToolTip);
|
|
|
|
FString IconPath = FPaths::ConvertRelativePathToFull(CurrentToolIconPath.FilePath);
|
|
const FSlateBrush* CustomIconBrush = nullptr;
|
|
if (FPaths::FileExists(IconPath))
|
|
{
|
|
FName BrushName = *IconPath;
|
|
CustomIconBrush = new FSlateDynamicImageBrush(BrushName, FVector2D(40.f, 40.f));
|
|
}
|
|
else
|
|
{
|
|
CustomIconBrush = FHoudiniEngineStyle::Get()->GetBrush(TEXT("HoudiniEngine.HoudiniEngineLogo40"));
|
|
}
|
|
|
|
// Construct the asset's ref
|
|
FString BaseToolName = ObjectTools::SanitizeObjectName( FPaths::GetBaseFilename( CurrentToolAssetPath.FilePath ) );
|
|
FString ToolAssetRef = TEXT("HoudiniAsset'") + FPaths::Combine(ToolPath, BaseToolName) + TEXT(".") + BaseToolName + TEXT("'");
|
|
TSoftObjectPtr<UHoudiniAsset> HoudiniAsset(ToolAssetRef);
|
|
|
|
// See if the HDA needs to be imported in Unreal, or just loaded
|
|
bool NeedsImport = false;
|
|
if ( !HoudiniAsset.IsValid() )
|
|
{
|
|
NeedsImport = true;
|
|
|
|
// Try to load the asset
|
|
UHoudiniAsset* LoadedAsset = HoudiniAsset.LoadSynchronous();
|
|
if ( LoadedAsset && !LoadedAsset->IsPendingKill() )
|
|
NeedsImport = !HoudiniAsset.IsValid();
|
|
}
|
|
|
|
if ( NeedsImport )
|
|
{
|
|
// Automatically import the HDA and create a uasset for it
|
|
FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools");
|
|
TArray<FString> FileNames;
|
|
FileNames.Add(CurrentToolAssetPath.FilePath);
|
|
|
|
UAutomatedAssetImportData* ImportData = NewObject<UAutomatedAssetImportData>();
|
|
ImportData->bReplaceExisting = true;
|
|
ImportData->bSkipReadOnly = true;
|
|
ImportData->Filenames = FileNames;
|
|
ImportData->DestinationPath = ToolPath;
|
|
TArray<UObject*> AssetArray = AssetToolsModule.Get().ImportAssetsAutomated(ImportData);
|
|
if ( AssetArray.Num() <= 0 )
|
|
continue;
|
|
|
|
HoudiniAsset = Cast< UHoudiniAsset >(AssetArray[0]);
|
|
if (!HoudiniAsset || HoudiniAsset->IsPendingKill() )
|
|
continue;
|
|
|
|
// Try to save the newly created package
|
|
UPackage* Pckg = Cast<UPackage>( HoudiniAsset->GetOuter() );
|
|
if (Pckg && !Pckg->IsPendingKill() && Pckg->IsDirty() )
|
|
{
|
|
Pckg->FullyLoad();
|
|
UPackage::SavePackage(
|
|
Pckg, nullptr, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone,
|
|
*FPackageName::LongPackageNameToFilename( Pckg->GetName(), FPackageName::GetAssetPackageExtension() ) );
|
|
}
|
|
}
|
|
|
|
// Add the tool to the tool list
|
|
HoudiniTools.Add( MakeShareable( new FHoudiniTool(
|
|
HoudiniAsset, ToolName, CurrentToolType, CurrentToolSelectionType, ToolTip, CustomIconBrush, CurrentToolHelpURL, isDefault, CurrentToolAssetPath, HoudiniToolsDirectory, CurrentJsonFile ) ) );
|
|
}
|
|
}
|
|
|
|
bool
|
|
FHoudiniEngineEditor::GetHoudiniToolDescriptionFromJSON(const FString& JsonFilePath,
|
|
FString& OutName, EHoudiniToolType& OutType, EHoudiniToolSelectionType& OutSelectionType,
|
|
FString& OutToolTip, FFilePath& OutIconPath, FFilePath& OutAssetPath, FString& OutHelpURL )
|
|
{
|
|
if ( JsonFilePath.IsEmpty() )
|
|
return false;
|
|
|
|
// Read the file as a string
|
|
FString FileContents;
|
|
if ( !FFileHelper::LoadFileToString( FileContents, *JsonFilePath ) )
|
|
{
|
|
HOUDINI_LOG_ERROR( TEXT("Failed to import Houdini Tool .json file: %s"), *JsonFilePath);
|
|
return false;
|
|
}
|
|
|
|
// Parse it as JSON
|
|
TSharedPtr<FJsonObject> JSONObject;
|
|
TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create( FileContents );
|
|
if (!FJsonSerializer::Deserialize(Reader, JSONObject) || !JSONObject.IsValid())
|
|
{
|
|
HOUDINI_LOG_ERROR( TEXT("Invalid json in Houdini Tool .json file: %s"), *JsonFilePath);
|
|
return false;
|
|
}
|
|
|
|
// First, check that the tool is compatible with UE4
|
|
bool IsCompatible = true;
|
|
if (JSONObject->HasField(TEXT("target")))
|
|
{
|
|
IsCompatible = false;
|
|
TArray<TSharedPtr<FJsonValue> >TargetArray = JSONObject->GetArrayField("target");
|
|
for (TSharedPtr<FJsonValue> TargetValue : TargetArray)
|
|
{
|
|
if ( !TargetValue.IsValid() )
|
|
continue;
|
|
|
|
// Check the target array field contains either "all" or "unreal"
|
|
FString TargetString = TargetValue->AsString();
|
|
if ( TargetString.Equals( TEXT("all"), ESearchCase::IgnoreCase )
|
|
|| TargetString.Equals(TEXT("unreal"), ESearchCase::IgnoreCase) )
|
|
{
|
|
IsCompatible = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !IsCompatible )
|
|
{
|
|
// The tool is not compatible with unreal, skip it
|
|
HOUDINI_LOG_MESSAGE( TEXT("Skipped Houdini Tool due to invalid target in JSON file: %s"), *JsonFilePath );
|
|
return false;
|
|
}
|
|
|
|
// Then, we need to make sure the json file has a correponding hda
|
|
// Start by looking at the assetPath field
|
|
FString HDAFilePath = FString();
|
|
if ( JSONObject->HasField( TEXT("assetPath") ) )
|
|
{
|
|
// If the json has the assetPath field, read it from there
|
|
HDAFilePath = JSONObject->GetStringField(TEXT("assetPath"));
|
|
if (!FPaths::FileExists(HDAFilePath))
|
|
HDAFilePath = FString();
|
|
}
|
|
|
|
if (HDAFilePath.IsEmpty())
|
|
{
|
|
// Look for an hda file with the same name as the json file
|
|
HDAFilePath = JsonFilePath.Replace(TEXT(".json"), TEXT(".hda"));
|
|
if (!FPaths::FileExists(HDAFilePath))
|
|
{
|
|
// Try .hdalc
|
|
HDAFilePath = JsonFilePath.Replace(TEXT(".json"), TEXT(".hdalc"));
|
|
if (!FPaths::FileExists(HDAFilePath))
|
|
{
|
|
// Try .hdanc
|
|
HDAFilePath = JsonFilePath.Replace(TEXT(".json"), TEXT(".hdanc"));
|
|
if (!FPaths::FileExists(HDAFilePath))
|
|
HDAFilePath = FString();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( HDAFilePath.IsEmpty() )
|
|
{
|
|
HOUDINI_LOG_ERROR(TEXT("Could not find the hda for the Houdini Tool .json file: %s"), *JsonFilePath);
|
|
return false;
|
|
}
|
|
|
|
// Create a new asset.
|
|
OutAssetPath = FFilePath{ HDAFilePath };
|
|
|
|
// Read the tool name
|
|
OutName = FPaths::GetBaseFilename(JsonFilePath);
|
|
if ( JSONObject->HasField(TEXT("name") ) )
|
|
OutName = JSONObject->GetStringField(TEXT("name"));
|
|
|
|
// Read the tool type
|
|
OutType = EHoudiniToolType::HTOOLTYPE_OPERATOR_SINGLE;
|
|
if ( JSONObject->HasField( TEXT("toolType") ) )
|
|
{
|
|
FString ToolType = JSONObject->GetStringField(TEXT("toolType"));
|
|
if ( ToolType.Equals( TEXT("GENERATOR"), ESearchCase::IgnoreCase ) )
|
|
OutType = EHoudiniToolType::HTOOLTYPE_GENERATOR;
|
|
else if (ToolType.Equals( TEXT("OPERATOR_SINGLE"), ESearchCase::IgnoreCase ) )
|
|
OutType = EHoudiniToolType::HTOOLTYPE_OPERATOR_SINGLE;
|
|
else if (ToolType.Equals(TEXT("OPERATOR_MULTI"), ESearchCase::IgnoreCase))
|
|
OutType = EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI;
|
|
else if (ToolType.Equals(TEXT("BATCH"), ESearchCase::IgnoreCase))
|
|
OutType = EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH;
|
|
}
|
|
|
|
// Read the tooltip
|
|
OutToolTip = FString();
|
|
if ( JSONObject->HasField( TEXT("toolTip") ) )
|
|
{
|
|
OutToolTip = JSONObject->GetStringField(TEXT("toolTip"));
|
|
}
|
|
|
|
// Read the help url
|
|
OutHelpURL = FString();
|
|
if (JSONObject->HasField(TEXT("helpURL")))
|
|
{
|
|
OutHelpURL = JSONObject->GetStringField(TEXT("helpURL"));
|
|
}
|
|
|
|
// Read the icon path
|
|
FString IconPath = FString();
|
|
if (JSONObject->HasField(TEXT("iconPath")))
|
|
{
|
|
// If the json has the iconPath field, read it from there
|
|
IconPath = JSONObject->GetStringField(TEXT("iconPath"));
|
|
if (!FPaths::FileExists(IconPath))
|
|
IconPath = FString();
|
|
}
|
|
|
|
if (IconPath.IsEmpty())
|
|
{
|
|
// Look for a png file with the same name as the json file
|
|
IconPath = JsonFilePath.Replace(TEXT(".json"), TEXT(".png"));
|
|
if (!FPaths::FileExists(IconPath))
|
|
{
|
|
IconPath = GetDefaultHoudiniToolIcon();
|
|
}
|
|
}
|
|
|
|
OutIconPath = FFilePath{ IconPath };
|
|
|
|
// Read the selection types
|
|
FString SelectionType = TEXT("All");
|
|
if ( JSONObject->HasField(TEXT("UE_SelectionType")) )
|
|
SelectionType = JSONObject->GetStringField( TEXT("UE_SelectionType") );
|
|
|
|
if ( SelectionType.Equals( TEXT("CB"), ESearchCase::IgnoreCase ) )
|
|
OutSelectionType = EHoudiniToolSelectionType::HTOOL_SELECTION_CB_ONLY;
|
|
else if ( SelectionType.Equals( TEXT("World"), ESearchCase::IgnoreCase ) )
|
|
OutSelectionType = EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY;
|
|
else
|
|
OutSelectionType = EHoudiniToolSelectionType::HTOOL_SELECTION_ALL;
|
|
|
|
// TODO: Handle Tags in the JSON?
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
FHoudiniEngineEditor::WriteJSONFromHoudiniTool(const FHoudiniTool& Tool)
|
|
{
|
|
// Start by building a JSON object from the tool
|
|
TSharedPtr<FJsonObject> JSONObject = MakeShareable(new FJsonObject);
|
|
|
|
// Mark the target as unreal only
|
|
TArray< TSharedPtr<FJsonValue> > TargetValue;
|
|
TargetValue.Add(MakeShareable(new FJsonValueString(TEXT("unreal"))));
|
|
JSONObject->SetArrayField(TEXT("target"), TargetValue );
|
|
|
|
// Write the asset Path
|
|
if (!Tool.SourceAssetPath.FilePath.IsEmpty())
|
|
{
|
|
// Only write the assetPath if it's different from the default one (same path as JSON)
|
|
if (FPaths::GetBaseFilename(Tool.SourceAssetPath.FilePath) != (FPaths::GetBaseFilename(Tool.JSONFile))
|
|
&& (FPaths::GetPath(Tool.SourceAssetPath.FilePath) != Tool.ToolDirectory.Path.Path))
|
|
{
|
|
JSONObject->SetStringField(TEXT("assetPath"), FPaths::ConvertRelativePathToFull(Tool.SourceAssetPath.FilePath));
|
|
}
|
|
}
|
|
|
|
// The Tool Name
|
|
if ( !Tool.Name.IsEmpty() )
|
|
JSONObject->SetStringField(TEXT("name"), Tool.Name.ToString());
|
|
|
|
// Tooltype
|
|
FString ToolType = TEXT("GENERATOR");
|
|
if ( Tool.Type == EHoudiniToolType::HTOOLTYPE_OPERATOR_SINGLE)
|
|
ToolType = TEXT("OPERATOR_SINGLE");
|
|
else if (Tool.Type == EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI)
|
|
ToolType = TEXT("OPERATOR_MULTI");
|
|
else if ( Tool.Type == EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH)
|
|
ToolType = TEXT("BATCH");
|
|
|
|
JSONObject->SetStringField(TEXT("toolType"), ToolType);
|
|
|
|
// Tooltip
|
|
if ( !Tool.ToolTipText.IsEmpty() )
|
|
JSONObject->SetStringField(TEXT("toolTip"), Tool.ToolTipText.ToString());
|
|
|
|
// Help URL
|
|
if ( !Tool.HelpURL.IsEmpty() )
|
|
JSONObject->SetStringField(TEXT("helpURL"), Tool.HelpURL);
|
|
|
|
// IconPath
|
|
if ( Tool.Icon )
|
|
{
|
|
FString IconPath = Tool.Icon->GetResourceName().ToString();
|
|
JSONObject->SetStringField(TEXT("iconPath"), IconPath);
|
|
}
|
|
|
|
// Selection Type
|
|
FString SelectionType = TEXT("All");
|
|
if (Tool.SelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_CB_ONLY)
|
|
SelectionType = TEXT("CB");
|
|
else if (Tool.SelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY)
|
|
SelectionType = TEXT("World");
|
|
JSONObject->SetStringField(TEXT("UE_SelectionType"), SelectionType);
|
|
|
|
FString ToolJSONFilePath = Tool.ToolDirectory.Path.Path / Tool.JSONFile;
|
|
|
|
// Output the JSON to a String
|
|
FString OutputString;
|
|
TSharedRef< TJsonWriter<> > Writer = TJsonWriterFactory<>::Create(&OutputString);
|
|
FJsonSerializer::Serialize(JSONObject.ToSharedRef(), Writer);
|
|
|
|
// Then write the output string to the json file itself
|
|
FString SaveDirectory = Tool.ToolDirectory.Path.Path;
|
|
FString FileName = Tool.JSONFile;
|
|
|
|
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
|
|
// Returns true if the directory existed or has been created
|
|
if ( PlatformFile.CreateDirectoryTree(*SaveDirectory) )
|
|
{
|
|
FString AbsoluteFilePath = SaveDirectory / FileName;
|
|
return FFileHelper::SaveStringToFile(OutputString, *AbsoluteFilePath);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
FHoudiniEngineEditor::FindHoudiniTool( const FHoudiniTool& Tool, int32& FoundIndex, bool& IsDefault )
|
|
{
|
|
// Return -1 if we cant find the Tool
|
|
FoundIndex = -1;
|
|
|
|
for ( int32 Idx = 0; Idx < HoudiniTools.Num(); Idx++ )
|
|
{
|
|
TSharedPtr<FHoudiniTool> Current = HoudiniTools[ Idx ];
|
|
if ( !Current.IsValid() )
|
|
continue;
|
|
|
|
if ( Current->HoudiniAsset != Tool.HoudiniAsset )
|
|
continue;
|
|
|
|
if ( Current->Type != Tool.Type )
|
|
continue;
|
|
|
|
if ( Current->JSONFile != Tool.JSONFile )
|
|
continue;
|
|
|
|
if (Current->ToolDirectory != Tool.ToolDirectory)
|
|
continue;
|
|
|
|
// We found the Houdini Tool
|
|
IsDefault = Current->DefaultTool;
|
|
FoundIndex = Idx;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
FHoudiniEngineEditor::FindHoudiniToolInHoudiniSettings( const FHoudiniTool& Tool, int32& FoundIndex )
|
|
{
|
|
// Return -1 if we cant find the Tool
|
|
FoundIndex = -1;
|
|
|
|
// Remove the tool from the runtime settings
|
|
const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >();
|
|
if ( !HoudiniRuntimeSettings )
|
|
return false;
|
|
|
|
// TODO: FIX ME!
|
|
/*
|
|
for ( int32 Idx = 0; Idx < HoudiniRuntimeSettings->CustomHoudiniTools.Num(); Idx++ )
|
|
{
|
|
FHoudiniToolDescription CurrentDesc = HoudiniRuntimeSettings->CustomHoudiniTools[ Idx ];
|
|
|
|
if ( CurrentDesc.HoudiniAsset != Tool.HoudiniAsset )
|
|
continue;
|
|
|
|
if ( CurrentDesc.Type != Tool.Type )
|
|
continue;
|
|
|
|
FoundIndex = Idx;
|
|
return true;
|
|
}
|
|
*/
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
FHoudiniEngineCommands::RegisterCommands()
|
|
{
|
|
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( ReportBug, "Report a plugin bug", "Report a bug for Houdini Engine plugin.", 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( BakeAllAssets, "Bake And Replace All Houdini Assets", "Bakes and replaces with blueprints all Houdini Assets in the scene.", 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 ) );
|
|
|
|
UI_COMMAND( CookSelec, "Recook Selection", "Recooks selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord( EKeys::C, EModifierKey::Control | EModifierKey::Alt ) );
|
|
UI_COMMAND( RebuildSelec, "Rebuild Selection", "Rebuilds selected Houdini Asset Actors in the current level.", EUserInterfaceActionType::Button, FInputChord( EKeys::R, EModifierKey::Control | EModifierKey::Alt ) );
|
|
UI_COMMAND( BakeSelec, "Bake 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( RestartSession, "Restart the Houdini Engine Session", "Restarts the current Houdini Engine session.", EUserInterfaceActionType::Button, FInputChord());
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE |