/* * 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 "SHoudiniToolPalette.h" #include "HoudiniApi.h" #include "Editor.h" #include "Modules/ModuleManager.h" #include "Widgets/SBoxPanel.h" #include "SlateOptMacros.h" #include "Styling/CoreStyle.h" #include "Widgets/Images/SImage.h" #include "Styling/SlateTypes.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Layout/SScrollBorder.h" #include "Widgets/Layout/SBorder.h" #include "Widgets/Views/STableViewBase.h" #include "Widgets/Views/STableRow.h" #include "Widgets/Views/SListView.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SComboBox.h" #include "EditorStyleSet.h" #include "HoudiniAsset.h" #include "HoudiniAssetActor.h" #include "HoudiniAssetComponent.h" #include "AssetRegistryModule.h" #include "DragAndDrop/AssetDragDropOp.h" #include "ActorFactories/ActorFactory.h" #include "Engine/Selection.h" #include "ContentBrowserModule.h" #include "IContentBrowserSingleton.h" #include "EditorViewportClient.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "AssetToolsModule.h" #include "EditorFramework/AssetImportData.h" #include "Toolkits/GlobalEditorCommonCommands.h" #include "Landscape.h" #include "LandscapeProxy.h" #include "Framework/Application/SlateApplication.h" #include "HAL/PlatformFilemanager.h" #include "HAL/FileManager.h" #include "Modules/ModuleManager.h" #include "PropertyEditorModule.h" #include "Interfaces/IMainFrameModule.h" #define LOCTEXT_NAMESPACE "HoudiniToolPalette" UHoudiniToolProperties::UHoudiniToolProperties(const FObjectInitializer & ObjectInitializer) :Super( ObjectInitializer ) , Name() , Type(EHoudiniToolType::HTOOLTYPE_OPERATOR_SINGLE) , SelectionType(EHoudiniToolSelectionType::HTOOL_SELECTION_ALL) , ToolTip() , IconPath(FFilePath()) , AssetPath(FFilePath()) , HelpURL() { }; UHoudiniToolDirectoryProperties::UHoudiniToolDirectoryProperties(const FObjectInitializer & ObjectInitializer) :Super(ObjectInitializer) , CustomHoudiniToolsDirectories() { }; BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void SHoudiniToolPalette::Construct( const FArguments& InArgs ) { FHoudiniEngineEditor& HoudiniEngineEditor = FModuleManager::GetModuleChecked("HoudiniEngineEditor"); UpdateHoudiniToolDirectories(); SAssignNew( HoudiniToolListView, SHoudiniToolListView ) .SelectionMode( ESelectionMode::Single ) .ListItemsSource( &HoudiniEngineEditor.GetHoudiniTools() ) .OnGenerateRow( this, &SHoudiniToolPalette::MakeListViewWidget ) .OnSelectionChanged( this, &SHoudiniToolPalette::OnSelectionChanged ) .OnMouseButtonDoubleClick( this, &SHoudiniToolPalette::OnDoubleClickedListViewWidget ) .OnContextMenuOpening( this, &SHoudiniToolPalette::ConstructHoudiniToolContextMenu ) .ItemHeight( 35 ); int32 ToolDirComboSelectedIdx = HoudiniEngineEditor.CurrentHoudiniToolDirIndex + 1; if (!HoudiniToolDirArray.IsValidIndex(ToolDirComboSelectedIdx)) ToolDirComboSelectedIdx = 0; TSharedRef > > HoudiniToolDirComboBox = SNew(SComboBox< TSharedPtr< FString > >) .OptionsSource( &HoudiniToolDirArray) .InitiallySelectedItem(HoudiniToolDirArray[ ToolDirComboSelectedIdx ]) .OnGenerateWidget( SComboBox< TSharedPtr< FString > >::FOnGenerateWidget::CreateLambda( []( TSharedPtr< FString > ChoiceEntry ) { FText ChoiceEntryText = FText::FromString( *ChoiceEntry ); return SNew( STextBlock ) .Text( ChoiceEntryText ) .ToolTipText( ChoiceEntryText ) .Font( FEditorStyle::GetFontStyle( TEXT( "PropertyWindow.NormalFont" ) ) ); } ) ) .OnSelectionChanged( SComboBox< TSharedPtr< FString > >::FOnSelectionChanged::CreateLambda( [=]( TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType ) { if ( !NewChoice.IsValid() ) return; OnDirectoryChange(*(NewChoice.Get()) ); } ) ) [ SNew(STextBlock) .Text(this, &SHoudiniToolPalette::OnGetSelectedDirText ) .ToolTipText( this, &SHoudiniToolPalette::OnGetSelectedDirText ) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) ]; TSharedPtr< SButton > EditButton; FText EditButtonText = FText::FromString(TEXT("Edit")); FText EditButtonTooltip = FText::FromString(TEXT("Add, Remove or Edit custom Houdini tool directories")); FText ActiveShelfText = FText::FromString(TEXT("Active Shelf:")); FText ActiveShelfTooltip = FText::FromString(TEXT("Name of the currently selected Houdini Tool Shelf")); ChildSlot [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() [ SNew(SScrollBorder, HoudiniToolListView.ToSharedRef()) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(ActiveShelfText) .ToolTipText(ActiveShelfTooltip) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.BoldFont"))) ] + SHorizontalBox::Slot() [ HoudiniToolDirComboBox ] + SHorizontalBox::Slot() .AutoWidth() [ SAssignNew(EditButton, SButton) .VAlign(VAlign_Center) .HAlign(HAlign_Center) .Text(EditButtonText) .ToolTipText(EditButtonTooltip) .OnClicked(FOnClicked::CreateSP(this, &SHoudiniToolPalette::OnEditToolDirectories)) ] ] ] + SVerticalBox::Slot() .FillHeight(1.0f) [ SNew(SScrollBorder, HoudiniToolListView.ToSharedRef()) [ HoudiniToolListView.ToSharedRef() ] ] ]; } END_SLATE_FUNCTION_BUILD_OPTIMIZATION TSharedRef< ITableRow > SHoudiniToolPalette::MakeListViewWidget( TSharedPtr< FHoudiniTool > HoudiniTool, const TSharedRef< STableViewBase >& OwnerTable ) { check( HoudiniTool.IsValid() ); auto HelpDefault = FEditorStyle::GetBrush( "HelpIcon" ); auto HelpHovered = FEditorStyle::GetBrush( "HelpIcon.Hovered" ); auto HelpPressed = FEditorStyle::GetBrush( "HelpIcon.Pressed" ); auto DefaultTool = FHoudiniEngineStyle::Get()->GetBrush( "HoudiniEngine.HoudiniEngineLogo"); TSharedPtr< SImage > HelpButtonImage; TSharedPtr< SButton > HelpButton; TSharedPtr< SImage > DefaultToolImage; // Building the tool's tooltip FString HoudiniToolTip = HoudiniTool->Name.ToString() + TEXT( "\n" ); if ( HoudiniTool->HoudiniAsset.IsValid() ) HoudiniToolTip += HoudiniTool->HoudiniAsset.ToSoftObjectPath().ToString() /*->AssetFileName */+ TEXT( "\n\n" ); if ( !HoudiniTool->ToolTipText.IsEmpty() ) HoudiniToolTip += HoudiniTool->ToolTipText.ToString() + TEXT("\n\n"); // Adding a description of the tools behavior deriving from its type switch ( HoudiniTool->Type ) { case EHoudiniToolType::HTOOLTYPE_OPERATOR_SINGLE: HoudiniToolTip += TEXT("Operator (Single):\nDouble clicking on this tool will instantiate the asset in the world.\nThe current selection will be automatically assigned to the asset's first input.\n" ); break; case EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI: HoudiniToolTip += TEXT("Operator (Multiple):\nDouble clicking on this tool will instantiate the asset in the world.\nEach of the selected object will be assigned to one of the asset's input (object1 to input1, object2 to input2 etc.)\n" ); break; case EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH: HoudiniToolTip += TEXT("Operator (Batch):\nDouble clicking on this tool will instantiate the asset in the world.\nAn instance of the asset will be created for each of the selected object, and the asset's first input will be set to that object.\n"); break; case EHoudiniToolType::HTOOLTYPE_GENERATOR: default: HoudiniToolTip += TEXT("Generator:\nDouble clicking on this tool will instantiate the asset in the world.\n"); break; } // Add a description from the tools selection type if ( HoudiniTool->Type != EHoudiniToolType::HTOOLTYPE_GENERATOR ) { switch ( HoudiniTool->SelectionType ) { case EHoudiniToolSelectionType::HTOOL_SELECTION_ALL: HoudiniToolTip += TEXT("\nBoth Content Browser and World Outliner selection will be considered.\nIf objects are selected in the content browser, the world selection will be ignored.\n"); break; case EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY: HoudiniToolTip += TEXT("\nOnly World Outliner selection will be considered.\n"); break; case EHoudiniToolSelectionType::HTOOL_SELECTION_CB_ONLY: HoudiniToolTip += TEXT("\nOnly Content Browser selection will be considered.\n"); break; } } if ( HoudiniTool->Type != EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH ) { if ( HoudiniTool->SelectionType != EHoudiniToolSelectionType::HTOOL_SELECTION_CB_ONLY ) HoudiniToolTip += TEXT( "If objects are selected in the world outliner, the asset's transform will default to the mean Transform of the select objects.\n" ); } else { if ( HoudiniTool->SelectionType != EHoudiniToolSelectionType::HTOOL_SELECTION_CB_ONLY ) HoudiniToolTip += TEXT( "If objects are selected in the world outliner, each created asset will use its object transform.\n" ); } FText ToolTipText = FText::FromString( HoudiniToolTip ); // Build the Help Tooltip FString HelpURL = HoudiniTool->HelpURL; bool HasHelp = HelpURL.Len() > 0; FText HelpTip = HasHelp ? FText::Format( LOCTEXT( "OpenHelp", "Click to view tool help: {0}" ), FText::FromString( HelpURL ) ) : HoudiniTool->Name; // Build the "DefaultTool" tool tip bool IsDefault = HoudiniTool->DefaultTool; FText DefaultToolText = FText::FromString( TEXT("Default Houdini Engine for Unreal plug-in tool") ); TSharedRef< STableRow> > TableRowWidget = SNew( STableRow>, OwnerTable ) .Style( FHoudiniEngineStyle::Get(), "HoudiniEngine.TableRow" ) .OnDragDetected( this, &SHoudiniToolPalette::OnDraggingListViewWidget ); TSharedPtr< SHorizontalBox > ContentBox; TSharedRef Content = SNew( SBorder ) .BorderImage( FCoreStyle::Get().GetBrush( "NoBorder" ) ) .Padding( 0 ) .ToolTip( SNew( SToolTip ).Text( ToolTipText ) ) .Cursor( EMouseCursor::GrabHand ) [ SAssignNew( ContentBox, SHorizontalBox ) ]; // Tool Icon ContentBox->AddSlot() .AutoWidth() [ SNew( SBorder ) .Padding( 4.0f ) .BorderImage( FHoudiniEngineStyle::Get()->GetBrush( "HoudiniEngine.ThumbnailShadow" ) ) [ SNew( SBox ) .WidthOverride( 35.0f ) .HeightOverride( 35.0f ) [ SNew( SBorder ) .BorderImage( FHoudiniEngineStyle::Get()->GetBrush( "HoudiniEngine.ThumbnailBackground" ) ) .HAlign( HAlign_Center ) .VAlign( VAlign_Center ) [ SNew( SImage ) .Image( HoudiniTool->Icon ) .ToolTip( SNew( SToolTip ).Text( ToolTipText ) ) ] ] ] ]; // Tool name + tooltip ContentBox->AddSlot() .HAlign( HAlign_Left ) .VAlign( VAlign_Center ) .FillWidth( 1.f ) [ SNew( STextBlock ) .TextStyle( FHoudiniEngineStyle::Get(), "HoudiniEngine.ThumbnailText" ) .Text( HoudiniTool->Name ) .ToolTip( SNew( SToolTip ).Text( ToolTipText ) ) ]; // Default Tool Icon if ( IsDefault ) { ContentBox->AddSlot() .VAlign( VAlign_Center ) .AutoWidth() [ SAssignNew( DefaultToolImage, SImage ) .ToolTip( SNew( SToolTip ).Text( DefaultToolText ) ) ]; // Houdini logo for the Default Tool DefaultToolImage->SetImage( DefaultTool ); } if ( HasHelp ) { ContentBox->AddSlot() .VAlign( VAlign_Center ) .AutoWidth() [ SAssignNew( HelpButton, SButton ) .ContentPadding( 0 ) .ButtonStyle( FEditorStyle::Get(), "HelpButton" ) .OnClicked( FOnClicked::CreateLambda( [ HelpURL ]() { if ( HelpURL.Len() ) FPlatformProcess::LaunchURL( *HelpURL, nullptr, nullptr ); return FReply::Handled(); } ) ) .HAlign( HAlign_Center ) .VAlign( VAlign_Center ) .ToolTip( SNew( SToolTip ).Text( HelpTip ) ) [ SAssignNew( HelpButtonImage, SImage ) .ToolTip( SNew( SToolTip ).Text( HelpTip ) ) ] ]; HelpButtonImage->SetImage( TAttribute::Create( TAttribute::FGetter::CreateLambda( [=]() { if ( HelpButton->IsPressed() ) { return HelpPressed; } if ( HelpButtonImage->IsHovered() ) { return HelpHovered; } return HelpDefault; } ) ) ); } TableRowWidget->SetContent( Content ); return TableRowWidget; } void SHoudiniToolPalette::OnSelectionChanged( TSharedPtr ToolType, ESelectInfo::Type SelectionType ) { if ( ToolType.IsValid() ) { ActiveTool = ToolType; } else { ActiveTool = NULL; } } FReply SHoudiniToolPalette::OnDraggingListViewWidget( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) { if ( MouseEvent.IsMouseButtonDown( EKeys::LeftMouseButton ) ) { if ( ActiveTool.IsValid() ) { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( "AssetRegistry" ); UObject* AssetObj = ActiveTool->HoudiniAsset.LoadSynchronous(); if ( AssetObj ) { FAssetData BackingAssetData = AssetRegistryModule.Get().GetAssetByObjectPath(*AssetObj->GetPathName()); return FReply::Handled().BeginDragDrop( FAssetDragDropOp::New( BackingAssetData, GEditor->FindActorFactoryForActorClass( AHoudiniAssetActor::StaticClass() ) ) ); } } } return FReply::Unhandled(); } void SHoudiniToolPalette::OnDoubleClickedListViewWidget( TSharedPtr HoudiniTool ) { if ( !HoudiniTool.IsValid() ) return; return InstantiateHoudiniTool( HoudiniTool.Get() ); } void SHoudiniToolPalette::InstantiateHoudiniTool( FHoudiniTool* HoudiniTool ) { if ( !HoudiniTool ) return; FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked( "AssetRegistry" ); // Load the asset UObject* AssetObj = HoudiniTool->HoudiniAsset.LoadSynchronous(); if ( !AssetObj ) return; // Get the asset Factory UActorFactory* Factory = GEditor->FindActorFactoryForActorClass( AHoudiniAssetActor::StaticClass() ); if ( !Factory ) return; // Get the current Level Editor Selection TArray WorldSelection; int32 WorldSelectionCount = FHoudiniEngineEditor::GetWorldSelection( WorldSelection ); // Get the current Content browser selection TArray ContentBrowserSelection; int32 ContentBrowserSelectionCount = FHoudiniEngineEditor::GetContentBrowserSelection( ContentBrowserSelection ); // By default, Content browser selection has a priority over the world selection bool UseCBSelection = ContentBrowserSelectionCount > 0; if ( HoudiniTool->SelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_CB_ONLY ) UseCBSelection = true; else if ( HoudiniTool->SelectionType == EHoudiniToolSelectionType::HTOOL_SELECTION_WORLD_ONLY ) UseCBSelection = false; // Modify the created actor's position from the current editor world selection FTransform SpawnTransform = GetDefaulToolSpawnTransform(); if ( WorldSelectionCount > 0 ) { // Get the "mean" transform of all the selected actors SpawnTransform = GetMeanWorldSelectionTransform(); } // If the current tool is a batch one, we'll need to create multiple instances of the HDA if ( HoudiniTool->Type == EHoudiniToolType::HTOOLTYPE_OPERATOR_BATCH ) { // Unselect the current selection to select the created actor after if ( GEditor ) GEditor->SelectNone( true, true, false ); // An instance of the asset will be created for each selected object for( int32 SelecIndex = 0; SelecIndex < ( UseCBSelection ? ContentBrowserSelectionCount : WorldSelectionCount ); SelecIndex++ ) { // Get the current object UObject* CurrentSelectedObject = nullptr; if ( UseCBSelection && ContentBrowserSelection.IsValidIndex( SelecIndex ) ) CurrentSelectedObject = ContentBrowserSelection[ SelecIndex ]; if ( !UseCBSelection && WorldSelection.IsValidIndex( SelecIndex ) ) CurrentSelectedObject = WorldSelection[ SelecIndex ]; if ( !CurrentSelectedObject ) continue; // If it's an actor, use its Transform to spawn the HDA AActor* CurrentSelectedActor = Cast( CurrentSelectedObject ); if ( CurrentSelectedActor ) SpawnTransform = CurrentSelectedActor->GetTransform(); else SpawnTransform = GetDefaulToolSpawnTransform(); // Create the actor for the HDA AActor* CreatedActor = Factory->CreateActor( AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), SpawnTransform ); if ( !CreatedActor ) continue; // Get the HoudiniAssetActor / HoudiniAssetComponent we just created AHoudiniAssetActor* HoudiniAssetActor = ( AHoudiniAssetActor* )CreatedActor; if ( !HoudiniAssetActor ) continue; UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor->GetHoudiniAssetComponent(); if ( !HoudiniAssetComponent ) continue; // Create and set the input preset for this HDA and selected Object TMap< UObject*, int32 > InputPreset; InputPreset.Add( CurrentSelectedObject, 0 ); HoudiniAssetComponent->SetHoudiniToolInputPresets( InputPreset ); // Select the Actor we just created if ( GEditor && GEditor->CanSelectActor( CreatedActor, true, false ) ) GEditor->SelectActor( CreatedActor, true, true, true ); } } else { // We only need to create a single instance of the asset, regarding of the selection AActor* CreatedActor = Factory->CreateActor( AssetObj, GEditor->GetEditorWorldContext().World()->GetCurrentLevel(), SpawnTransform ); if ( !CreatedActor ) return; // Generator tools don't need to preset their input if ( HoudiniTool->Type != EHoudiniToolType::HTOOLTYPE_GENERATOR ) { TMap InputPresets; AHoudiniAssetActor* HoudiniAssetActor = (AHoudiniAssetActor*)CreatedActor; UHoudiniAssetComponent* HoudiniAssetComponent = HoudiniAssetActor ? HoudiniAssetActor->GetHoudiniAssetComponent() : nullptr; if ( HoudiniAssetComponent ) { // Build the preset map int InputIndex = 0; for ( auto CurrentObject : ( UseCBSelection ? ContentBrowserSelection : WorldSelection ) ) { if ( !CurrentObject ) continue; if ( HoudiniTool->Type == EHoudiniToolType::HTOOLTYPE_OPERATOR_MULTI ) { // The selection will be applied individually to multiple inputs // (first object to first input, second object to second input etc...) InputPresets.Add( CurrentObject, InputIndex++ ); } else { // All the selection will be applied to the asset's first input InputPresets.Add( CurrentObject, 0 ); } } // Set the input preset on the HoudiniAssetComponent if ( InputPresets.Num() > 0 ) HoudiniAssetComponent->SetHoudiniToolInputPresets( InputPresets ); } } // Select the Actor we just created if ( GEditor->CanSelectActor( CreatedActor, true, true ) ) { GEditor->SelectNone( true, true, false ); GEditor->SelectActor( CreatedActor, true, true, true ); } } } FTransform SHoudiniToolPalette::GetDefaulToolSpawnTransform() { FTransform SpawnTransform = FTransform::Identity; // Get the editor viewport LookAt position to spawn the new objects if ( GEditor && GEditor->GetActiveViewport() ) { FEditorViewportClient* ViewportClient = (FEditorViewportClient*)GEditor->GetActiveViewport()->GetClient(); if ( ViewportClient ) { // We need to toggle the orbit camera on to get the proper LookAtLocation to spawn our asset ViewportClient->ToggleOrbitCamera( true ); SpawnTransform.SetLocation( ViewportClient->GetLookAtLocation() ); ViewportClient->ToggleOrbitCamera( false ); } } return SpawnTransform; } FTransform SHoudiniToolPalette::GetMeanWorldSelectionTransform() { FTransform SpawnTransform = GetDefaulToolSpawnTransform(); if ( GEditor && ( GEditor->GetSelectedActorCount() > 0 ) ) { // Get the current Level Editor Selection USelection* SelectedActors = GEditor->GetSelectedActors(); int NumAppliedTransform = 0; for ( FSelectionIterator It( *SelectedActors ); It; ++It ) { AActor * Actor = Cast< AActor >( *It ); if ( !Actor ) continue; // Just Ignore the SkySphere... FString ClassName = Actor->GetClass() ? Actor->GetClass()->GetName() : FString(); if ( ClassName == TEXT( "BP_Sky_Sphere_C" ) ) continue; FTransform CurrentTransform = Actor->GetTransform(); ALandscapeProxy* Landscape = Cast< ALandscapeProxy >( Actor ); if ( Landscape ) { // We need to offset Landscape's transform in X/Y to center them properly FVector Origin, Extent; Actor->GetActorBounds(false, Origin, Extent); // Use the origin's XY Position FVector Location = CurrentTransform.GetLocation(); Location.X = Origin.X; Location.Y = Origin.Y; CurrentTransform.SetLocation( Location ); } // Accumulate all the actor transforms... if ( NumAppliedTransform == 0 ) SpawnTransform = CurrentTransform; else SpawnTransform.Accumulate( CurrentTransform ); NumAppliedTransform++; } if ( NumAppliedTransform > 0 ) { // "Mean" all the accumulated Transform SpawnTransform.SetScale3D( FVector::OneVector ); SpawnTransform.NormalizeRotation(); if ( NumAppliedTransform > 1 ) SpawnTransform.SetLocation( SpawnTransform.GetLocation() / (float)NumAppliedTransform ); } } return SpawnTransform; } // Builds the context menu for the selected tool TSharedPtr SHoudiniToolPalette::ConstructHoudiniToolContextMenu() { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked< FAssetRegistryModule >( "AssetRegistry" ); FMenuBuilder MenuBuilder( true, NULL ); TArray< UObject* > AssetArray; if ( ActiveTool.IsValid() && !ActiveTool->HoudiniAsset.IsNull() ) { // Load the asset UObject* AssetObj = ActiveTool->HoudiniAsset.LoadSynchronous(); if( AssetObj ) AssetArray.Add( AssetObj ); } //MenuBuilder.AddMenuEntry(FGlobalEditorCommonCommands::Get().FindInContentBrowser ); FAssetToolsModule & AssetToolsModule = FModuleManager::GetModuleChecked< FAssetToolsModule >( "AssetTools" ); TSharedPtr< IAssetTypeActions > EngineActions = AssetToolsModule.Get().GetAssetTypeActionsForClass( UHoudiniAsset::StaticClass() ).Pin(); if ( EngineActions.IsValid() ) EngineActions->GetActions( AssetArray, MenuBuilder ); // Add HoudiniTools actions MenuBuilder.AddMenuSeparator(); // Remove Tool MenuBuilder.AddMenuEntry( NSLOCTEXT( "HoudiniToolsTypeActions", "HoudiniTool_Remove", "Remove Tool" ), NSLOCTEXT( "HoudiniToolsTypeActions", "HoudiniTool_RemoveTooltip", "Remove the selected tool from the list of Houdini Tools." ), FSlateIcon( FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.HoudiniEngineLogo" ), FUIAction( FExecuteAction::CreateSP(this, &SHoudiniToolPalette::RemoveActiveTool ), FCanExecuteAction::CreateLambda([&] { return IsActiveHoudiniToolEditable(); } ) ) ); // Edit Tool MenuBuilder.AddMenuEntry( NSLOCTEXT( "HoudiniToolsTypeActions", "HoudiniTool_Edit", "Edit Tool Properties" ), NSLOCTEXT( "HoudiniToolsTypeActions", "HoudiniTool_EditTooltip", "Edit the selected tool properties." ), FSlateIcon( FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.HoudiniEngineLogo" ), FUIAction( FExecuteAction::CreateSP(this, &SHoudiniToolPalette::EditActiveHoudiniTool ), FCanExecuteAction::CreateLambda([&] { return IsActiveHoudiniToolEditable(); } ) ) ); // Add HoudiniTools actions MenuBuilder.AddMenuSeparator(); // Generate Missing Tool Descriptions MenuBuilder.AddMenuEntry( NSLOCTEXT("HoudiniToolsTypeActions", "HoudiniTool_GenMissing", "Generate Missing Tool Descriptions"), NSLOCTEXT("HoudiniToolsTypeActions", "HoudiniTool_GenMissingTooltip", "Generates the tool descriptions .json file for HDA that doesnt have one."), FSlateIcon(FHoudiniEngineStyle::GetStyleSetName(), "HoudiniEngine.HoudiniEngineLogo"), FUIAction( FExecuteAction::CreateSP(this, &SHoudiniToolPalette::GenerateMissingJSONFiles ), FCanExecuteAction::CreateLambda([&] { return true; }) ) ); return MenuBuilder.MakeWidget(); } void SHoudiniToolPalette::EditActiveHoudiniTool() { if ( !ActiveTool.IsValid() ) return; if ( !IsActiveHoudiniToolEditable() ) return; // Create a new Tool property object for the property dialog FString ToolName = ActiveTool->Name.ToString(); if ( ActiveTool->HoudiniAsset ) ToolName += TEXT(" (") + ActiveTool->HoudiniAsset->AssetFileName + TEXT(")"); UHoudiniToolProperties* NewToolProperty = NewObject< UHoudiniToolProperties >( GetTransientPackage(), FName( *ToolName ) ); NewToolProperty->AddToRoot(); // Set the default values for this asset NewToolProperty->Name = ActiveTool->Name.ToString(); NewToolProperty->Type = ActiveTool->Type; NewToolProperty->ToolTip = ActiveTool->ToolTipText.ToString(); FName IconResourceName = ActiveTool->Icon->GetResourceName(); NewToolProperty->IconPath.FilePath = IconResourceName.ToString(); NewToolProperty->HelpURL = ActiveTool->HelpURL; NewToolProperty->AssetPath = ActiveTool->SourceAssetPath; NewToolProperty->SelectionType = ActiveTool->SelectionType; TArray ActiveHoudiniTools; ActiveHoudiniTools.Add( NewToolProperty ); // Create a new property editor window TSharedRef< SWindow > Window = CreateFloatingDetailsView( ActiveHoudiniTools ); Window->SetOnWindowClosed( FOnWindowClosed::CreateSP( this, &SHoudiniToolPalette::OnEditActiveHoudiniToolWindowClosed, ActiveHoudiniTools ) ); } void SHoudiniToolPalette::OnEditActiveHoudiniToolWindowClosed( const TSharedRef& InWindow, TArray InObjects ) { // Sanity check, we can only edit one tool at a time! if ( InObjects.Num() != 1 ) return; TArray< FHoudiniTool > EditedToolArray; for ( int32 ObjIdx = 0; ObjIdx < InObjects.Num(); ObjIdx++ ) { UHoudiniToolProperties* ToolProperties = Cast< UHoudiniToolProperties >( InObjects[ ObjIdx ] ); if ( !ToolProperties ) continue; FString IconPath = FPaths::ConvertRelativePathToFull( ToolProperties->IconPath.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" ) ); } // Create a new Houdini Tool from the modified properties FHoudiniTool EditedHoudiniTool( ActiveTool->HoudiniAsset, FText::FromString( ToolProperties->Name ), ToolProperties->Type, ToolProperties->SelectionType, FText::FromString (ToolProperties->ToolTip ), CustomIconBrush, ToolProperties->HelpURL, false, ToolProperties->AssetPath, ActiveTool->ToolDirectory, ActiveTool->JSONFile); EditedToolArray.Add( EditedHoudiniTool ); // Remove the tool from Root ToolProperties->RemoveFromRoot(); } if ( EditedToolArray.Num() != 1 ) return; // Add the new tools to the editor TArray< TSharedPtr >* EditorTools = FHoudiniEngineEditor::Get().GetHoudiniToolsForWrite(); if ( !EditorTools ) return; for ( int32 Idx = 0; Idx < EditedToolArray.Num(); Idx++ ) { FHoudiniTool EditedTool = EditedToolArray[ Idx ]; // Update the editor tool list int32 FoundIndex = -1; bool IsDefault = false; if ( !FHoudiniEngineEditor::Get().FindHoudiniTool( *ActiveTool, FoundIndex, IsDefault ) ) continue; if ( IsDefault ) continue; // Modify the Editor Tool if needed bool bModified = false; TSharedPtr< FHoudiniTool > EditorTool = (*EditorTools)[FoundIndex]; if (!EditorTool->Name.EqualTo(EditedTool.Name)) { EditorTool->Name = EditedTool.Name; bModified = true; } if (EditorTool->Type != EditedTool.Type) { EditorTool->Type = EditedTool.Type; bModified = true; } if (EditorTool->SelectionType != EditedTool.SelectionType) { EditorTool->SelectionType = EditedTool.SelectionType; bModified = true; } if (!EditorTool->ToolTipText.EqualTo(EditedTool.ToolTipText)) { EditorTool->ToolTipText = EditedTool.ToolTipText; bModified = true; } if (EditorTool->Icon && EditedTool.Icon) { if (EditorTool->Icon->GetResourceName().ToString() != EditedTool.Icon->GetResourceName().ToString()) { EditorTool->Icon = EditedTool.Icon; bModified = true; } } if( EditorTool->HelpURL != EditedTool.HelpURL ) { EditorTool->HelpURL = EditedTool.HelpURL; bModified = true; } if (!FPaths::IsSamePath(EditorTool->SourceAssetPath.FilePath, EditedTool.SourceAssetPath.FilePath)) { EditorTool->SourceAssetPath = EditedTool.SourceAssetPath; bModified = true; } EditorTool->DefaultTool = false; // Write the new tool's state to JSON if (bModified) FHoudiniEngineEditor::Get().WriteJSONFromHoudiniTool( EditedTool ); } // Call construct to refresh the shelf SHoudiniToolPalette::FArguments Args; Construct( Args ); } void SHoudiniToolPalette::GenerateMissingJSONFiles() { // Get the current list of directories TArray< FHoudiniToolDirectory > ToolDirectories; FHoudiniEngineEditor::Get().GetHoudiniToolDirectories(FHoudiniEngineEditor::Get().CurrentHoudiniToolDirIndex, ToolDirectories ); // For all selected tool directories for (int32 n = 0; n < ToolDirectories.Num(); n++) { FString ToolDirPath = ToolDirectories[n].Path.Path; // List all the json files in the current directory TArray JSONFiles; IFileManager::Get().FindFiles(JSONFiles, *ToolDirPath, TEXT(".json")); // List all the hda files in the current directory TArray HDAFiles; IFileManager::Get().FindFiles(HDAFiles, *ToolDirPath, TEXT(".hda")); // For each HDA, try to find a corresponding json file // If we find one, remove the hda from the array for ( int32 HDAIndex = HDAFiles.Num() - 1; HDAIndex >= 0; HDAIndex-- ) { FString HDABaseName = FPaths::GetBaseFilename( HDAFiles[ HDAIndex ] ); for (auto CurrentJSON : JSONFiles) { FString JSONBaseName = FPaths::GetBaseFilename( CurrentJSON ); if ( JSONBaseName.Equals( HDABaseName ) ) HDAFiles.RemoveAt( HDAIndex ); } } // Generate a default JSON file for each remaining HDA for ( auto CurrentHDA : HDAFiles ) { FString HDABaseName = FPaths::GetBaseFilename( CurrentHDA ); FHoudiniTool DefaultTool; DefaultTool.Name = FText::FromString( HDABaseName ); DefaultTool.ToolTipText = FText::FromString( FString() ); DefaultTool.Icon = nullptr; DefaultTool.HelpURL = FString(); DefaultTool.Type = EHoudiniToolType::HTOOLTYPE_OPERATOR_SINGLE; DefaultTool.DefaultTool = false; DefaultTool.SelectionType = EHoudiniToolSelectionType::HTOOL_SELECTION_ALL; DefaultTool.SourceAssetPath.FilePath = ToolDirPath / CurrentHDA; DefaultTool.ToolDirectory = ToolDirectories[ n ]; DefaultTool.JSONFile = HDABaseName + TEXT(".json"); FHoudiniEngineEditor::Get().WriteJSONFromHoudiniTool( DefaultTool ); } } // Trigger a refresh of the list OnDirectoryChange( CurrentHoudiniToolDir ); } TSharedRef SHoudiniToolPalette::CreateFloatingDetailsView( const TArray< UObject* >& InObjects ) { TSharedRef NewSlateWindow = SNew(SWindow) .Title(NSLOCTEXT("PropertyEditor", "WindowTitle", "Houdini Tools Property Editor")) .ClientSize(FVector2D(400, 550)); // If the main frame exists parent the window to it TSharedPtr< SWindow > ParentWindow; if ( FModuleManager::Get().IsModuleLoaded("MainFrame") ) { IMainFrameModule& MainFrame = FModuleManager::GetModuleChecked("MainFrame"); ParentWindow = MainFrame.GetParentWindow(); } if ( ParentWindow.IsValid() ) { // Parent the window to the main frame FSlateApplication::Get().AddWindowAsNativeChild( NewSlateWindow, ParentWindow.ToSharedRef() ); } else { FSlateApplication::Get().AddWindow( NewSlateWindow ); } FDetailsViewArgs Args; Args.bHideSelectionTip = true; Args.bLockable = false; Args.bAllowMultipleTopLevelObjects = true; Args.ViewIdentifier = TEXT("Houdini Tools Properties"); Args.NameAreaSettings = FDetailsViewArgs::HideNameArea; Args.bShowPropertyMatrixButton = false; Args.bShowOptions = false; Args.bShowModifiedPropertiesOption = false; Args.bShowActorLabel = false; FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked("PropertyEditor"); TSharedRef DetailView = PropertyEditorModule.CreateDetailView( Args ); DetailView->SetObjects( InObjects ); NewSlateWindow->SetContent( SNew( SBorder ) .BorderImage( FEditorStyle::GetBrush( TEXT("PropertyWindow.WindowBorder") ) ) [ DetailView ] ); return NewSlateWindow; } FReply SHoudiniToolPalette::OnKeyDown( const FGeometry& InGeometry, const FKeyEvent& InKeyEvent ) { if ( InKeyEvent.GetKey() == EKeys::Delete ) { RemoveActiveTool(); return FReply::Handled(); } else if ( InKeyEvent.GetKey() == EKeys::Enter ) { EditActiveHoudiniTool(); return FReply::Handled(); } else if (InKeyEvent.GetKey() == EKeys::F5 ) { OnDirectoryChange( CurrentHoudiniToolDir ); return FReply::Handled(); } return FReply::Unhandled(); } void SHoudiniToolPalette::RemoveActiveTool() { if ( !ActiveTool.IsValid() ) return; // Remove the tool from the editor list TArray< TSharedPtr >* EditorTools = FHoudiniEngineEditor::Get().GetHoudiniToolsForWrite(); if ( !EditorTools ) return; int32 EditorIndex = -1; bool IsDefaultTool = false; if ( !FHoudiniEngineEditor::Get().FindHoudiniTool( *ActiveTool, EditorIndex, IsDefaultTool ) ) return; if ( IsDefaultTool ) return; EditorTools->RemoveAt( EditorIndex ); // Delete the tool's JSON file to remove it FString ToolJSONFilePath = ActiveTool->ToolDirectory.Path.Path / ActiveTool->JSONFile; if ( FPaths::FileExists(ToolJSONFilePath) ) { FPlatformFileManager::Get().GetPlatformFile().DeleteFile(*ToolJSONFilePath); } // Call construct to refresh the shelf SHoudiniToolPalette::FArguments Args; Construct( Args ); } bool SHoudiniToolPalette::IsActiveHoudiniToolEditable() { int32 FoundIndex = -1; bool IsDefault = false; if ( !ActiveTool.IsValid() ) return false; if ( !FHoudiniEngineEditor::Get().FindHoudiniTool( *ActiveTool, FoundIndex, IsDefault ) ) return false; return !IsDefault; } FReply SHoudiniToolPalette::OnEditToolDirectories() { // Append all the custom tools directory from the runtime settings const UHoudiniRuntimeSettings * HoudiniRuntimeSettings = GetDefault< UHoudiniRuntimeSettings >(); if (!HoudiniRuntimeSettings) return FReply::Handled(); TArray NewCustomHoudiniToolDirs; FString CustomToolDirs = TEXT("CustomToolDir"); UHoudiniToolDirectoryProperties* NewToolDirProperty = NewObject< UHoudiniToolDirectoryProperties >(GetTransientPackage(), FName(*CustomToolDirs)); NewToolDirProperty->CustomHoudiniToolsDirectories = HoudiniRuntimeSettings->CustomHoudiniToolsLocation; NewCustomHoudiniToolDirs.Add(NewToolDirProperty); TSharedRef< SWindow > Window = CreateFloatingDetailsView(NewCustomHoudiniToolDirs); Window->SetOnWindowClosed(FOnWindowClosed::CreateSP(this, &SHoudiniToolPalette::OnEditToolDirectoriesWindowClosed, NewCustomHoudiniToolDirs)); return FReply::Handled(); } void SHoudiniToolPalette::OnEditToolDirectoriesWindowClosed(const TSharedRef& InWindow, TArray InObjects) { TArray EditedToolDirectories; for (int32 ObjIdx = 0; ObjIdx < InObjects.Num(); ObjIdx++) { UHoudiniToolDirectoryProperties* ToolDirProperties = Cast< UHoudiniToolDirectoryProperties >(InObjects[ObjIdx]); if (!ToolDirProperties) continue; for ( auto CurrentToolDir : ToolDirProperties->CustomHoudiniToolsDirectories ) { EditedToolDirectories.AddUnique( CurrentToolDir ); } // Remove the tool directory from Root ToolDirProperties->RemoveFromRoot(); } // Get the runtime settings to add the new custom tools there UHoudiniRuntimeSettings * HoudiniRuntimeSettings = const_cast(GetDefault< UHoudiniRuntimeSettings >()); if (!HoudiniRuntimeSettings) return; // Check if there's been any modification bool bModified = false; if ( EditedToolDirectories.Num() != HoudiniRuntimeSettings->CustomHoudiniToolsLocation.Num() ) { bModified = true; } else { // Same number of directory, look for a value change for ( int32 n = 0; n < EditedToolDirectories.Num(); n++ ) { if (EditedToolDirectories[n] != HoudiniRuntimeSettings->CustomHoudiniToolsLocation[n]) { bModified = true; break; } } } if ( !bModified ) return; // Replace the Tool dir in the runtime settings with the edited list HoudiniRuntimeSettings->CustomHoudiniToolsLocation.Empty(); for ( auto EditedToolDir : EditedToolDirectories ) HoudiniRuntimeSettings->CustomHoudiniToolsLocation.AddUnique( EditedToolDir ); // Save the Settings file to keep the new tools upon restart HoudiniRuntimeSettings->SaveConfig(); // Call construct to refresh the shelf SHoudiniToolPalette::FArguments Args; Construct(Args); } bool SHoudiniToolPalette::GetCurrentDirectoryIndex(int32& SelectedIndex ) const { if ( CurrentHoudiniToolDir.IsEmpty() ) return false; for (int32 n = 0; n < HoudiniToolDirArray.Num(); n++) { if (!HoudiniToolDirArray[n].IsValid()) continue; if (!HoudiniToolDirArray[n]->Equals(CurrentHoudiniToolDir)) continue; SelectedIndex = (n - 1); /* FHoudiniToolDirectory EditorToolDir; FHoudiniEngineEditor::Get().GetHoudiniToolDirectories(FoundIndex, EditorToolDir); if ( EditorToolDir.Name.Equals(CurrentHoudiniToolDir) ) return true; */ return true; } return false; } void SHoudiniToolPalette::OnDirectoryChange(const FString& NewDir) { if ( NewDir.IsEmpty() ) return; CurrentHoudiniToolDir = NewDir; int32 DirIndex = 0; if ( GetCurrentDirectoryIndex( DirIndex ) ) { FHoudiniEngineEditor::Get().UpdateHoudiniToolList( DirIndex ); HoudiniToolListView->RequestListRefresh(); } } void SHoudiniToolPalette::UpdateHoudiniToolDirectories() { int32 ChoiceIndex = FHoudiniEngineEditor::Get().CurrentHoudiniToolDirIndex; // Refresh the Editor's Houdini Tool list FHoudiniEngineEditor::Get().UpdateHoudiniToolList( ChoiceIndex ); // Add the directory choice lists HoudiniToolDirArray.Empty(); TArray ToolDirArray; FHoudiniEngineEditor::Get().GetAllHoudiniToolDirectories( ToolDirArray ); { FString * ChoiceLabel = new FString(TEXT("All")); HoudiniToolDirArray.Add(TSharedPtr< FString >(ChoiceLabel)); if (ChoiceIndex < 0 ) CurrentHoudiniToolDir = *ChoiceLabel; } { FString * ChoiceLabel = new FString(TEXT("Default")); HoudiniToolDirArray.Add(TSharedPtr< FString >(ChoiceLabel)); if ( ChoiceIndex == 0 ) CurrentHoudiniToolDir = *ChoiceLabel; } for (int32 n = 1; n < ToolDirArray.Num(); n++) { FString * ChoiceLabel = new FString( ToolDirArray[n].Name ); HoudiniToolDirArray.Add(TSharedPtr< FString >(ChoiceLabel)); if ( ChoiceIndex == n ) CurrentHoudiniToolDir = *ChoiceLabel; } } FText SHoudiniToolPalette::OnGetSelectedDirText() const { return FText::FromString(CurrentHoudiniToolDir); } #undef LOCTEXT_NAMESPACE