2331 lines
92 KiB
C++
2331 lines
92 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 "HoudiniAssetComponentDetails.h"
|
|
|
|
#include "HoudiniApi.h"
|
|
#include "HoudiniEngineEditorPrivatePCH.h"
|
|
#include "HoudiniEngine.h"
|
|
#include "HoudiniEngineUtils.h"
|
|
#include "HoudiniEngineBakeUtils.h"
|
|
#include "HoudiniAsset.h"
|
|
#include "HoudiniAssetComponent.h"
|
|
#include "HoudiniAssetLogWidget.h"
|
|
#include "HoudiniEngineString.h"
|
|
#include "HoudiniParameterDetails.h"
|
|
#include "HoudiniAssetInput.h"
|
|
#include "HoudiniAssetInstanceInput.h"
|
|
|
|
#include "Components/InstancedStaticMeshComponent.h"
|
|
#include "ContentBrowserModule.h"
|
|
#include "Editor/PropertyEditor/Public/PropertyCustomizationHelpers.h"
|
|
#include "DetailWidgetRow.h"
|
|
#include "Editor.h"
|
|
#include "IContentBrowserSingleton.h"
|
|
#include "Landscape.h"
|
|
#include "SAssetDropTarget.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "Widgets/Input/SCheckBox.h"
|
|
#include "Widgets/Input/SEditableTextBox.h"
|
|
#include "Widgets/Layout/SSeparator.h"
|
|
#include "Widgets/Layout/SBox.h"
|
|
#include "Widgets/Images/SImage.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Materials/Material.h"
|
|
|
|
#include "HoudiniEngineRuntimePrivatePCH.h"
|
|
#include "Internationalization/Internationalization.h"
|
|
#include "DetailLayoutBuilder.h"
|
|
#include "IDetailGroup.h"
|
|
|
|
|
|
#define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE
|
|
|
|
uint32
|
|
GetTypeHash( TPair< UStaticMesh *, int32 > Pair )
|
|
{
|
|
return PointerHash( Pair.Key, Pair.Value );
|
|
}
|
|
|
|
uint32
|
|
GetTypeHash( TPair< ALandscape *, int32 > Pair )
|
|
{
|
|
return PointerHash(Pair.Key, Pair.Value);
|
|
}
|
|
|
|
uint32
|
|
GetTypeHash(TPair< ALandscapeProxy *, int32 > Pair)
|
|
{
|
|
return PointerHash(Pair.Key, Pair.Value);
|
|
}
|
|
|
|
TSharedRef< IDetailCustomization >
|
|
FHoudiniAssetComponentDetails::MakeInstance()
|
|
{
|
|
return MakeShareable( new FHoudiniAssetComponentDetails );
|
|
}
|
|
|
|
FHoudiniAssetComponentDetails::FHoudiniAssetComponentDetails()
|
|
{
|
|
|
|
}
|
|
|
|
|
|
FHoudiniAssetComponentDetails::~FHoudiniAssetComponentDetails()
|
|
{
|
|
|
|
}
|
|
|
|
void
|
|
FHoudiniAssetComponentDetails::CustomizeDetails( IDetailLayoutBuilder & DetailBuilder )
|
|
{
|
|
// Get all components which are being customized.
|
|
TArray< TWeakObjectPtr< UObject > > ObjectsCustomized;
|
|
DetailBuilder.GetObjectsBeingCustomized( ObjectsCustomized );
|
|
|
|
// See if we need to enable baking option.
|
|
for ( int32 i = 0; i < ObjectsCustomized.Num(); ++i )
|
|
{
|
|
if ( ObjectsCustomized[ i ].IsValid() )
|
|
{
|
|
UObject * Object = ObjectsCustomized[ i ].Get();
|
|
if( Object )
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = Cast< UHoudiniAssetComponent >( Object );
|
|
HoudiniAssetComponents.Add( HoudiniAssetComponent );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create Houdini parameters.
|
|
{
|
|
IDetailCategoryBuilder & DetailCategoryBuilder =
|
|
DetailBuilder.EditCategory( "HoudiniParameters", FText::GetEmpty(), ECategoryPriority::Important );
|
|
|
|
// If we are running Houdini Engine Indie license, we need to display special label.
|
|
if ( FHoudiniEngineUtils::IsLicenseHoudiniEngineIndie() )
|
|
{
|
|
FText ParameterLabelText =
|
|
FText::FromString( TEXT( "Houdini Engine Indie - For Limited Commercial Use Only" ) );
|
|
|
|
FSlateFontInfo LargeDetailsFont = IDetailLayoutBuilder::GetDetailFontBold();
|
|
LargeDetailsFont.Size += 2;
|
|
|
|
FSlateColor LabelColor = FLinearColor( 1.0f, 1.0f, 0.0f, 1.0f );
|
|
|
|
DetailCategoryBuilder.AddCustomRow( FText::GetEmpty() )
|
|
[
|
|
SNew( STextBlock )
|
|
.Text( ParameterLabelText )
|
|
.ToolTipText( ParameterLabelText )
|
|
.Font( LargeDetailsFont )
|
|
.Justification( ETextJustify::Center )
|
|
.ColorAndOpacity( LabelColor )
|
|
];
|
|
|
|
DetailCategoryBuilder.AddCustomRow( FText::GetEmpty() )
|
|
[
|
|
SNew( SVerticalBox )
|
|
+SVerticalBox::Slot()
|
|
.Padding( 0, 0, 5, 0 )
|
|
[
|
|
SNew( SSeparator )
|
|
.Thickness( 2.0f )
|
|
]
|
|
];
|
|
}
|
|
|
|
for ( TArray< UHoudiniAssetComponent * >::TIterator
|
|
IterComponents( HoudiniAssetComponents ); IterComponents; ++IterComponents )
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents;
|
|
if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() )
|
|
continue;
|
|
|
|
for ( TMap< HAPI_ParmId, UHoudiniAssetParameter * >::TIterator
|
|
IterParams( HoudiniAssetComponent->Parameters ); IterParams; ++IterParams )
|
|
{
|
|
// We only want to create root parameters here, they will recursively create child parameters.
|
|
UHoudiniAssetParameter * HoudiniAssetParameter = IterParams.Value();
|
|
if (!HoudiniAssetParameter || HoudiniAssetParameter->IsPendingKill() || HoudiniAssetParameter->IsChildParameter())
|
|
continue;
|
|
|
|
// ensure the parameter is properly owned by a HAC
|
|
const UHoudiniAssetComponent* Owner = HoudiniAssetParameter->GetHoudiniAssetComponent();
|
|
if (!Owner || Owner->IsPendingKill())
|
|
continue;
|
|
|
|
FHoudiniParameterDetails::CreateWidget( DetailCategoryBuilder, HoudiniAssetParameter );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create Houdini Inputs.
|
|
{
|
|
IDetailCategoryBuilder & DetailCategoryBuilder = DetailBuilder.EditCategory(
|
|
"HoudiniInputs", FText::GetEmpty(), ECategoryPriority::Important );
|
|
for ( TArray< UHoudiniAssetComponent * >::TIterator
|
|
IterComponents( HoudiniAssetComponents ); IterComponents; ++IterComponents )
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents;
|
|
if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() )
|
|
continue;
|
|
|
|
for ( TArray< UHoudiniAssetInput * >::TIterator
|
|
IterInputs( HoudiniAssetComponent->Inputs ); IterInputs; ++IterInputs )
|
|
{
|
|
UHoudiniAssetInput * HoudiniAssetInput = *IterInputs;
|
|
if( HoudiniAssetInput && !HoudiniAssetInput->IsPendingKill() )
|
|
{
|
|
FHoudiniParameterDetails::CreateWidget( DetailCategoryBuilder, HoudiniAssetInput );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create Houdini Instanced Inputs category.
|
|
{
|
|
IDetailCategoryBuilder & DetailCategoryBuilder = DetailBuilder.EditCategory(
|
|
"HoudiniInstancedInputs", FText::GetEmpty(), ECategoryPriority::Important );
|
|
for ( TArray< UHoudiniAssetComponent * >::TIterator
|
|
IterComponents( HoudiniAssetComponents ); IterComponents; ++IterComponents )
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents;
|
|
if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() )
|
|
continue;
|
|
|
|
for ( auto& InstanceInput : HoudiniAssetComponent->InstanceInputs )
|
|
{
|
|
if ( InstanceInput && !InstanceInput->IsPendingKill() )
|
|
{
|
|
FHoudiniParameterDetails::CreateWidget( DetailCategoryBuilder, InstanceInput );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create Houdini Asset category.
|
|
{
|
|
IDetailCategoryBuilder & DetailCategoryBuilder =
|
|
DetailBuilder.EditCategory( "HoudiniAsset", FText::GetEmpty(), ECategoryPriority::Important );
|
|
CreateHoudiniAssetWidget( DetailCategoryBuilder );
|
|
}
|
|
|
|
// Create category for generated static meshes and their materials.
|
|
{
|
|
IDetailCategoryBuilder & DetailCategoryBuilder =
|
|
DetailBuilder.EditCategory( "HoudiniGeneratedMeshes", FText::GetEmpty(), ECategoryPriority::Important );
|
|
CreateStaticMeshAndMaterialWidgets( DetailCategoryBuilder );
|
|
}
|
|
|
|
// Create Houdini Generated Static mesh settings category.
|
|
DetailBuilder.EditCategory( "HoudiniGeneratedStaticMeshSettings", FText::GetEmpty(), ECategoryPriority::Important );
|
|
}
|
|
|
|
void
|
|
FHoudiniAssetComponentDetails::CreateStaticMeshAndMaterialWidgets( IDetailCategoryBuilder & DetailCategoryBuilder )
|
|
{
|
|
StaticMeshThumbnailBorders.Empty();
|
|
LandscapeThumbnailBorders.Empty();
|
|
MaterialInterfaceComboButtons.Empty();
|
|
MaterialInterfaceThumbnailBorders.Empty();
|
|
|
|
// Get thumbnail pool for this builder.
|
|
IDetailLayoutBuilder & DetailLayoutBuilder = DetailCategoryBuilder.GetParentLayout();
|
|
TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool();
|
|
|
|
int32 NumberOfGeneratedMeshes = 0;
|
|
for ( TArray< UHoudiniAssetComponent * >::TIterator
|
|
IterComponents( HoudiniAssetComponents ); IterComponents; ++IterComponents)
|
|
{
|
|
int32 MeshIdx = 0;
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents;
|
|
if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() )
|
|
continue;
|
|
|
|
for ( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator
|
|
IterMeshes( HoudiniAssetComponent->StaticMeshes ); IterMeshes; ++IterMeshes )
|
|
{
|
|
UStaticMesh * StaticMesh = IterMeshes.Value();
|
|
FHoudiniGeoPartObject & HoudiniGeoPartObject = IterMeshes.Key();
|
|
|
|
if ( !StaticMesh || StaticMesh->IsPendingKill() )
|
|
continue;
|
|
|
|
NumberOfGeneratedMeshes++;
|
|
|
|
FString Label = HoudiniAssetComponent->GetBakingBaseName( HoudiniGeoPartObject);
|
|
|
|
// Create thumbnail for this mesh.
|
|
TSharedPtr< FAssetThumbnail > StaticMeshThumbnail =
|
|
MakeShareable( new FAssetThumbnail( StaticMesh, 64, 64, AssetThumbnailPool ) );
|
|
|
|
TSharedPtr< SBorder > StaticMeshThumbnailBorder;
|
|
TSharedRef< SVerticalBox > VerticalBox = SNew( SVerticalBox );
|
|
|
|
IDetailGroup& StaticMeshGrp = DetailCategoryBuilder.AddGroup(FName(*Label), FText::FromString(Label));
|
|
|
|
StaticMeshGrp.AddWidgetRow()
|
|
.NameContent()
|
|
[
|
|
SNew( STextBlock )
|
|
.Text( LOCTEXT( "BakeBaseName", "Bake Name" ) )
|
|
.Font( IDetailLayoutBuilder::GetDetailFont() )
|
|
]
|
|
.ValueContent()
|
|
.MinDesiredWidth( HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH )
|
|
[
|
|
SNew( SHorizontalBox )
|
|
+SHorizontalBox::Slot()
|
|
.Padding( 2.0f, 0.0f )
|
|
.VAlign( VAlign_Center )
|
|
.FillWidth( 1 )
|
|
[
|
|
SNew( SEditableTextBox )
|
|
.Text( FText::FromString(Label) )
|
|
.Font( IDetailLayoutBuilder::GetDetailFont() )
|
|
.OnTextCommitted( this, &FHoudiniAssetComponentDetails::OnBakeNameCommited, HoudiniAssetComponent, HoudiniGeoPartObject )
|
|
.ToolTipText( LOCTEXT( "BakeNameTip", "The base name of the baked asset") )
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.Padding( 2.0f, 0.0f )
|
|
.VAlign( VAlign_Center )
|
|
.AutoWidth()
|
|
[
|
|
SNew( SButton )
|
|
.ToolTipText( LOCTEXT( "RevertNameOverride", "Revert bake name override" ) )
|
|
.ButtonStyle( FEditorStyle::Get(), "NoBorder" )
|
|
.ContentPadding( 0 )
|
|
.Visibility( EVisibility::Visible )
|
|
.OnClicked( this, &FHoudiniAssetComponentDetails::OnRemoveBakingBaseNameOverride, HoudiniAssetComponent, HoudiniGeoPartObject )
|
|
[
|
|
SNew( SImage )
|
|
.Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) )
|
|
]
|
|
]
|
|
];
|
|
|
|
FString MeshLabel = TEXT( "Static Mesh" );
|
|
if( HoudiniGeoPartObject.bHasCollisionBeenAdded )
|
|
{
|
|
int32 NumColliders = 1;
|
|
if ( StaticMesh->BodySetup && !StaticMesh->BodySetup->IsPendingKill() )
|
|
NumColliders = StaticMesh->BodySetup->AggGeom.GetElementCount();
|
|
|
|
MeshLabel += TEXT( "\n(") + FString::FromInt( NumColliders ) + TEXT(" Simple Collider" );
|
|
if ( NumColliders > 1 )
|
|
MeshLabel += TEXT("s");
|
|
MeshLabel += TEXT(")");
|
|
}
|
|
else if( HoudiniGeoPartObject.bIsRenderCollidable )
|
|
{
|
|
MeshLabel += TEXT( "\n(Rendered Complex Collider)" );
|
|
}
|
|
else if( HoudiniGeoPartObject.bIsCollidable )
|
|
{
|
|
MeshLabel += TEXT( "\n(Invisible Complex Collider)" );
|
|
}
|
|
|
|
if ( StaticMesh->GetNumLODs() > 1 )
|
|
MeshLabel += TEXT("\n(") + FString::FromInt( StaticMesh->GetNumLODs() ) + TEXT(" LODs)");
|
|
|
|
if ( StaticMesh->Sockets.Num() > 0 )
|
|
MeshLabel += TEXT("\n(") + FString::FromInt( StaticMesh->Sockets.Num() ) + TEXT(" sockets)");
|
|
|
|
StaticMeshGrp.AddWidgetRow()
|
|
.NameContent()
|
|
[
|
|
SNew( STextBlock )
|
|
.Text( FText::FromString(MeshLabel) )
|
|
.Font( IDetailLayoutBuilder::GetDetailFont() )
|
|
]
|
|
.ValueContent()
|
|
.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH)
|
|
[
|
|
VerticalBox
|
|
];
|
|
|
|
VerticalBox->AddSlot().Padding( 0, 2 ).AutoHeight()
|
|
[
|
|
SNew( SHorizontalBox )
|
|
+SHorizontalBox::Slot()
|
|
.Padding( 0.0f, 0.0f, 2.0f, 0.0f )
|
|
.AutoWidth()
|
|
[
|
|
SAssignNew( StaticMeshThumbnailBorder, SBorder )
|
|
.Padding( 5.0f )
|
|
.BorderImage( this, &FHoudiniAssetComponentDetails::GetStaticMeshThumbnailBorder, StaticMesh )
|
|
.OnMouseDoubleClick( this, &FHoudiniAssetComponentDetails::OnThumbnailDoubleClick, (UObject *) StaticMesh )
|
|
[
|
|
SNew( SBox )
|
|
.WidthOverride( 64 )
|
|
.HeightOverride( 64 )
|
|
.ToolTipText( FText::FromString( StaticMesh->GetPathName() ) )
|
|
[
|
|
StaticMeshThumbnail->MakeThumbnailWidget()
|
|
]
|
|
]
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.FillWidth( 1.0f )
|
|
.Padding( 0.0f, 4.0f, 4.0f, 4.0f )
|
|
.VAlign( VAlign_Center )
|
|
[
|
|
SNew( SVerticalBox )
|
|
+SVerticalBox::Slot()
|
|
[
|
|
SNew( SHorizontalBox )
|
|
+SHorizontalBox::Slot()
|
|
.MaxWidth( 80.0f )
|
|
[
|
|
SNew( SButton )
|
|
.VAlign( VAlign_Center )
|
|
.HAlign( HAlign_Center )
|
|
.Text( LOCTEXT( "Bake", "Bake" ) )
|
|
.OnClicked( this, &FHoudiniAssetComponentDetails::OnBakeStaticMesh, StaticMesh, HoudiniAssetComponent )
|
|
.ToolTipText( LOCTEXT( "HoudiniStaticMeshBakeButton", "Bake this generated static mesh" ) )
|
|
]
|
|
]
|
|
]
|
|
];
|
|
|
|
// Store thumbnail for this mesh.
|
|
StaticMeshThumbnailBorders.Add( StaticMesh, StaticMeshThumbnailBorder );
|
|
|
|
// We need to add material box for each material present in this static mesh.
|
|
auto & StaticMeshMaterials = StaticMesh->StaticMaterials;
|
|
for ( int32 MaterialIdx = 0; MaterialIdx < StaticMeshMaterials.Num(); ++MaterialIdx )
|
|
{
|
|
UMaterialInterface * MaterialInterface = StaticMeshMaterials[ MaterialIdx ].MaterialInterface;
|
|
TSharedPtr< SBorder > MaterialThumbnailBorder;
|
|
TSharedPtr< SHorizontalBox > HorizontalBox = NULL;
|
|
|
|
FString MaterialName, MaterialPathName;
|
|
if ( MaterialInterface && !MaterialInterface->IsPendingKill()
|
|
&& MaterialInterface->GetOuter() && !MaterialInterface->GetOuter()->IsPendingKill() )
|
|
{
|
|
MaterialName = MaterialInterface->GetName();
|
|
MaterialPathName = MaterialInterface->GetPathName();
|
|
}
|
|
else
|
|
{
|
|
MaterialInterface = nullptr;
|
|
MaterialName = TEXT("Material (invalid)") + FString::FromInt( MaterialIdx ) ;
|
|
MaterialPathName = TEXT("Material (invalid)") + FString::FromInt(MaterialIdx);
|
|
}
|
|
|
|
// Create thumbnail for this material.
|
|
TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail =
|
|
MakeShareable( new FAssetThumbnail( MaterialInterface, 64, 64, AssetThumbnailPool ) );
|
|
|
|
VerticalBox->AddSlot().Padding( 0, 2 )
|
|
[
|
|
SNew( SAssetDropTarget )
|
|
.OnIsAssetAcceptableForDrop( this, &FHoudiniAssetComponentDetails::OnMaterialInterfaceDraggedOver )
|
|
.OnAssetDropped(
|
|
this, &FHoudiniAssetComponentDetails::OnMaterialInterfaceDropped,
|
|
StaticMesh, &HoudiniGeoPartObject, MaterialIdx )
|
|
[
|
|
SAssignNew( HorizontalBox, SHorizontalBox )
|
|
]
|
|
];
|
|
|
|
HorizontalBox->AddSlot().Padding( 0.0f, 0.0f, 2.0f, 0.0f ).AutoWidth()
|
|
[
|
|
SAssignNew( MaterialThumbnailBorder, SBorder )
|
|
.Padding( 5.0f )
|
|
.BorderImage(
|
|
this, &FHoudiniAssetComponentDetails::GetMaterialInterfaceThumbnailBorder, StaticMesh, MaterialIdx )
|
|
.OnMouseDoubleClick(
|
|
this, &FHoudiniAssetComponentDetails::OnThumbnailDoubleClick, (UObject *) MaterialInterface )
|
|
[
|
|
SNew( SBox )
|
|
.WidthOverride( 64 )
|
|
.HeightOverride( 64 )
|
|
.ToolTipText( FText::FromString( MaterialPathName ) )
|
|
[
|
|
MaterialInterfaceThumbnail->MakeThumbnailWidget()
|
|
]
|
|
]
|
|
];
|
|
|
|
// Store thumbnail for this mesh and material index.
|
|
{
|
|
TPairInitializer< UStaticMesh *, int32 > Pair( StaticMesh, MaterialIdx );
|
|
MaterialInterfaceThumbnailBorders.Add( Pair, MaterialThumbnailBorder );
|
|
}
|
|
|
|
TSharedPtr< SComboButton > AssetComboButton;
|
|
TSharedPtr< SHorizontalBox > ButtonBox;
|
|
|
|
HorizontalBox->AddSlot()
|
|
.FillWidth( 1.0f )
|
|
.Padding( 0.0f, 4.0f, 4.0f, 4.0f )
|
|
.VAlign( VAlign_Center )
|
|
[
|
|
SNew( SVerticalBox )
|
|
+SVerticalBox::Slot()
|
|
.HAlign( HAlign_Fill )
|
|
[
|
|
SAssignNew( ButtonBox, SHorizontalBox )
|
|
+SHorizontalBox::Slot()
|
|
[
|
|
SAssignNew( AssetComboButton, SComboButton )
|
|
//.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip )
|
|
.ButtonStyle( FEditorStyle::Get(), "PropertyEditor.AssetComboStyle" )
|
|
.ForegroundColor( FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity" ) )
|
|
.OnGetMenuContent( this, &FHoudiniAssetComponentDetails::OnGetMaterialInterfaceMenuContent,
|
|
MaterialInterface, StaticMesh, &HoudiniGeoPartObject, MaterialIdx )
|
|
.ContentPadding( 2.0f )
|
|
.ButtonContent()
|
|
[
|
|
SNew( STextBlock )
|
|
.TextStyle( FEditorStyle::Get(), "PropertyEditor.AssetClass" )
|
|
.Font( FEditorStyle::GetFontStyle( FName( TEXT( "PropertyWindow.NormalFont" ) ) ) )
|
|
.Text( FText::FromString( MaterialName ) )
|
|
]
|
|
]
|
|
]
|
|
];
|
|
|
|
// Create tooltip.
|
|
FFormatNamedArguments Args;
|
|
Args.Add( TEXT( "Asset" ), FText::FromString( MaterialName ) );
|
|
FText MaterialTooltip = FText::Format(
|
|
LOCTEXT( "BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser" ), Args );
|
|
|
|
ButtonBox->AddSlot()
|
|
.AutoWidth()
|
|
.Padding( 2.0f, 0.0f )
|
|
.VAlign( VAlign_Center )
|
|
[
|
|
PropertyCustomizationHelpers::MakeBrowseButton(
|
|
FSimpleDelegate::CreateSP(
|
|
this, &FHoudiniAssetComponentDetails::OnMaterialInterfaceBrowse, MaterialInterface ),
|
|
TAttribute< FText >( MaterialTooltip ) )
|
|
];
|
|
|
|
ButtonBox->AddSlot()
|
|
.AutoWidth()
|
|
.Padding( 2.0f, 0.0f )
|
|
.VAlign( VAlign_Center )
|
|
[
|
|
SNew( SButton )
|
|
.ToolTipText( LOCTEXT( "ResetToBaseMaterial", "Reset to base material" ) )
|
|
.ButtonStyle( FEditorStyle::Get(), "NoBorder" )
|
|
.ContentPadding( 0 )
|
|
.Visibility( EVisibility::Visible )
|
|
.OnClicked(
|
|
this, &FHoudiniAssetComponentDetails::OnResetMaterialInterfaceClicked,
|
|
StaticMesh, &HoudiniGeoPartObject, MaterialIdx )
|
|
[
|
|
SNew( SImage )
|
|
.Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) )
|
|
]
|
|
];
|
|
|
|
// Store combo button for this mesh and index.
|
|
{
|
|
TPairInitializer< UStaticMesh *, int32 > Pair( StaticMesh, MaterialIdx );
|
|
MaterialInterfaceComboButtons.Add( Pair, AssetComboButton );
|
|
}
|
|
}
|
|
|
|
MeshIdx++;
|
|
}
|
|
|
|
// Do the same for the Landscape components
|
|
for (TMap< FHoudiniGeoPartObject, TWeakObjectPtr<ALandscapeProxy> >::TIterator
|
|
IterLandscapes(HoudiniAssetComponent->LandscapeComponents); IterLandscapes; ++IterLandscapes)
|
|
{
|
|
ALandscapeProxy * Landscape = IterLandscapes.Value().Get();
|
|
FHoudiniGeoPartObject & HoudiniGeoPartObject = IterLandscapes.Key();
|
|
|
|
if (!Landscape || !Landscape->IsValidLowLevel() )
|
|
continue;
|
|
|
|
NumberOfGeneratedMeshes++;
|
|
|
|
FString Label = TEXT("");
|
|
if (HoudiniGeoPartObject.HasCustomName())
|
|
Label = HoudiniGeoPartObject.PartName;
|
|
else
|
|
Label = Landscape->GetName();
|
|
|
|
// Create thumbnail for this landscape.
|
|
TSharedPtr< FAssetThumbnail > LandscapeThumbnail =
|
|
MakeShareable(new FAssetThumbnail(Landscape, 64, 64, AssetThumbnailPool));
|
|
|
|
TSharedPtr< SBorder > LandscapeThumbnailBorder;
|
|
TSharedRef< SVerticalBox > VerticalBox = SNew(SVerticalBox);
|
|
|
|
IDetailGroup& LandscapeGrp = DetailCategoryBuilder.AddGroup(FName(*Label), FText::FromString(Label));
|
|
LandscapeGrp.AddWidgetRow()
|
|
.NameContent()
|
|
[
|
|
SNew(SSpacer)
|
|
.Size(FVector2D(250, 64))
|
|
]
|
|
.ValueContent()
|
|
.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH)
|
|
[
|
|
VerticalBox
|
|
];
|
|
|
|
VerticalBox->AddSlot().Padding(0, 2).AutoHeight()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(0.0f, 0.0f, 2.0f, 0.0f)
|
|
.AutoWidth()
|
|
[
|
|
SAssignNew(LandscapeThumbnailBorder, SBorder)
|
|
.Padding(5.0f)
|
|
.BorderImage(this, &FHoudiniAssetComponentDetails::GetLandscapeThumbnailBorder, Landscape)
|
|
.OnMouseDoubleClick(this, &FHoudiniAssetComponentDetails::OnThumbnailDoubleClick, (UObject *)Landscape)
|
|
[
|
|
SNew(SBox)
|
|
.WidthOverride(64)
|
|
.HeightOverride(64)
|
|
.ToolTipText(FText::FromString(Landscape->GetPathName()))
|
|
[
|
|
LandscapeThumbnail->MakeThumbnailWidget()
|
|
]
|
|
]
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
.Padding(0.0f, 4.0f, 4.0f, 4.0f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.MaxWidth(80.0f)
|
|
[
|
|
SNew(SButton)
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Center)
|
|
.Text(LOCTEXT("Bake", "Bake"))
|
|
.OnClicked(this, &FHoudiniAssetComponentDetails::OnBakeLandscape, Landscape, HoudiniAssetComponent)
|
|
.ToolTipText(LOCTEXT("HoudiniLandscapeBakeButton", "Bake this landscape"))
|
|
]
|
|
]
|
|
]
|
|
];
|
|
|
|
// Store thumbnail for this landscape.
|
|
LandscapeThumbnailBorders.Add(Landscape, LandscapeThumbnailBorder);
|
|
|
|
// We need to add material box for each the landscape and landscape hole materials
|
|
for (int32 MaterialIdx = 0; MaterialIdx < 2; ++MaterialIdx)
|
|
{
|
|
UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial();
|
|
TSharedPtr< SBorder > MaterialThumbnailBorder;
|
|
TSharedPtr< SHorizontalBox > HorizontalBox = NULL;
|
|
|
|
FString MaterialName, MaterialPathName;
|
|
if (MaterialInterface)
|
|
{
|
|
MaterialName = MaterialInterface->GetName();
|
|
MaterialPathName = MaterialInterface->GetPathName();
|
|
}
|
|
|
|
// Create thumbnail for this material.
|
|
TSharedPtr< FAssetThumbnail > MaterialInterfaceThumbnail =
|
|
MakeShareable(new FAssetThumbnail(MaterialInterface, 64, 64, AssetThumbnailPool));
|
|
|
|
VerticalBox->AddSlot().Padding(2, 2, 5, 2).AutoHeight()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(MaterialIdx == 0 ? LOCTEXT("LandscapeMaterial", "Landscape Material") : LOCTEXT("LandscapeHoleMaterial", "Landscape Hole Material"))
|
|
.Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont")))
|
|
];
|
|
|
|
VerticalBox->AddSlot().Padding(0, 2)
|
|
[
|
|
SNew(SAssetDropTarget)
|
|
.OnIsAssetAcceptableForDrop( this, &FHoudiniAssetComponentDetails::OnMaterialInterfaceDraggedOver )
|
|
.OnAssetDropped(
|
|
this, &FHoudiniAssetComponentDetails::OnMaterialInterfaceDropped,
|
|
Landscape, &HoudiniGeoPartObject, MaterialIdx)
|
|
[
|
|
SAssignNew(HorizontalBox, SHorizontalBox)
|
|
]
|
|
];
|
|
|
|
HorizontalBox->AddSlot().Padding(0.0f, 0.0f, 2.0f, 0.0f).AutoWidth()
|
|
[
|
|
SAssignNew(MaterialThumbnailBorder, SBorder)
|
|
.Padding(5.0f)
|
|
.BorderImage(
|
|
this, &FHoudiniAssetComponentDetails::GetMaterialInterfaceThumbnailBorder, Landscape, MaterialIdx)
|
|
.OnMouseDoubleClick(
|
|
this, &FHoudiniAssetComponentDetails::OnThumbnailDoubleClick, (UObject *)MaterialInterface)
|
|
[
|
|
SNew(SBox)
|
|
.WidthOverride(64)
|
|
.HeightOverride(64)
|
|
.ToolTipText(FText::FromString(MaterialPathName))
|
|
[
|
|
MaterialInterfaceThumbnail->MakeThumbnailWidget()
|
|
]
|
|
]
|
|
];
|
|
|
|
// Store thumbnail for this mesh and material index.
|
|
{
|
|
TPairInitializer< ALandscapeProxy *, int32 > Pair(Landscape, MaterialIdx);
|
|
LandscapeMaterialInterfaceThumbnailBorders.Add(Pair, MaterialThumbnailBorder);
|
|
}
|
|
|
|
TSharedPtr< SComboButton > AssetComboButton;
|
|
TSharedPtr< SHorizontalBox > ButtonBox;
|
|
|
|
HorizontalBox->AddSlot()
|
|
.FillWidth(1.0f)
|
|
.Padding(0.0f, 4.0f, 4.0f, 4.0f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
[
|
|
SAssignNew(ButtonBox, SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
[
|
|
SAssignNew(AssetComboButton, SComboButton)
|
|
//.ToolTipText( this, &FHoudiniAssetComponentDetails::OnGetToolTip )
|
|
.ButtonStyle(FEditorStyle::Get(), "PropertyEditor.AssetComboStyle")
|
|
.ForegroundColor(FEditorStyle::GetColor("PropertyEditor.AssetName.ColorAndOpacity"))
|
|
.OnGetMenuContent(this, &FHoudiniAssetComponentDetails::OnGetMaterialInterfaceMenuContent,
|
|
MaterialInterface, Landscape, &HoudiniGeoPartObject, MaterialIdx)
|
|
.ContentPadding(2.0f)
|
|
.ButtonContent()
|
|
[
|
|
SNew(STextBlock)
|
|
.TextStyle(FEditorStyle::Get(), "PropertyEditor.AssetClass")
|
|
.Font(FEditorStyle::GetFontStyle(FName(TEXT("PropertyWindow.NormalFont"))))
|
|
.Text(FText::FromString(MaterialName))
|
|
]
|
|
]
|
|
]
|
|
];
|
|
|
|
// Create tooltip.
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("Asset"), FText::FromString(MaterialName));
|
|
FText MaterialTooltip = FText::Format(
|
|
LOCTEXT("BrowseToSpecificAssetInContentBrowser", "Browse to '{Asset}' in Content Browser"), Args);
|
|
|
|
ButtonBox->AddSlot()
|
|
.AutoWidth()
|
|
.Padding(2.0f, 0.0f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
PropertyCustomizationHelpers::MakeBrowseButton(
|
|
FSimpleDelegate::CreateSP(
|
|
this, &FHoudiniAssetComponentDetails::OnMaterialInterfaceBrowse, MaterialInterface ),
|
|
TAttribute< FText >( MaterialTooltip ) )
|
|
];
|
|
|
|
ButtonBox->AddSlot()
|
|
.AutoWidth()
|
|
.Padding(2.0f, 0.0f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SButton)
|
|
.ToolTipText(LOCTEXT("ResetToBaseMaterial", "Reset to base material"))
|
|
.ButtonStyle(FEditorStyle::Get(), "NoBorder")
|
|
.ContentPadding(0)
|
|
.Visibility(EVisibility::Visible)
|
|
.OnClicked(
|
|
this, &FHoudiniAssetComponentDetails::OnResetMaterialInterfaceClicked,
|
|
Landscape, &HoudiniGeoPartObject, MaterialIdx )
|
|
[
|
|
SNew(SImage)
|
|
.Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault"))
|
|
]
|
|
];
|
|
|
|
// Store combo button for this mesh and index.
|
|
{
|
|
TPairInitializer< ALandscapeProxy *, int32 > Pair(Landscape, MaterialIdx);
|
|
LandscapeMaterialInterfaceComboButtons.Add(Pair, AssetComboButton);
|
|
}
|
|
}
|
|
|
|
MeshIdx++;
|
|
}
|
|
}
|
|
|
|
if (NumberOfGeneratedMeshes > 1)
|
|
{
|
|
// Add the BakeAll button
|
|
TSharedRef< SHorizontalBox > HorizontalButtonBox = SNew(SHorizontalBox);
|
|
DetailCategoryBuilder.AddCustomRow(FText::GetEmpty())
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.Padding(0, 2.0f, 0, 0)
|
|
.FillHeight(1.0f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SAssignNew(HorizontalButtonBox, SHorizontalBox)
|
|
]
|
|
];
|
|
|
|
HorizontalButtonBox->AddSlot()
|
|
.AutoWidth()
|
|
.Padding(2.0f, 0.0f)
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Center)
|
|
[
|
|
SNew(SButton)
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Center)
|
|
.OnClicked(this, &FHoudiniAssetComponentDetails::OnBakeAllGeneratedMeshes)
|
|
.Text(LOCTEXT("BakeHoudiniActor", "Bake All"))
|
|
.ToolTipText(LOCTEXT("BakeHoudiniActorToolTip", "Bake all generated meshes"))
|
|
];
|
|
}
|
|
}
|
|
|
|
void
|
|
FHoudiniAssetComponentDetails::CreateHoudiniAssetWidget( IDetailCategoryBuilder & DetailCategoryBuilder )
|
|
{
|
|
// Reset Houdini asset related widgets.
|
|
HoudiniAssetComboButton.Reset();
|
|
HoudiniAssetThumbnailBorder.Reset();
|
|
|
|
// Get thumbnail pool for this builder.
|
|
IDetailLayoutBuilder& DetailLayoutBuilder = DetailCategoryBuilder.GetParentLayout();
|
|
TSharedPtr< FAssetThumbnailPool > AssetThumbnailPool = DetailLayoutBuilder.GetThumbnailPool();
|
|
|
|
FSlateFontInfo NormalFont = FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"));
|
|
UHoudiniAsset * HoudiniAsset = nullptr;
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = nullptr;
|
|
FString HoudiniAssetPathName = TEXT( "" );
|
|
FString HoudiniAssetName = TEXT( "" );
|
|
|
|
if ( HoudiniAssetComponents.Num() > 0 )
|
|
{
|
|
HoudiniAssetComponent = HoudiniAssetComponents[ 0 ];
|
|
HoudiniAsset = HoudiniAssetComponent->HoudiniAsset;
|
|
|
|
if ( HoudiniAsset )
|
|
{
|
|
HoudiniAssetPathName = HoudiniAsset->GetPathName();
|
|
HoudiniAssetName = HoudiniAsset->GetName();
|
|
}
|
|
}
|
|
|
|
// Create thumbnail for this Houdini asset.
|
|
TSharedPtr< FAssetThumbnail > HoudiniAssetThumbnail =
|
|
MakeShareable(new FAssetThumbnail(HoudiniAsset, 64, 64, AssetThumbnailPool));
|
|
|
|
IDetailGroup& OptionsGroup = DetailCategoryBuilder.AddGroup(TEXT("Options"), LOCTEXT("Options", "Asset Options"));
|
|
OptionsGroup.AddWidgetRow()
|
|
.NameContent()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("HoudiniDigitalAsset", "Houdini Digital Asset"))
|
|
.Font(NormalFont)
|
|
]
|
|
.ValueContent()
|
|
.MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH)
|
|
[
|
|
SNew( SAssetDropTarget )
|
|
.OnIsAssetAcceptableForDrop( this, &FHoudiniAssetComponentDetails::OnHoudiniAssetDraggedOver )
|
|
.OnAssetDropped( this, &FHoudiniAssetComponentDetails::OnHoudiniAssetDropped )
|
|
[
|
|
SNew( SHorizontalBox )
|
|
+SHorizontalBox::Slot()
|
|
.Padding( 0.0f, 0.0f, 2.0f, 0.0f )
|
|
.AutoWidth()
|
|
[
|
|
SAssignNew( HoudiniAssetThumbnailBorder, SBorder )
|
|
.Padding( 5.0f )
|
|
.BorderImage( this, &FHoudiniAssetComponentDetails::GetHoudiniAssetThumbnailBorder )
|
|
[
|
|
SNew( SBox )
|
|
.WidthOverride( 64 )
|
|
.HeightOverride( 64 )
|
|
.ToolTipText( FText::FromString( HoudiniAssetPathName ) )
|
|
[
|
|
HoudiniAssetThumbnail->MakeThumbnailWidget()
|
|
]
|
|
]
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.FillWidth(1.f)
|
|
.Padding( 0.0f, 4.0f, 4.0f, 4.0f )
|
|
.VAlign( VAlign_Center )
|
|
[
|
|
SNew( SVerticalBox )
|
|
+SVerticalBox::Slot()
|
|
.HAlign( HAlign_Fill )
|
|
[
|
|
SNew( SHorizontalBox )
|
|
+SHorizontalBox::Slot()
|
|
[
|
|
SAssignNew( HoudiniAssetComboButton, SComboButton )
|
|
.ButtonStyle( FEditorStyle::Get(), "PropertyEditor.AssetComboStyle" )
|
|
.ForegroundColor( FEditorStyle::GetColor( "PropertyEditor.AssetName.ColorAndOpacity" ) )
|
|
.OnGetMenuContent( this, &FHoudiniAssetComponentDetails::OnGetHoudiniAssetMenuContent )
|
|
.ContentPadding( 2.0f )
|
|
.ButtonContent()
|
|
[
|
|
SNew( STextBlock )
|
|
.TextStyle( FEditorStyle::Get(), "PropertyEditor.AssetClass" )
|
|
.Font(NormalFont)
|
|
.Text( FText::FromString( HoudiniAssetName ) )
|
|
]
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding( 2.0f, 0.0f )
|
|
.VAlign( VAlign_Center )
|
|
[
|
|
PropertyCustomizationHelpers::MakeBrowseButton(
|
|
FSimpleDelegate::CreateSP( this, &FHoudiniAssetComponentDetails::OnHoudiniAssetBrowse ),
|
|
TAttribute< FText >( FText::FromString( HoudiniAssetName ) ) )
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding( 2.0f, 0.0f )
|
|
.VAlign( VAlign_Center )
|
|
[
|
|
SNew( SButton )
|
|
.ToolTipText( LOCTEXT( "ResetToBaseHoudiniAsset", "Reset Houdini Asset" ) )
|
|
.ButtonStyle( FEditorStyle::Get(), "NoBorder" )
|
|
.ContentPadding( 0 )
|
|
.Visibility( EVisibility::Visible )
|
|
.OnClicked( this, &FHoudiniAssetComponentDetails::OnResetHoudiniAssetClicked )
|
|
[
|
|
SNew( SImage )
|
|
.Image( FEditorStyle::GetBrush( "PropertyWindow.DiffersFromDefault" ) )
|
|
]
|
|
] // horizontal buttons next to thumbnail box
|
|
]
|
|
] // horizontal asset chooser box
|
|
]
|
|
];
|
|
|
|
auto AddOptionRow = [&](const FText& NameText, FOnCheckStateChanged OnCheckStateChanged, TAttribute<ECheckBoxState> IsCheckedAttr)
|
|
{
|
|
OptionsGroup.AddWidgetRow()
|
|
.NameContent()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(NameText)
|
|
.Font(NormalFont)
|
|
]
|
|
.ValueContent()
|
|
[
|
|
SNew( SCheckBox )
|
|
.OnCheckStateChanged( OnCheckStateChanged )
|
|
.IsChecked( IsCheckedAttr )
|
|
];
|
|
};
|
|
|
|
AddOptionRow(
|
|
LOCTEXT("HoudiniEnableCookingOnParamChange", "Enable Cooking on Parameter Change"),
|
|
FOnCheckStateChanged::CreateSP(this, &FHoudiniAssetComponentDetails::CheckStateChangedComponentSettingCooking, HoudiniAssetComponent),
|
|
TAttribute<ECheckBoxState>::Create(TAttribute<ECheckBoxState>::FGetter::CreateSP(this, &FHoudiniAssetComponentDetails::IsCheckedComponentSettingCooking, HoudiniAssetComponent)));
|
|
AddOptionRow(
|
|
LOCTEXT("HoudiniUploadTransformsToHoudiniEngine", "Upload Transforms to Houdini Engine"),
|
|
FOnCheckStateChanged::CreateSP(this, &FHoudiniAssetComponentDetails::CheckStateChangedComponentSettingUploadTransform, HoudiniAssetComponent),
|
|
TAttribute<ECheckBoxState>::Create(TAttribute<ECheckBoxState>::FGetter::CreateSP(this, &FHoudiniAssetComponentDetails::IsCheckedComponentSettingUploadTransform, HoudiniAssetComponent)));
|
|
AddOptionRow(
|
|
LOCTEXT("HoudiniTransformChangeTriggersCooks", "Transform Change Triggers Cooks"),
|
|
FOnCheckStateChanged::CreateSP(this, &FHoudiniAssetComponentDetails::CheckStateChangedComponentSettingTransformCooking, HoudiniAssetComponent),
|
|
TAttribute<ECheckBoxState>::Create(TAttribute<ECheckBoxState>::FGetter::CreateSP(this, &FHoudiniAssetComponentDetails::IsCheckedComponentSettingTransformCooking, HoudiniAssetComponent)));
|
|
AddOptionRow(
|
|
LOCTEXT("HoudiniUseHoudiniMaterials", "Use Native Houdini Materials"),
|
|
FOnCheckStateChanged::CreateSP(this, &FHoudiniAssetComponentDetails::CheckStateChangedComponentSettingUseHoudiniMaterials, HoudiniAssetComponent),
|
|
TAttribute<ECheckBoxState>::Create(TAttribute<ECheckBoxState>::FGetter::CreateSP(this, &FHoudiniAssetComponentDetails::IsCheckedComponentSettingUseHoudiniMaterials, HoudiniAssetComponent)));
|
|
AddOptionRow(
|
|
LOCTEXT("HoudiniCookingTriggersDownstreamCooks", "Cooking Triggers Downstream Cooks"),
|
|
FOnCheckStateChanged::CreateSP(this, &FHoudiniAssetComponentDetails::CheckStateChangedComponentSettingCookingTriggersDownstreamCooks, HoudiniAssetComponent),
|
|
TAttribute<ECheckBoxState>::Create(TAttribute<ECheckBoxState>::FGetter::CreateSP(this, &FHoudiniAssetComponentDetails::IsCheckedComponentSettingCookingTriggersDownstreamCooks, HoudiniAssetComponent)));
|
|
|
|
auto ActionButtonSlot = [&](const FText& InText, const FText& InToolTipText, FOnClicked InOnClicked) -> SHorizontalBox::FSlot&
|
|
{
|
|
return SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding( 2.0f, 0.0f )
|
|
.VAlign( VAlign_Center )
|
|
.HAlign( HAlign_Center )
|
|
[
|
|
SNew( SButton )
|
|
.VAlign( VAlign_Center )
|
|
.HAlign( HAlign_Center )
|
|
.OnClicked(InOnClicked)
|
|
.Text(InText)
|
|
.ToolTipText(InToolTipText)
|
|
];
|
|
};
|
|
|
|
IDetailGroup& CookGroup = DetailCategoryBuilder.AddGroup(TEXT("Cooking"), LOCTEXT("CookingActions", "Cooking Actions"));
|
|
CookGroup.AddWidgetRow()
|
|
.WholeRowContent()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ActionButtonSlot(
|
|
LOCTEXT("RecookHoudiniActor", "Recook Asset"),
|
|
LOCTEXT("RecookHoudiniActorToolTip", "Recooks the outputs of the Houdini asset"),
|
|
FOnClicked::CreateSP(this, &FHoudiniAssetComponentDetails::OnRecookAsset))
|
|
+ActionButtonSlot(
|
|
LOCTEXT("RebuildHoudiniActor", "Rebuild Asset"),
|
|
LOCTEXT("RebuildHoudiniActorToolTip", "Deletes and then re-creates and re-cooks the Houdini asset"),
|
|
FOnClicked::CreateSP(this, &FHoudiniAssetComponentDetails::OnRebuildAsset))
|
|
+ActionButtonSlot(
|
|
LOCTEXT("ResetHoudiniActor", "Reset Parameters"),
|
|
LOCTEXT("ResetHoudiniActorToolTip", "Resets parameters to their default values"),
|
|
FOnClicked::CreateSP(this, &FHoudiniAssetComponentDetails::OnResetAsset))
|
|
];
|
|
|
|
// Cook folder widget
|
|
FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>("ContentBrowser");
|
|
FPathPickerConfig CookPathPickerConfig;
|
|
CookPathPickerConfig.OnPathSelected = FOnPathSelected::CreateSP(this, &FHoudiniAssetComponentDetails::OnCookFolderSelected);
|
|
CookPathPickerConfig.bAllowContextMenu = false;
|
|
CookPathPickerConfig.bAllowClassesFolder = true;
|
|
|
|
CookGroup.AddWidgetRow()
|
|
.NameContent()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("CookFolder", "Temporary Cook Folder"))
|
|
.Font(NormalFont)
|
|
]
|
|
.ValueContent()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SEditableText)
|
|
.IsReadOnly(true)
|
|
.Text(TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateSP(this, &FHoudiniAssetComponentDetails::GetTempCookFolderText)))
|
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
|
.ToolTipText(TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateSP(this, &FHoudiniAssetComponentDetails::GetTempCookFolderText)))
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
[
|
|
SNew(SComboButton)
|
|
.ButtonStyle(FEditorStyle::Get(), "HoverHintOnly")
|
|
.ToolTipText(LOCTEXT("ChooseACookFolder", "Choose a temporary cook folder for this asset."))
|
|
.ContentPadding(2.0f)
|
|
.ForegroundColor(FSlateColor::UseForeground())
|
|
.IsFocusable(false)
|
|
.MenuContent()
|
|
[
|
|
SNew(SBox)
|
|
.WidthOverride(300.0f)
|
|
.HeightOverride(300.0f)
|
|
[
|
|
ContentBrowserModule.Get().CreatePathPicker(CookPathPickerConfig)
|
|
]
|
|
]
|
|
]
|
|
];
|
|
|
|
IDetailGroup& BakeGroup = DetailCategoryBuilder.AddGroup(TEXT("Baking"), LOCTEXT("Baking", "Baking"));
|
|
TSharedPtr< SButton > BakeToInputButton, BakeToFoliageButton, ReplaceWithFoliageButton;
|
|
|
|
BakeGroup.AddWidgetRow()
|
|
.WholeRowContent()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ ActionButtonSlot(
|
|
LOCTEXT("BakeBlueprintHoudiniActor", "Bake Blueprint"),
|
|
LOCTEXT("BakeBlueprintHoudiniActorToolTip", "Bakes to a new Blueprint"),
|
|
FOnClicked::CreateSP(this, &FHoudiniAssetComponentDetails::OnBakeBlueprint))
|
|
|
|
+ ActionButtonSlot(
|
|
LOCTEXT("BakeToActors", "Bake to Actors"),
|
|
LOCTEXT("BakeToActorsTooltip", "Bakes each output and creates new individual Actors"),
|
|
FOnClicked::CreateSP(this, &FHoudiniAssetComponentDetails::OnBakeToActors))
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.0f, 0.0f)
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Center)
|
|
[
|
|
SAssignNew(BakeToFoliageButton, SButton)
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Center)
|
|
.OnClicked(this, &FHoudiniAssetComponentDetails::OnBakeToFoliage)
|
|
.Text(LOCTEXT("BakeToFoliage", "Bake to Foliage"))
|
|
.ToolTipText(LOCTEXT("BakeToFoliageTooltip", "Bakes instanced static meshes to foliage actor.\nNote: The asset must be outputing at least one instancer. Only instanced static meshes will be baked to foliage."))
|
|
]
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.0f, 0.0f)
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Center)
|
|
[
|
|
SAssignNew(BakeToInputButton, SButton)
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Center)
|
|
.OnClicked(this, &FHoudiniAssetComponentDetails::OnBakeToInput)
|
|
.Text(LOCTEXT("BakeToInput", "Bake to Outliner Input"))
|
|
.ToolTipText(LOCTEXT("BakeToInputTooltip", "Bakes single static mesh and sets it on the first outliner input actor and then disconnects it.\nNote: There must be one static mesh outliner input and one generated mesh."))
|
|
]
|
|
];
|
|
|
|
BakeGroup.AddWidgetRow()
|
|
.WholeRowContent()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ ActionButtonSlot(
|
|
LOCTEXT("BakeReplaceBlueprintHoudiniActor", "Replace With Blueprint"),
|
|
LOCTEXT("BakeReplaceBlueprintHoudiniActorToolTip", "Bakes to a new Blueprint and replaces this Actor"),
|
|
FOnClicked::CreateSP(this, &FHoudiniAssetComponentDetails::OnBakeBlueprintReplace))
|
|
|
|
+ ActionButtonSlot(
|
|
LOCTEXT("BakeReplaceActors", "Replace with Actors"),
|
|
LOCTEXT("BakeReplaceActorsTooltip", "Replace this Actors with individual actors baked from each output."),
|
|
FOnClicked::CreateSP(this, &FHoudiniAssetComponentDetails::OnBakeToActorsReplace))
|
|
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2.0f, 0.0f)
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Center)
|
|
[
|
|
SAssignNew(ReplaceWithFoliageButton, SButton)
|
|
.VAlign(VAlign_Center)
|
|
.HAlign(HAlign_Center)
|
|
.OnClicked(this, &FHoudiniAssetComponentDetails::OnBakeToFoliageReplace)
|
|
.Text(LOCTEXT("BakeReplaceFoliage", "Replace with Foliage"))
|
|
.ToolTipText(LOCTEXT("BakeReplaceFoliageTooltip", "Bakes instanced static meshes to the foliage actor and deletes this Actor.\nNote: The asset must be outputing at least one instancer. Only instanced static meshes will be baked to foliage."))
|
|
]
|
|
];
|
|
|
|
|
|
|
|
// Enable disable the bake to input/foliage buttons
|
|
BakeToInputButton->SetEnabled(TAttribute<bool>::Create(TAttribute<bool>::FGetter::CreateLambda([=] {
|
|
return FHoudiniEngineBakeUtils::CanComponentBakeToOutlinerInput(HoudiniAssetComponent);
|
|
})));
|
|
|
|
BakeToFoliageButton->SetEnabled(TAttribute<bool>::Create(TAttribute<bool>::FGetter::CreateLambda([=] {
|
|
return FHoudiniEngineBakeUtils::CanComponentBakeToFoliage(HoudiniAssetComponent);
|
|
})));
|
|
|
|
ReplaceWithFoliageButton->SetEnabled(TAttribute<bool>::Create(TAttribute<bool>::FGetter::CreateLambda([=] {
|
|
return FHoudiniEngineBakeUtils::CanComponentBakeToFoliage(HoudiniAssetComponent);
|
|
})));
|
|
|
|
|
|
//
|
|
// Bake folder widget
|
|
//
|
|
FPathPickerConfig BakePathPickerConfig;
|
|
BakePathPickerConfig.OnPathSelected = FOnPathSelected::CreateSP( this, &FHoudiniAssetComponentDetails::OnBakeFolderSelected );
|
|
BakePathPickerConfig.bAllowContextMenu = false;
|
|
BakePathPickerConfig.bAllowClassesFolder = true;
|
|
|
|
BakeGroup.AddWidgetRow()
|
|
.NameContent()
|
|
[
|
|
SNew( STextBlock )
|
|
.Text( LOCTEXT( "BakeFolder", "Bake Folder" ) )
|
|
.Font( NormalFont )
|
|
]
|
|
.ValueContent()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SEditableText)
|
|
.IsReadOnly(true)
|
|
.Text( TAttribute<FText>::Create( TAttribute<FText>::FGetter::CreateSP(this, &FHoudiniAssetComponentDetails::GetBakeFolderText ) ) )
|
|
.Font( IDetailLayoutBuilder::GetDetailFont() )
|
|
.ToolTipText( TAttribute<FText>::Create( TAttribute<FText>::FGetter::CreateSP( this, &FHoudiniAssetComponentDetails::GetBakeFolderText ) ) )
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SComboButton)
|
|
.ButtonStyle( FEditorStyle::Get(), "HoverHintOnly" )
|
|
.ToolTipText( LOCTEXT( "ChooseABakeFolder", "Choose a baking output folder") )
|
|
.ContentPadding( 2.0f )
|
|
.ForegroundColor( FSlateColor::UseForeground() )
|
|
.IsFocusable( false )
|
|
.MenuContent()
|
|
[
|
|
SNew(SBox)
|
|
.WidthOverride(300.0f)
|
|
.HeightOverride(300.0f)
|
|
[
|
|
ContentBrowserModule.Get().CreatePathPicker(BakePathPickerConfig)
|
|
]
|
|
]
|
|
]
|
|
];
|
|
|
|
//
|
|
// Help widget
|
|
//
|
|
IDetailGroup& HelpGroup = DetailCategoryBuilder.AddGroup(TEXT("Help"), LOCTEXT("Help", "Help and Debugging"));
|
|
HelpGroup.AddWidgetRow()
|
|
.WholeRowContent()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ActionButtonSlot(
|
|
LOCTEXT("FetchCookLogHoudiniActor", "Fetch Cook Log"),
|
|
LOCTEXT("FetchCookLogHoudiniActorToolTip", "Fetches the cook log from Houdini"),
|
|
FOnClicked::CreateSP(this, &FHoudiniAssetComponentDetails::OnFetchCookLog))
|
|
+ActionButtonSlot(
|
|
LOCTEXT("FetchAssetHelpHoudiniActor", "Asset Help"),
|
|
LOCTEXT("FetchAssetHelpHoudiniActorToolTip", "Displays the asset's Help text"),
|
|
FOnClicked::CreateSP(this, &FHoudiniAssetComponentDetails::OnFetchAssetHelp, HoudiniAssetComponent))
|
|
];
|
|
}
|
|
|
|
const FSlateBrush *
|
|
FHoudiniAssetComponentDetails::GetStaticMeshThumbnailBorder( UStaticMesh * StaticMesh ) const
|
|
{
|
|
TSharedPtr< SBorder > ThumbnailBorder = StaticMeshThumbnailBorders[ StaticMesh ];
|
|
if ( ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered() )
|
|
return FEditorStyle::GetBrush( "PropertyEditor.AssetThumbnailLight" );
|
|
else
|
|
return FEditorStyle::GetBrush( "PropertyEditor.AssetThumbnailShadow" );
|
|
}
|
|
|
|
const FSlateBrush *
|
|
FHoudiniAssetComponentDetails::GetLandscapeThumbnailBorder(ALandscapeProxy * Landscape ) const
|
|
{
|
|
TSharedPtr< SBorder > ThumbnailBorder = LandscapeThumbnailBorders[ Landscape ];
|
|
if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered())
|
|
return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight");
|
|
else
|
|
return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailShadow");
|
|
}
|
|
|
|
const FSlateBrush *
|
|
FHoudiniAssetComponentDetails::GetMaterialInterfaceThumbnailBorder( UStaticMesh * StaticMesh, int32 MaterialIdx ) const
|
|
{
|
|
if ( !StaticMesh )
|
|
return nullptr;
|
|
|
|
TPairInitializer< UStaticMesh *, int32 > Pair( StaticMesh, MaterialIdx );
|
|
TSharedPtr< SBorder > ThumbnailBorder = MaterialInterfaceThumbnailBorders[ Pair ];
|
|
|
|
if ( ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered() )
|
|
return FEditorStyle::GetBrush("PropertyEditor.AssetThumbnailLight");
|
|
else
|
|
return FEditorStyle::GetBrush( "PropertyEditor.AssetThumbnailShadow" );
|
|
}
|
|
|
|
const FSlateBrush *
|
|
FHoudiniAssetComponentDetails::GetMaterialInterfaceThumbnailBorder(ALandscapeProxy * Landscape, int32 MaterialIdx ) const
|
|
{
|
|
if ( !Landscape )
|
|
return nullptr;
|
|
|
|
TPairInitializer< ALandscapeProxy *, int32 > Pair( Landscape, MaterialIdx );
|
|
TSharedPtr< SBorder > ThumbnailBorder = LandscapeMaterialInterfaceThumbnailBorders[ Pair ];
|
|
|
|
if (ThumbnailBorder.IsValid() && ThumbnailBorder->IsHovered())
|
|
return FEditorStyle::GetBrush( "PropertyEditor.AssetThumbnailLight" );
|
|
else
|
|
return FEditorStyle::GetBrush( "PropertyEditor.AssetThumbnailShadow" );
|
|
}
|
|
|
|
FReply
|
|
FHoudiniAssetComponentDetails::OnThumbnailDoubleClick(
|
|
const FGeometry & InMyGeometry,
|
|
const FPointerEvent & InMouseEvent, UObject * Object )
|
|
{
|
|
if ( Object && GEditor )
|
|
GEditor->EditObject( Object );
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply
|
|
FHoudiniAssetComponentDetails::OnBakeStaticMesh( UStaticMesh * StaticMesh, UHoudiniAssetComponent * HoudiniAssetComponent )
|
|
{
|
|
if ( HoudiniAssetComponent && StaticMesh && !HoudiniAssetComponent->IsPendingKill() && !StaticMesh->IsPendingKill() )
|
|
{
|
|
// We need to locate corresponding geo part object in component.
|
|
const FHoudiniGeoPartObject& HoudiniGeoPartObject = HoudiniAssetComponent->LocateGeoPartObject( StaticMesh );
|
|
|
|
(void) FHoudiniEngineBakeUtils::DuplicateStaticMeshAndCreatePackage(
|
|
StaticMesh, HoudiniAssetComponent, HoudiniGeoPartObject, EBakeMode::ReplaceExisitingAssets );
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
void
|
|
FHoudiniAssetComponentDetails::OnBakeNameCommited( const FText& NewText, ETextCommit::Type CommitType,
|
|
UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject HoudiniGeoPartObject )
|
|
{
|
|
if( ensure(HoudiniAssetComponent) )
|
|
{
|
|
HoudiniAssetComponent->SetBakingBaseNameOverride( HoudiniGeoPartObject, NewText.ToString() );
|
|
}
|
|
|
|
// The group label has to be updated
|
|
if( HoudiniAssetComponent )
|
|
HoudiniAssetComponent->UpdateEditorProperties( false );
|
|
}
|
|
|
|
FReply
|
|
FHoudiniAssetComponentDetails::OnRemoveBakingBaseNameOverride( UHoudiniAssetComponent * HoudiniAssetComponent, FHoudiniGeoPartObject GeoPartObject )
|
|
{
|
|
if( HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill() )
|
|
{
|
|
if ( HoudiniAssetComponent->RemoveBakingBaseNameOverride( GeoPartObject ) )
|
|
HoudiniAssetComponent->UpdateEditorProperties( false );
|
|
}
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply
|
|
FHoudiniAssetComponentDetails::OnBakeLandscape(ALandscapeProxy * Landscape, UHoudiniAssetComponent * HoudiniAssetComponent)
|
|
{
|
|
if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill()
|
|
|| !Landscape || Landscape->IsPendingKill() )
|
|
return FReply::Handled();
|
|
|
|
bool bNeedToUpdateProperties = FHoudiniEngineBakeUtils::BakeLandscape( HoudiniAssetComponent, Landscape );
|
|
|
|
// Modify the component GUID to avoid overwriting the layers
|
|
if ( bNeedToUpdateProperties )
|
|
{
|
|
HoudiniAssetComponent->ComponentGUID = FGuid::NewGuid();
|
|
HoudiniAssetComponent->UpdateEditorProperties(false);
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply
|
|
FHoudiniAssetComponentDetails::OnBakeAllGeneratedMeshes()
|
|
{
|
|
if ( HoudiniAssetComponents.Num() > 0 )
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ];
|
|
if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() )
|
|
return FReply::Handled();
|
|
|
|
for( TMap< FHoudiniGeoPartObject, UStaticMesh * >::TIterator
|
|
Iter(HoudiniAssetComponent->StaticMeshes); Iter; ++Iter )
|
|
{
|
|
FHoudiniGeoPartObject & HoudiniGeoPartObject = Iter.Key();
|
|
UStaticMesh * StaticMesh = Iter.Value();
|
|
(void) OnBakeStaticMesh( StaticMesh, HoudiniAssetComponent );
|
|
}
|
|
|
|
for (TMap< FHoudiniGeoPartObject, TWeakObjectPtr<ALandscapeProxy> >::TIterator
|
|
IterLandscapes(HoudiniAssetComponent->LandscapeComponents); IterLandscapes; ++IterLandscapes)
|
|
{
|
|
ALandscapeProxy * Landscape = IterLandscapes.Value().Get();
|
|
if ( !Landscape || !Landscape->IsValidLowLevel() )
|
|
continue;
|
|
|
|
(void) OnBakeLandscape(Landscape, HoudiniAssetComponent);
|
|
}
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply
|
|
FHoudiniAssetComponentDetails::OnRecookAsset()
|
|
{
|
|
if ( HoudiniAssetComponents.Num() > 0 )
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ];
|
|
if ( HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill() )
|
|
HoudiniAssetComponent->StartTaskAssetCookingManual();
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply
|
|
FHoudiniAssetComponentDetails::OnRebuildAsset()
|
|
{
|
|
if ( HoudiniAssetComponents.Num() > 0 )
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ];
|
|
if ( HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill() )
|
|
HoudiniAssetComponent->StartTaskAssetRebuildManual();
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply
|
|
FHoudiniAssetComponentDetails::OnResetAsset()
|
|
{
|
|
if ( HoudiniAssetComponents.Num() > 0 )
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ];
|
|
if ( HoudiniAssetComponent && !HoudiniAssetComponent->IsPendingKill() )
|
|
HoudiniAssetComponent->StartTaskAssetResetManual();
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply
|
|
FHoudiniAssetComponentDetails::OnBakeBlueprint()
|
|
{
|
|
if ( HoudiniAssetComponents.Num() > 0 )
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ];
|
|
if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() )
|
|
return FReply::Handled();
|
|
|
|
// If component is not cooking or instancing, we can bake blueprint.
|
|
if ( !HoudiniAssetComponent->IsInstantiatingOrCooking() )
|
|
FHoudiniEngineBakeUtils::BakeBlueprint( HoudiniAssetComponent );
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply
|
|
FHoudiniAssetComponentDetails::OnBakeBlueprintReplace()
|
|
{
|
|
if ( HoudiniAssetComponents.Num() > 0 )
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ];
|
|
if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() )
|
|
return FReply::Handled();
|
|
|
|
// If component is not cooking or instancing, we can bake blueprint.
|
|
if ( !HoudiniAssetComponent->IsInstantiatingOrCooking() )
|
|
FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithBlueprint( HoudiniAssetComponent );
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply
|
|
FHoudiniAssetComponentDetails::OnBakeToActors()
|
|
{
|
|
if ( HoudiniAssetComponents.Num() > 0 )
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ];
|
|
if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() )
|
|
return FReply::Handled();
|
|
|
|
// If component is not cooking or instancing, we can bake.
|
|
if ( !HoudiniAssetComponent->IsInstantiatingOrCooking() )
|
|
{
|
|
FHoudiniEngineBakeUtils::BakeHoudiniActorToActors( HoudiniAssetComponent, true );
|
|
}
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply
|
|
FHoudiniAssetComponentDetails::OnBakeToActorsReplace()
|
|
{
|
|
if (HoudiniAssetComponents.Num() > 0)
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[0];
|
|
if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())
|
|
return FReply::Handled();
|
|
|
|
// If component is not cooking or instancing, we can bake.
|
|
if (!HoudiniAssetComponent->IsInstantiatingOrCooking())
|
|
{
|
|
FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(HoudiniAssetComponent, false);
|
|
}
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply
|
|
FHoudiniAssetComponentDetails::OnBakeToInput()
|
|
{
|
|
if ( HoudiniAssetComponents.Num() > 0 )
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ];
|
|
if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() )
|
|
return FReply::Handled();
|
|
|
|
// If component is not cooking or instancing, we can bake.
|
|
if ( !HoudiniAssetComponent->IsInstantiatingOrCooking() )
|
|
{
|
|
FHoudiniEngineBakeUtils::BakeHoudiniActorToOutlinerInput( HoudiniAssetComponent );
|
|
}
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply
|
|
FHoudiniAssetComponentDetails::OnBakeToFoliage()
|
|
{
|
|
if ( HoudiniAssetComponents.Num() > 0 )
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[0];
|
|
if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())
|
|
return FReply::Handled();
|
|
|
|
// If component is not cooking or instancing, we can bake.
|
|
if (!HoudiniAssetComponent->IsInstantiatingOrCooking())
|
|
{
|
|
FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(HoudiniAssetComponent);
|
|
}
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply
|
|
FHoudiniAssetComponentDetails::OnBakeToFoliageReplace()
|
|
{
|
|
if (HoudiniAssetComponents.Num() > 0)
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[0];
|
|
if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill())
|
|
return FReply::Handled();
|
|
|
|
// If component is not cooking or instancing, we can bake.
|
|
if (!HoudiniAssetComponent->IsInstantiatingOrCooking())
|
|
{
|
|
FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithFoliage(HoudiniAssetComponent);
|
|
}
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
void
|
|
FHoudiniAssetComponentDetails::OnBakeFolderSelected( const FString& Folder )
|
|
{
|
|
if( HoudiniAssetComponents.Num() && HoudiniAssetComponents[ 0 ] && !HoudiniAssetComponents[0]->IsPendingKill() )
|
|
{
|
|
HoudiniAssetComponents[ 0 ]->SetBakeFolder( Folder );
|
|
}
|
|
}
|
|
|
|
FText
|
|
FHoudiniAssetComponentDetails::GetBakeFolderText() const
|
|
{
|
|
FText BakeFolderText;
|
|
if( HoudiniAssetComponents.Num() && HoudiniAssetComponents[ 0 ] && !HoudiniAssetComponents[0]->IsPendingKill() )
|
|
{
|
|
BakeFolderText = HoudiniAssetComponents[ 0 ]->GetBakeFolder();
|
|
}
|
|
return BakeFolderText;
|
|
}
|
|
|
|
void
|
|
FHoudiniAssetComponentDetails::OnCookFolderSelected(const FString& Folder)
|
|
{
|
|
if (HoudiniAssetComponents.Num() && HoudiniAssetComponents[0] && !HoudiniAssetComponents[0]->IsPendingKill())
|
|
{
|
|
HoudiniAssetComponents[0]->SetTempCookFolder(Folder);
|
|
}
|
|
}
|
|
|
|
FText
|
|
FHoudiniAssetComponentDetails::GetTempCookFolderText() const
|
|
{
|
|
FText TempCookFolderText;
|
|
if (HoudiniAssetComponents.Num() && HoudiniAssetComponents[0] && !HoudiniAssetComponents[0]->IsPendingKill() )
|
|
{
|
|
TempCookFolderText = HoudiniAssetComponents[0]->GetTempCookFolder();
|
|
}
|
|
return TempCookFolderText;
|
|
}
|
|
|
|
FReply
|
|
FHoudiniAssetComponentDetails::OnFetchCookLog()
|
|
{
|
|
TSharedPtr< SWindow > ParentWindow;
|
|
|
|
FString CookLog;
|
|
|
|
// Get fetch cook status.
|
|
FString CookResult = FHoudiniEngineUtils::GetCookResult();
|
|
if (!CookResult.IsEmpty())
|
|
CookLog += TEXT("Cook Results:\n") + CookResult + TEXT("\n\n");
|
|
|
|
// Add the cook state
|
|
FString CookState = FHoudiniEngineUtils::GetCookState();
|
|
if ( !CookState.IsEmpty())
|
|
CookLog += TEXT("Cook State:\n") + CookState + TEXT("\n\n");
|
|
|
|
// Error Description
|
|
FString Error = FHoudiniEngineUtils::GetErrorDescription();
|
|
if (!Error.IsEmpty())
|
|
CookLog += TEXT("Error Description:\n") + Error + TEXT("\n\n");
|
|
|
|
// Iterates on all the selected HAC and get their node errors
|
|
for (auto& HAC : HoudiniAssetComponents)
|
|
{
|
|
if (!HAC || HAC->IsPendingKill())
|
|
continue;
|
|
|
|
// Get the node errors, warnings and messages
|
|
FString NodeErrors = FHoudiniEngineUtils::GetNodeErrorsWarningsAndMessages(HAC->GetAssetId());
|
|
if (!NodeErrors.IsEmpty())
|
|
CookLog += NodeErrors;
|
|
}
|
|
|
|
// Check if the main frame is loaded. When using the old main frame it may not be.
|
|
if ( FModuleManager::Get().IsModuleLoaded( "MainFrame" ) )
|
|
{
|
|
IMainFrameModule & MainFrame = FModuleManager::LoadModuleChecked<IMainFrameModule>( "MainFrame" );
|
|
ParentWindow = MainFrame.GetParentWindow();
|
|
}
|
|
|
|
if (CookLog.IsEmpty())
|
|
{
|
|
// See if a failed HAPI initialization / invalid session is preventing us from getting the cook log
|
|
if (!FHoudiniApi::IsHAPIInitialized())
|
|
{
|
|
CookLog += TEXT("\n\nThe Houdini Engine API Library (HAPI) has not been initialized properly.\n\n");
|
|
}
|
|
else
|
|
{
|
|
const HAPI_Session * SessionPtr = FHoudiniEngine::Get().GetSession();
|
|
if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsSessionValid(SessionPtr))
|
|
{
|
|
CookLog += TEXT("\n\nThe current Houdini Engine Session is not valid.\n\n");
|
|
}
|
|
else if (HAPI_RESULT_SUCCESS != FHoudiniApi::IsInitialized(SessionPtr))
|
|
{
|
|
CookLog += TEXT("\n\nThe current Houdini Engine Session has not been initialized properly.\n\n");
|
|
}
|
|
}
|
|
|
|
if (!CookLog.IsEmpty())
|
|
{
|
|
CookLog += TEXT("Please try to restart the current Houdini Engine session via File > Restart Houdini Engine Session.\n\n");
|
|
}
|
|
else
|
|
{
|
|
CookLog = TEXT("\n\nThe cook log is empty...\n\n");
|
|
}
|
|
}
|
|
|
|
if ( ParentWindow.IsValid() )
|
|
{
|
|
TSharedPtr< SHoudiniAssetLogWidget > HoudiniAssetCookLog;
|
|
|
|
TSharedRef< SWindow > Window =
|
|
SNew( SWindow )
|
|
.Title( LOCTEXT( "WindowTitle", "Houdini Cook Log" ) )
|
|
.ClientSize( FVector2D( 640, 480 ) );
|
|
|
|
Window->SetContent(
|
|
SAssignNew( HoudiniAssetCookLog, SHoudiniAssetLogWidget )
|
|
.LogText(CookLog) );
|
|
|
|
FSlateApplication::Get().AddModalWindow( Window, ParentWindow, false );
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply
|
|
FHoudiniAssetComponentDetails::OnFetchAssetHelp( UHoudiniAssetComponent * HoudiniAssetComponent )
|
|
{
|
|
if ( HoudiniAssetComponent )
|
|
{
|
|
HAPI_AssetInfo AssetInfo;
|
|
FHoudiniApi::AssetInfo_Init(&AssetInfo);
|
|
HAPI_NodeId AssetId = HoudiniAssetComponent->GetAssetId();
|
|
|
|
if ( FHoudiniEngineUtils::IsValidNodeId( AssetId ) )
|
|
{
|
|
auto result = FHoudiniApi::GetAssetInfo( FHoudiniEngine::Get().GetSession(), AssetId, &AssetInfo );
|
|
if ( result == HAPI_RESULT_SUCCESS )
|
|
{
|
|
FString HelpLogString = TEXT( "" );
|
|
FHoudiniEngineString HoudiniEngineString( AssetInfo.helpTextSH );
|
|
if ( HoudiniEngineString.ToFString( HelpLogString ) )
|
|
{
|
|
if ( HelpLogString.IsEmpty() )
|
|
HelpLogString = TEXT( "No Asset Help Found" );
|
|
|
|
TSharedPtr< SWindow > ParentWindow;
|
|
|
|
// Check if the main frame is loaded. When using the old main frame it may not be.
|
|
if ( FModuleManager::Get().IsModuleLoaded( "MainFrame" ) )
|
|
{
|
|
IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked< IMainFrameModule >( "MainFrame" );
|
|
ParentWindow = MainFrame.GetParentWindow();
|
|
}
|
|
|
|
if ( ParentWindow.IsValid() )
|
|
{
|
|
TSharedPtr< SHoudiniAssetLogWidget > HoudiniAssetHelpLog;
|
|
|
|
TSharedRef< SWindow > Window =
|
|
SNew( SWindow )
|
|
.Title( LOCTEXT( "WindowTitle", "Houdini Asset Help" ) )
|
|
.ClientSize( FVector2D( 640, 480 ) );
|
|
|
|
Window->SetContent(
|
|
SAssignNew( HoudiniAssetHelpLog, SHoudiniAssetLogWidget )
|
|
.LogText( HelpLogString ) );
|
|
|
|
FSlateApplication::Get().AddModalWindow( Window, ParentWindow, false );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
bool
|
|
FHoudiniAssetComponentDetails::OnMaterialInterfaceDraggedOver( const UObject * InObject ) const
|
|
{
|
|
return ( InObject && InObject->IsA( UMaterialInterface::StaticClass() ) );
|
|
}
|
|
|
|
void
|
|
FHoudiniAssetComponentDetails::OnMaterialInterfaceDropped(
|
|
UObject * InObject, UStaticMesh * StaticMesh,
|
|
FHoudiniGeoPartObject * HoudiniGeoPartObject, int32 MaterialIdx )
|
|
{
|
|
UMaterialInterface * MaterialInterface = Cast< UMaterialInterface >( InObject );
|
|
if ( !MaterialInterface || MaterialInterface->IsPendingKill() )
|
|
return;
|
|
|
|
if ( !StaticMesh || StaticMesh->IsPendingKill() )
|
|
return;
|
|
|
|
if ( !StaticMesh->StaticMaterials.IsValidIndex(MaterialIdx) )
|
|
return;
|
|
|
|
bool bViewportNeedsUpdate = false;
|
|
|
|
// Replace material on component using this static mesh.
|
|
for ( TArray< UHoudiniAssetComponent * >::TIterator
|
|
IterComponents( HoudiniAssetComponents ); IterComponents; ++IterComponents )
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents;
|
|
if ( !HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() )
|
|
continue;
|
|
|
|
// Retrieve material interface which is being replaced.
|
|
UMaterialInterface * OldMaterialInterface = StaticMesh->StaticMaterials[ MaterialIdx ].MaterialInterface;
|
|
|
|
if ( OldMaterialInterface == MaterialInterface )
|
|
continue;
|
|
|
|
// Record replaced material.
|
|
const bool bReplaceSuccessful = HoudiniAssetComponent->ReplaceMaterial(
|
|
*HoudiniGeoPartObject, MaterialInterface, OldMaterialInterface, MaterialIdx );
|
|
|
|
bool bMaterialReplaced = false;
|
|
if ( bReplaceSuccessful )
|
|
{
|
|
FScopedTransaction Transaction(
|
|
TEXT( HOUDINI_MODULE_EDITOR ),
|
|
LOCTEXT( "HoudiniMaterialReplacement", "Houdini Material Replacement" ), HoudiniAssetComponent );
|
|
|
|
// Replace material on static mesh.
|
|
StaticMesh->Modify();
|
|
StaticMesh->StaticMaterials[ MaterialIdx ].MaterialInterface = MaterialInterface;
|
|
|
|
UStaticMeshComponent * StaticMeshComponent =
|
|
HoudiniAssetComponent->LocateStaticMeshComponent( StaticMesh );
|
|
if ( StaticMeshComponent && !StaticMeshComponent->IsPendingKill() )
|
|
{
|
|
StaticMeshComponent->Modify();
|
|
StaticMeshComponent->SetMaterial( MaterialIdx, MaterialInterface );
|
|
|
|
bMaterialReplaced = true;
|
|
}
|
|
|
|
TArray< UInstancedStaticMeshComponent * > InstancedStaticMeshComponents;
|
|
if ( HoudiniAssetComponent->LocateInstancedStaticMeshComponents( StaticMesh, InstancedStaticMeshComponents ) )
|
|
{
|
|
for ( int32 Idx = 0; Idx < InstancedStaticMeshComponents.Num(); ++Idx )
|
|
{
|
|
UInstancedStaticMeshComponent * InstancedStaticMeshComponent = InstancedStaticMeshComponents[ Idx ];
|
|
if ( InstancedStaticMeshComponent && !InstancedStaticMeshComponent->IsPendingKill() )
|
|
{
|
|
InstancedStaticMeshComponent->Modify();
|
|
InstancedStaticMeshComponent->SetMaterial( MaterialIdx, MaterialInterface );
|
|
|
|
bMaterialReplaced = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bMaterialReplaced )
|
|
{
|
|
HoudiniAssetComponent->UpdateEditorProperties( false );
|
|
bViewportNeedsUpdate = true;
|
|
}
|
|
}
|
|
|
|
if ( GEditor && bViewportNeedsUpdate )
|
|
GEditor->RedrawAllViewports();
|
|
}
|
|
|
|
void
|
|
FHoudiniAssetComponentDetails::OnMaterialInterfaceDropped(
|
|
UObject * InObject, ALandscapeProxy * Landscape,
|
|
FHoudiniGeoPartObject * HoudiniGeoPartObject, int32 MaterialIdx)
|
|
{
|
|
UMaterialInterface * MaterialInterface = Cast< UMaterialInterface >( InObject );
|
|
if (!MaterialInterface || MaterialInterface->IsPendingKill() )
|
|
return;
|
|
|
|
bool bViewportNeedsUpdate = false;
|
|
|
|
// Replace material on component using this static mesh.
|
|
for (TArray< UHoudiniAssetComponent * >::TIterator
|
|
IterComponents(HoudiniAssetComponents); IterComponents; ++IterComponents)
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents;
|
|
if (!HoudiniAssetComponent || HoudiniAssetComponent->IsPendingKill() )
|
|
continue;
|
|
|
|
TWeakObjectPtr<ALandscapeProxy>* FoundLandscapePtr = HoudiniAssetComponent->LandscapeComponents.Find( *HoudiniGeoPartObject );
|
|
if ( !FoundLandscapePtr || !FoundLandscapePtr->IsValid() )
|
|
continue;
|
|
|
|
ALandscapeProxy* FoundLandscape = FoundLandscapePtr->Get();
|
|
if (!FoundLandscape || !FoundLandscape->IsValidLowLevel())
|
|
continue;
|
|
|
|
if ( FoundLandscape != Landscape )
|
|
continue;
|
|
|
|
// Retrieve the material interface which is being replaced.
|
|
UMaterialInterface * OldMaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial();
|
|
if ( OldMaterialInterface == MaterialInterface )
|
|
continue;
|
|
|
|
// Record replaced material.
|
|
const bool bReplaceSuccessful = HoudiniAssetComponent->ReplaceMaterial(
|
|
*HoudiniGeoPartObject, MaterialInterface, OldMaterialInterface, MaterialIdx );
|
|
|
|
if ( !bReplaceSuccessful )
|
|
continue;
|
|
|
|
{
|
|
FScopedTransaction Transaction(
|
|
TEXT( HOUDINI_MODULE_EDITOR ),
|
|
LOCTEXT( "HoudiniMaterialReplacement", "Houdini Material Replacement" ), HoudiniAssetComponent );
|
|
|
|
// Replace material on static mesh.
|
|
Landscape->Modify();
|
|
|
|
if ( MaterialIdx == 0 )
|
|
Landscape->LandscapeMaterial = MaterialInterface;
|
|
else
|
|
Landscape->LandscapeHoleMaterial = MaterialInterface;
|
|
|
|
//Landscape->UpdateAllComponentMaterialInstances();
|
|
|
|
// As UpdateAllComponentMaterialInstances() is not accessible to us, we'll try to access the Material's UProperty
|
|
// to trigger a fake Property change event that will call the Update function...
|
|
FProperty* FoundProperty = FindFProperty< FProperty >( Landscape->GetClass(), ( MaterialIdx == 0 ) ? TEXT( "LandscapeMaterial" ) : TEXT( "LandscapeHoleMaterial" ) );
|
|
if ( FoundProperty )
|
|
{
|
|
FPropertyChangedEvent PropChanged( FoundProperty, EPropertyChangeType::ValueSet );
|
|
Landscape->PostEditChangeProperty( PropChanged );
|
|
}
|
|
else
|
|
{
|
|
// The only way to update the material for now is to recook/recreate the landscape...
|
|
HoudiniAssetComponent->StartTaskAssetCookingManual();
|
|
}
|
|
}
|
|
|
|
HoudiniAssetComponent->UpdateEditorProperties( false );
|
|
bViewportNeedsUpdate = true;
|
|
}
|
|
|
|
if ( GEditor && bViewportNeedsUpdate )
|
|
GEditor->RedrawAllViewports();
|
|
}
|
|
|
|
TSharedRef< SWidget >
|
|
FHoudiniAssetComponentDetails::OnGetMaterialInterfaceMenuContent(
|
|
UMaterialInterface * MaterialInterface,
|
|
UStaticMesh * StaticMesh, FHoudiniGeoPartObject * HoudiniGeoPartObject, int32 MaterialIdx )
|
|
{
|
|
TArray< const UClass * > AllowedClasses;
|
|
AllowedClasses.Add( UMaterialInterface::StaticClass() );
|
|
|
|
TArray< UFactory * > NewAssetFactories;
|
|
|
|
return PropertyCustomizationHelpers::MakeAssetPickerWithMenu(
|
|
FAssetData( MaterialInterface ), true, AllowedClasses,
|
|
NewAssetFactories, OnShouldFilterMaterialInterface,
|
|
FOnAssetSelected::CreateSP(
|
|
this, &FHoudiniAssetComponentDetails::OnMaterialInterfaceSelected,
|
|
StaticMesh, HoudiniGeoPartObject, MaterialIdx ),
|
|
FSimpleDelegate::CreateSP( this, &FHoudiniAssetComponentDetails::CloseMaterialInterfaceComboButton ) );
|
|
}
|
|
|
|
TSharedRef< SWidget >
|
|
FHoudiniAssetComponentDetails::OnGetMaterialInterfaceMenuContent(
|
|
UMaterialInterface * MaterialInterface,
|
|
ALandscapeProxy * Landscape, FHoudiniGeoPartObject * HoudiniGeoPartObject, int32 MaterialIdx)
|
|
{
|
|
TArray< const UClass * > AllowedClasses;
|
|
AllowedClasses.Add( UMaterialInterface::StaticClass() );
|
|
|
|
TArray< UFactory * > NewAssetFactories;
|
|
|
|
return PropertyCustomizationHelpers::MakeAssetPickerWithMenu(
|
|
FAssetData(MaterialInterface), true, AllowedClasses,
|
|
NewAssetFactories, OnShouldFilterMaterialInterface,
|
|
FOnAssetSelected::CreateSP(
|
|
this, &FHoudiniAssetComponentDetails::OnMaterialInterfaceSelected,
|
|
Landscape, HoudiniGeoPartObject, MaterialIdx ),
|
|
FSimpleDelegate::CreateSP( this, &FHoudiniAssetComponentDetails::CloseMaterialInterfaceComboButton ) );
|
|
}
|
|
|
|
void
|
|
FHoudiniAssetComponentDetails::OnMaterialInterfaceSelected(
|
|
const FAssetData & AssetData, UStaticMesh * StaticMesh,
|
|
FHoudiniGeoPartObject * HoudiniGeoPartObject, int32 MaterialIdx )
|
|
{
|
|
TPairInitializer< UStaticMesh *, int32 > Pair( StaticMesh, MaterialIdx );
|
|
TSharedPtr< SComboButton > AssetComboButton = MaterialInterfaceComboButtons[ Pair ];
|
|
if ( AssetComboButton.IsValid() )
|
|
{
|
|
AssetComboButton->SetIsOpen( false );
|
|
|
|
UObject * Object = AssetData.GetAsset();
|
|
OnMaterialInterfaceDropped( Object, StaticMesh, HoudiniGeoPartObject, MaterialIdx );
|
|
}
|
|
}
|
|
|
|
void
|
|
FHoudiniAssetComponentDetails::OnMaterialInterfaceSelected(
|
|
const FAssetData & AssetData, ALandscapeProxy* Landscape,
|
|
FHoudiniGeoPartObject * HoudiniGeoPartObject, int32 MaterialIdx )
|
|
{
|
|
TPairInitializer< ALandscapeProxy *, int32 > Pair( Landscape, MaterialIdx );
|
|
TSharedPtr< SComboButton > AssetComboButton = LandscapeMaterialInterfaceComboButtons[ Pair ];
|
|
if ( AssetComboButton.IsValid() )
|
|
{
|
|
AssetComboButton->SetIsOpen( false );
|
|
|
|
UObject * Object = AssetData.GetAsset();
|
|
OnMaterialInterfaceDropped( Object, Landscape, HoudiniGeoPartObject, MaterialIdx );
|
|
}
|
|
}
|
|
|
|
void
|
|
FHoudiniAssetComponentDetails::CloseMaterialInterfaceComboButton()
|
|
{
|
|
|
|
}
|
|
|
|
void
|
|
FHoudiniAssetComponentDetails::OnMaterialInterfaceBrowse( UMaterialInterface * MaterialInterface )
|
|
{
|
|
if ( GEditor )
|
|
{
|
|
TArray< UObject * > Objects;
|
|
Objects.Add( MaterialInterface );
|
|
GEditor->SyncBrowserToObjects( Objects );
|
|
}
|
|
}
|
|
|
|
FReply
|
|
FHoudiniAssetComponentDetails::OnResetMaterialInterfaceClicked(
|
|
UStaticMesh * StaticMesh,
|
|
FHoudiniGeoPartObject * HoudiniGeoPartObject,
|
|
int32 MaterialIdx )
|
|
{
|
|
bool bViewportNeedsUpdate = false;
|
|
|
|
for ( TArray< UHoudiniAssetComponent * >::TIterator
|
|
IterComponents( HoudiniAssetComponents ); IterComponents; ++IterComponents )
|
|
{
|
|
// Retrieve material interface which is being replaced.
|
|
UMaterialInterface * MaterialInterface = StaticMesh->StaticMaterials[ MaterialIdx ].MaterialInterface;
|
|
UMaterialInterface * MaterialInterfaceReplacement = Cast<UMaterialInterface>(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get());
|
|
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents;
|
|
if ( !HoudiniAssetComponent )
|
|
continue;
|
|
|
|
bool bMaterialRestored = false;
|
|
FString MaterialShopName;
|
|
if ( !HoudiniAssetComponent->GetReplacementMaterialShopName( *HoudiniGeoPartObject, MaterialInterface, MaterialShopName ) )
|
|
{
|
|
// This material was not replaced so there's no need to reset it
|
|
continue;
|
|
}
|
|
|
|
// Remove the replacement
|
|
HoudiniAssetComponent->RemoveReplacementMaterial( *HoudiniGeoPartObject, MaterialShopName );
|
|
|
|
// Try to find the original assignment, if not, we'll use the default material
|
|
UMaterialInterface * AssignedMaterial = HoudiniAssetComponent->GetAssignmentMaterial(MaterialShopName);
|
|
if ( AssignedMaterial )
|
|
MaterialInterfaceReplacement = AssignedMaterial;
|
|
|
|
// Replace material on static mesh.
|
|
StaticMesh->Modify();
|
|
StaticMesh->StaticMaterials[ MaterialIdx ].MaterialInterface = MaterialInterfaceReplacement;
|
|
|
|
UStaticMeshComponent * StaticMeshComponent = HoudiniAssetComponent->LocateStaticMeshComponent( StaticMesh );
|
|
if ( StaticMeshComponent )
|
|
{
|
|
StaticMeshComponent->Modify();
|
|
StaticMeshComponent->SetMaterial( MaterialIdx, MaterialInterfaceReplacement );
|
|
|
|
bMaterialRestored = true;
|
|
}
|
|
|
|
TArray< UInstancedStaticMeshComponent * > InstancedStaticMeshComponents;
|
|
if ( HoudiniAssetComponent->LocateInstancedStaticMeshComponents( StaticMesh, InstancedStaticMeshComponents ) )
|
|
{
|
|
for ( int32 Idx = 0; Idx < InstancedStaticMeshComponents.Num(); ++Idx )
|
|
{
|
|
UInstancedStaticMeshComponent * InstancedStaticMeshComponent = InstancedStaticMeshComponents[ Idx ];
|
|
if ( InstancedStaticMeshComponent )
|
|
{
|
|
InstancedStaticMeshComponent->Modify();
|
|
InstancedStaticMeshComponent->SetMaterial( MaterialIdx, MaterialInterfaceReplacement );
|
|
|
|
bMaterialRestored = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bMaterialRestored )
|
|
{
|
|
HoudiniAssetComponent->UpdateEditorProperties( false );
|
|
bViewportNeedsUpdate = true;
|
|
}
|
|
}
|
|
|
|
if ( GEditor && bViewportNeedsUpdate )
|
|
{
|
|
GEditor->RedrawAllViewports();
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply
|
|
FHoudiniAssetComponentDetails::OnResetMaterialInterfaceClicked(
|
|
ALandscapeProxy * Landscape, FHoudiniGeoPartObject * HoudiniGeoPartObject, int32 MaterialIdx)
|
|
{
|
|
bool bViewportNeedsUpdate = false;
|
|
|
|
for ( TArray< UHoudiniAssetComponent * >::TIterator
|
|
IterComponents( HoudiniAssetComponents ); IterComponents; ++IterComponents )
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = *IterComponents;
|
|
if ( !HoudiniAssetComponent )
|
|
continue;
|
|
|
|
TWeakObjectPtr<ALandscapeProxy>* FoundLandscapePtr = HoudiniAssetComponent->LandscapeComponents.Find( *HoudiniGeoPartObject );
|
|
if ( !FoundLandscapePtr )
|
|
continue;
|
|
|
|
ALandscapeProxy* FoundLandscape = FoundLandscapePtr->Get();
|
|
if ( !FoundLandscape || !FoundLandscape->IsValidLowLevel() )
|
|
continue;
|
|
|
|
if ( FoundLandscape != Landscape )
|
|
continue;
|
|
|
|
// Retrieve the material interface which is being replaced.
|
|
UMaterialInterface * MaterialInterface = MaterialIdx == 0 ? Landscape->GetLandscapeMaterial() : Landscape->GetLandscapeHoleMaterial();
|
|
UMaterialInterface * MaterialInterfaceReplacement = Cast<UMaterialInterface>(FHoudiniEngine::Get().GetHoudiniDefaultMaterial().Get());
|
|
|
|
bool bMaterialRestored = false;
|
|
FString MaterialShopName;
|
|
if ( !HoudiniAssetComponent->GetReplacementMaterialShopName( *HoudiniGeoPartObject, MaterialInterface, MaterialShopName ) )
|
|
{
|
|
// This material was not replaced so there's no need to reset it
|
|
continue;
|
|
}
|
|
|
|
// Remove the replacement
|
|
HoudiniAssetComponent->RemoveReplacementMaterial( *HoudiniGeoPartObject, MaterialShopName );
|
|
|
|
// Try to find the original assignment, if not, we'll use the default material
|
|
UMaterialInterface * AssignedMaterial = HoudiniAssetComponent->GetAssignmentMaterial( MaterialShopName );
|
|
if ( AssignedMaterial )
|
|
MaterialInterfaceReplacement = AssignedMaterial;
|
|
|
|
// Replace material on the landscape
|
|
Landscape->Modify();
|
|
|
|
if ( MaterialIdx == 0 )
|
|
Landscape->LandscapeMaterial = MaterialInterfaceReplacement;
|
|
else
|
|
Landscape->LandscapeHoleMaterial = MaterialInterfaceReplacement;
|
|
|
|
//Landscape->UpdateAllComponentMaterialInstances();
|
|
|
|
// As UpdateAllComponentMaterialInstances() is not accessible to us, we'll try to access the Material's UProperty
|
|
// to trigger a fake Property change event that will call the Update function...
|
|
FProperty* FoundProperty = FindFProperty< FProperty >( Landscape->GetClass(), ( MaterialIdx == 0 ) ? TEXT( "LandscapeMaterial" ) : TEXT( "LandscapeHoleMaterial" ) );
|
|
if ( FoundProperty )
|
|
{
|
|
FPropertyChangedEvent PropChanged( FoundProperty, EPropertyChangeType::ValueSet );
|
|
Landscape->PostEditChangeProperty( PropChanged );
|
|
}
|
|
else
|
|
{
|
|
// The only way to update the material for now is to recook/recreate the landscape...
|
|
HoudiniAssetComponent->StartTaskAssetCookingManual();
|
|
}
|
|
|
|
HoudiniAssetComponent->UpdateEditorProperties( false );
|
|
bViewportNeedsUpdate = true;
|
|
}
|
|
|
|
if ( GEditor && bViewportNeedsUpdate )
|
|
{
|
|
GEditor->RedrawAllViewports();
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
void
|
|
FHoudiniAssetComponentDetails::OnHoudiniAssetDropped( UObject * InObject )
|
|
{
|
|
if ( InObject )
|
|
{
|
|
UHoudiniAsset * HoudiniAsset = Cast< UHoudiniAsset >( InObject );
|
|
if ( HoudiniAsset && HoudiniAssetComponents.Num() > 0 )
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ];
|
|
|
|
// Assign asset to component.
|
|
HoudiniAssetComponent->SetHoudiniAsset( HoudiniAsset );
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
FHoudiniAssetComponentDetails::OnHoudiniAssetDraggedOver( const UObject * InObject ) const
|
|
{
|
|
return ( InObject && InObject->IsA( UHoudiniAsset::StaticClass() ) );
|
|
}
|
|
|
|
const FSlateBrush *
|
|
FHoudiniAssetComponentDetails::GetHoudiniAssetThumbnailBorder() const
|
|
{
|
|
if ( HoudiniAssetThumbnailBorder.IsValid() && HoudiniAssetThumbnailBorder->IsHovered() )
|
|
return FEditorStyle::GetBrush( "PropertyEditor.AssetThumbnailLight" );
|
|
else
|
|
return FEditorStyle::GetBrush( "PropertyEditor.AssetThumbnailShadow" );
|
|
}
|
|
|
|
TSharedRef< SWidget >
|
|
FHoudiniAssetComponentDetails::OnGetHoudiniAssetMenuContent()
|
|
{
|
|
TArray< const UClass * > AllowedClasses;
|
|
AllowedClasses.Add( UHoudiniAsset::StaticClass() );
|
|
|
|
TArray< UFactory * > NewAssetFactories;
|
|
|
|
UHoudiniAsset * HoudiniAsset = nullptr;
|
|
if ( HoudiniAssetComponents.Num() > 0 )
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ];
|
|
HoudiniAsset = HoudiniAssetComponent->HoudiniAsset;
|
|
}
|
|
|
|
return PropertyCustomizationHelpers::MakeAssetPickerWithMenu(
|
|
FAssetData( HoudiniAsset ), true,
|
|
AllowedClasses, NewAssetFactories, OnShouldFilterHoudiniAsset,
|
|
FOnAssetSelected::CreateSP( this, &FHoudiniAssetComponentDetails::OnHoudiniAssetSelected ),
|
|
FSimpleDelegate::CreateSP( this, &FHoudiniAssetComponentDetails::CloseHoudiniAssetComboButton ) );
|
|
}
|
|
|
|
void
|
|
FHoudiniAssetComponentDetails::OnHoudiniAssetSelected( const FAssetData & AssetData )
|
|
{
|
|
if ( HoudiniAssetComboButton.IsValid() )
|
|
{
|
|
HoudiniAssetComboButton->SetIsOpen( false );
|
|
|
|
UObject * Object = AssetData.GetAsset();
|
|
OnHoudiniAssetDropped( Object );
|
|
}
|
|
}
|
|
|
|
void
|
|
FHoudiniAssetComponentDetails::CloseHoudiniAssetComboButton()
|
|
{
|
|
|
|
}
|
|
|
|
void
|
|
FHoudiniAssetComponentDetails::OnHoudiniAssetBrowse()
|
|
{
|
|
if ( GEditor )
|
|
{
|
|
UHoudiniAsset * HoudiniAsset = nullptr;
|
|
if ( HoudiniAssetComponents.Num() > 0 )
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ];
|
|
HoudiniAsset = HoudiniAssetComponent->HoudiniAsset;
|
|
|
|
if( HoudiniAsset )
|
|
{
|
|
TArray< UObject * > Objects;
|
|
Objects.Add(HoudiniAsset);
|
|
GEditor->SyncBrowserToObjects( Objects );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FReply
|
|
FHoudiniAssetComponentDetails::OnResetHoudiniAssetClicked()
|
|
{
|
|
if ( HoudiniAssetComponents.Num() > 0 )
|
|
{
|
|
UHoudiniAssetComponent * HoudiniAssetComponent = HoudiniAssetComponents[ 0 ];
|
|
HoudiniAssetComponent->SetHoudiniAsset( nullptr );
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
ECheckBoxState
|
|
FHoudiniAssetComponentDetails::IsCheckedComponentSettingCooking( UHoudiniAssetComponent * HoudiniAssetComponent ) const
|
|
{
|
|
if ( HoudiniAssetComponent && HoudiniAssetComponent->bEnableCooking )
|
|
return ECheckBoxState::Checked;
|
|
|
|
return ECheckBoxState::Unchecked;
|
|
}
|
|
|
|
ECheckBoxState
|
|
FHoudiniAssetComponentDetails::IsCheckedComponentSettingUploadTransform(
|
|
UHoudiniAssetComponent * HoudiniAssetComponent ) const
|
|
{
|
|
if ( HoudiniAssetComponent && HoudiniAssetComponent->bUploadTransformsToHoudiniEngine )
|
|
return ECheckBoxState::Checked;
|
|
|
|
return ECheckBoxState::Unchecked;
|
|
}
|
|
|
|
ECheckBoxState
|
|
FHoudiniAssetComponentDetails::IsCheckedComponentSettingTransformCooking(
|
|
UHoudiniAssetComponent * HoudiniAssetComponent ) const
|
|
{
|
|
if ( HoudiniAssetComponent && HoudiniAssetComponent->bTransformChangeTriggersCooks )
|
|
return ECheckBoxState::Checked;
|
|
|
|
return ECheckBoxState::Unchecked;
|
|
}
|
|
|
|
ECheckBoxState
|
|
FHoudiniAssetComponentDetails::IsCheckedComponentSettingUseHoudiniMaterials(
|
|
UHoudiniAssetComponent * HoudiniAssetComponent ) const
|
|
{
|
|
if ( HoudiniAssetComponent && HoudiniAssetComponent->bUseHoudiniMaterials )
|
|
return ECheckBoxState::Checked;
|
|
|
|
return ECheckBoxState::Unchecked;
|
|
}
|
|
|
|
ECheckBoxState
|
|
FHoudiniAssetComponentDetails::IsCheckedComponentSettingCookingTriggersDownstreamCooks(
|
|
UHoudiniAssetComponent * HoudiniAssetComponent ) const
|
|
{
|
|
if ( HoudiniAssetComponent && HoudiniAssetComponent->bCookingTriggersDownstreamCooks )
|
|
return ECheckBoxState::Checked;
|
|
|
|
return ECheckBoxState::Unchecked;
|
|
}
|
|
|
|
void
|
|
FHoudiniAssetComponentDetails::CheckStateChangedComponentSettingCooking(
|
|
ECheckBoxState NewState,
|
|
UHoudiniAssetComponent * HoudiniAssetComponent )
|
|
{
|
|
if ( HoudiniAssetComponent )
|
|
HoudiniAssetComponent->bEnableCooking = ( NewState == ECheckBoxState::Checked );
|
|
}
|
|
|
|
void
|
|
FHoudiniAssetComponentDetails::CheckStateChangedComponentSettingUploadTransform(
|
|
ECheckBoxState NewState,
|
|
UHoudiniAssetComponent * HoudiniAssetComponent )
|
|
{
|
|
if ( HoudiniAssetComponent )
|
|
HoudiniAssetComponent->bUploadTransformsToHoudiniEngine = ( NewState == ECheckBoxState::Checked );
|
|
}
|
|
|
|
void
|
|
FHoudiniAssetComponentDetails::CheckStateChangedComponentSettingTransformCooking(
|
|
ECheckBoxState NewState,
|
|
UHoudiniAssetComponent * HoudiniAssetComponent )
|
|
{
|
|
if ( HoudiniAssetComponent )
|
|
HoudiniAssetComponent->bTransformChangeTriggersCooks = ( NewState == ECheckBoxState::Checked );
|
|
}
|
|
|
|
void
|
|
FHoudiniAssetComponentDetails::CheckStateChangedComponentSettingUseHoudiniMaterials(
|
|
ECheckBoxState NewState,
|
|
UHoudiniAssetComponent * HoudiniAssetComponent )
|
|
{
|
|
if ( HoudiniAssetComponent )
|
|
HoudiniAssetComponent->bUseHoudiniMaterials = ( NewState == ECheckBoxState::Checked );
|
|
}
|
|
|
|
void
|
|
FHoudiniAssetComponentDetails::CheckStateChangedComponentSettingCookingTriggersDownstreamCooks(
|
|
ECheckBoxState NewState,
|
|
UHoudiniAssetComponent* HoudiniAssetComponent )
|
|
{
|
|
if ( HoudiniAssetComponent )
|
|
HoudiniAssetComponent->bCookingTriggersDownstreamCooks = ( NewState == ECheckBoxState::Checked );
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE |