/* * Copyright (c) <2021> Side Effects Software Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. The name of Side Effects Software may not be used to endorse or * promote products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "HoudiniPDGDetails.h" #include "HoudiniEngineEditorPrivatePCH.h" #include "HoudiniPDGAssetLink.h" #include "HoudiniPDGManager.h" #include "HoudiniEngineUtils.h" #include "HoudiniEngineRuntimePrivatePCH.h" #include "HoudiniAssetActor.h" #include "HoudiniEngine.h" #include "HoudiniEngineBakeUtils.h" #include "HoudiniEngineCommands.h" #include "HoudiniEngineDetails.h" #include "HoudiniEngineEditor.h" #include "HoudiniEngineEditorUtils.h" #include "DetailCategoryBuilder.h" #include "DetailLayoutBuilder.h" #include "IDetailGroup.h" #include "IDetailCustomization.h" #include "PropertyCustomizationHelpers.h" #include "DetailWidgetRow.h" #include "ScopedTransaction.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SCheckBox.h" #include "Widgets/Input/SComboBox.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Images/SImage.h" #include "Widgets/SBoxPanel.h" #include "Widgets/Layout/SSpacer.h" #include "Framework/SlateDelegates.h" #include "Templates/SharedPointer.h" #include "Internationalization/Internationalization.h" #define LOCTEXT_NAMESPACE HOUDINI_LOCTEXT_NAMESPACE #define HOUDINI_ENGINE_UI_SECTION_PDG_BAKE 2 void FHoudiniPDGDetails::CreateWidget( IDetailCategoryBuilder& HouPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink) { if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) return; // PDG ASSET FHoudiniPDGDetails::AddPDGAssetWidget(HouPDGCategory, InPDGAssetLink); // TOP NETWORKS FHoudiniPDGDetails::AddTOPNetworkWidget(HouPDGCategory, InPDGAssetLink); // PDG EVENT MESSAGES } void FHoudiniPDGDetails::AddPDGAssetWidget( IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink) { // PDG STATUS ROW AddPDGAssetStatus(InPDGCategory, InPDGAssetLink); // Commandlet Status row AddPDGCommandletStatus(InPDGCategory, FHoudiniEngine::Get().GetPDGCommandletStatus()); // REFRESH / RESET Buttons { TSharedRef RefreshHBox = SNew(SHorizontalBox); TSharedPtr ResetHBox = SNew(SHorizontalBox); FDetailWidgetRow& PDGRefreshResetRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) .WholeRowContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .HAlign(HAlign_Center) [ SNew(SBox) .WidthOverride(200.0f) [ SNew(SButton) //.Text(LOCTEXT("Refresh", "Refresh")) .ToolTipText(LOCTEXT("RefreshTooltip", "Refreshes infos displayed by the the PDG Asset Link")) .ContentPadding(FMargin(5.0f, 5.0f)) .VAlign(VAlign_Center) .HAlign(HAlign_Center) .OnClicked_Lambda([InPDGAssetLink]() { FHoudiniPDGDetails::RefreshPDGAssetLink(InPDGAssetLink); return FReply::Handled(); }) .Content() [ SAssignNew(RefreshHBox, SHorizontalBox) ] ] ] + SHorizontalBox::Slot() .AutoWidth() [ SNew(SBox) .WidthOverride(200.0f) [ SNew(SButton) //.Text(LOCTEXT("Reset", "Reset")) .ToolTipText(LOCTEXT("ResetTooltip", "Resets the PDG Asset Link")) .ContentPadding(FMargin(5.0f, 5.0f)) .VAlign(VAlign_Center) .HAlign(HAlign_Center) .OnClicked_Lambda([InPDGAssetLink]() { // TODO: RESET USELESS? FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); return FReply::Handled(); }) .Content() [ SAssignNew(ResetHBox, SHorizontalBox) ] ] ] ]; TSharedPtr RefreshIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGRefreshIconBrush(); if (RefreshIconBrush.IsValid()) { TSharedPtr RefreshImage; RefreshHBox->AddSlot() .MaxWidth(16.0f) [ SNew(SBox) .WidthOverride(16.0f) .HeightOverride(16.0f) [ SAssignNew(RefreshImage, SImage) ] ]; RefreshImage->SetImage( TAttribute::Create( TAttribute::FGetter::CreateLambda([RefreshIconBrush]() { return RefreshIconBrush.Get(); }))); } RefreshHBox->AddSlot() .Padding(5.0, 0.0, 0.0, 0.0) .VAlign(VAlign_Center) .AutoWidth() [ SNew(STextBlock) .Text(LOCTEXT("Refresh", "Refresh")) ]; TSharedPtr ResetIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGResetIconBrush(); if (ResetIconBrush.IsValid()) { TSharedPtr ResetImage; ResetHBox->AddSlot() .MaxWidth(16.0f) [ SNew(SBox) .WidthOverride(16.0f) .HeightOverride(16.0f) [ SAssignNew(ResetImage, SImage) ] ]; ResetImage->SetImage( TAttribute::Create( TAttribute::FGetter::CreateLambda([ResetIconBrush]() { return ResetIconBrush.Get(); }))); } ResetHBox->AddSlot() .Padding(5.0, 0.0, 0.0, 0.0) .VAlign(VAlign_Center) .AutoWidth() [ SNew(STextBlock) .Text(LOCTEXT("Reset", "Reset")) ]; } // TOP NODE FILTER { FText Tooltip = FText::FromString(TEXT("When enabled, the TOP Node Filter will only display the TOP Nodes found in the current network that start with the filter prefix. Disabling the Filter will display all of the TOP Network's TOP Nodes.")); // Lambda for changing the filter value auto ChangeTOPNodeFilter = [InPDGAssetLink](const FString& NewValue) { if (InPDGAssetLink->TOPNodeFilter.Equals(NewValue)) return; // Record a transaction for undo/redo FScopedTransaction Transaction( TEXT(HOUDINI_MODULE_RUNTIME), LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), InPDGAssetLink); InPDGAssetLink->Modify(); InPDGAssetLink->TOPNodeFilter = NewValue; // Notify that we have changed the property FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPNodeFilter), InPDGAssetLink); }; FDetailWidgetRow& PDGFilterRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); // Disable if PDG is not linked DisableIfPDGNotLinked(PDGFilterRow, InPDGAssetLink); PDGFilterRow.NameWidget.Widget = SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f, 0.0f) [ // Checkbox enable filter SNew(SCheckBox) .IsChecked_Lambda([InPDGAssetLink]() { return InPDGAssetLink->bUseTOPNodeFilter ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;; }) .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) { bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; if (InPDGAssetLink->bUseTOPNodeFilter == bNewState) return; // Record a transaction for undo/redo FScopedTransaction Transaction( TEXT(HOUDINI_MODULE_RUNTIME), LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), InPDGAssetLink); InPDGAssetLink->Modify(); InPDGAssetLink->bUseTOPNodeFilter = bNewState; // Notify that we have changed the property FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPNodeFilter), InPDGAssetLink); }) .ToolTipText(Tooltip) ] + SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f, 0.0f) [ SNew(STextBlock) .Text(FText::FromString(TEXT("TOP Node Filter"))) .ToolTipText(Tooltip) ]; PDGFilterRow.ValueWidget.Widget = SNew(SHorizontalBox) + SHorizontalBox::Slot().FillWidth(1.0f) [ SNew(SEditableTextBox) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) .ToolTipText(Tooltip) .Text_Lambda([InPDGAssetLink]() { if (!IsValid(InPDGAssetLink)) return FText(); return FText::FromString(InPDGAssetLink->TOPNodeFilter); }) .OnTextCommitted_Lambda([ChangeTOPNodeFilter](const FText& Val, ETextCommit::Type TextCommitType) { ChangeTOPNodeFilter(Val.ToString()); }) ] + SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f, 0.0f) .VAlign(VAlign_Center) [ SNew(SButton) .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) .ButtonStyle(FEditorStyle::Get(), "NoBorder") .ContentPadding(0) .Visibility(EVisibility::Visible) .OnClicked_Lambda([=]() { FString DefaultFilter = TEXT(HAPI_UNREAL_PDG_DEFAULT_TOP_FILTER); ChangeTOPNodeFilter(DefaultFilter); return FReply::Handled(); }) [ SNew(SImage) .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) ] ]; } // TOP OUTPUT FILTER { // Lambda for changing the filter value FText Tooltip = FText::FromString(TEXT("When enabled, the Work Item Output Files created for the TOP Nodes found in the current network that start with the filter prefix will be automatically loaded int the world after being cooked.")); auto ChangeTOPOutputFilter = [InPDGAssetLink](const FString& NewValue) { if (InPDGAssetLink->TOPOutputFilter.Equals(NewValue)) return; // Record a transaction for undo/redo FScopedTransaction Transaction( TEXT(HOUDINI_MODULE_RUNTIME), LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), InPDGAssetLink); InPDGAssetLink->Modify(); InPDGAssetLink->TOPOutputFilter = NewValue; // Notify that we have changed the property FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, TOPOutputFilter), InPDGAssetLink); }; FDetailWidgetRow& PDGOutputFilterRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); // Disable if PDG is not linked DisableIfPDGNotLinked(PDGOutputFilterRow, InPDGAssetLink); PDGOutputFilterRow.NameWidget.Widget = SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f, 0.0f) [ // Checkbox enable filter SNew(SCheckBox) .IsChecked_Lambda([InPDGAssetLink]() { return InPDGAssetLink->bUseTOPOutputFilter ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) { bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; if (InPDGAssetLink->bUseTOPOutputFilter == bNewState) return; // Record a transaction for undo/redo FScopedTransaction Transaction( TEXT(HOUDINI_MODULE_RUNTIME), LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), InPDGAssetLink); InPDGAssetLink->Modify(); InPDGAssetLink->bUseTOPOutputFilter = bNewState; // Notify that we have changed the property FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bUseTOPOutputFilter), InPDGAssetLink); }) .ToolTipText(Tooltip) ] + SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f, 0.0f) [ SNew(STextBlock) .Text(FText::FromString(TEXT("TOP Output Filter"))) .ToolTipText(Tooltip) ]; PDGOutputFilterRow.ValueWidget.Widget = SNew(SHorizontalBox) + SHorizontalBox::Slot().FillWidth(1.0f) [ SNew(SEditableTextBox) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) .Text_Lambda([InPDGAssetLink]() { if (!IsValid(InPDGAssetLink)) return FText(); return FText::FromString(InPDGAssetLink->TOPOutputFilter); }) .OnTextCommitted_Lambda([ChangeTOPOutputFilter](const FText& Val, ETextCommit::Type TextCommitType) { ChangeTOPOutputFilter(Val.ToString()); }) .ToolTipText(Tooltip) ] + SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f, 0.0f) .VAlign(VAlign_Center) [ SNew(SButton) .ToolTipText(LOCTEXT("RevertToDefault", "Revert to default")) .ButtonStyle(FEditorStyle::Get(), "NoBorder") .ContentPadding(0) .Visibility(EVisibility::Visible) .OnClicked_Lambda([ChangeTOPOutputFilter]() { FString DefaultFilter = TEXT(HAPI_UNREAL_PDG_DEFAULT_TOP_OUTPUT_FILTER); ChangeTOPOutputFilter(DefaultFilter); return FReply::Handled(); }) [ SNew(SImage) .Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault")) ] ]; } // Checkbox: Autocook { FText Tooltip = FText::FromString(TEXT("When enabled, the selected TOP Network's output will automatically cook after succesfully cooking the PDG Asset Link HDA.")); FDetailWidgetRow& PDGAutocookRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); // Disable if PDG is not linked DisableIfPDGNotLinked(PDGAutocookRow, InPDGAssetLink); PDGAutocookRow.NameWidget.Widget = SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f, 0.0f) [ SNew(STextBlock) .Text(FText::FromString(TEXT("Auto-cook"))) .ToolTipText(Tooltip) ]; TSharedPtr AutoCookCheckBox; PDGAutocookRow.ValueWidget.Widget = SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f, 0.0f) [ // Checkbox SAssignNew(AutoCookCheckBox, SCheckBox) .IsChecked_Lambda([InPDGAssetLink]() { return InPDGAssetLink->bAutoCook ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) { bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; if (!InPDGAssetLink || InPDGAssetLink->bAutoCook == bNewState) return; // Record a transaction for undo/redo FScopedTransaction Transaction( TEXT(HOUDINI_MODULE_RUNTIME), LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), InPDGAssetLink); InPDGAssetLink->Modify(); InPDGAssetLink->bAutoCook = bNewState; FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bAutoCook), InPDGAssetLink); }) .ToolTipText(Tooltip) ]; } // Output parent actor selector { IDetailPropertyRow* PDGOutputParentActorRow = InPDGCategory.AddExternalObjectProperty({ InPDGAssetLink }, "OutputParentActor"); if (PDGOutputParentActorRow) { TAttribute PDGOutputParentActorRowEnabled; BindDisableIfPDGNotLinked(PDGOutputParentActorRowEnabled, InPDGAssetLink); PDGOutputParentActorRow->IsEnabled(PDGOutputParentActorRowEnabled); TSharedPtr NameWidget; TSharedPtr ValueWidget; PDGOutputParentActorRow->GetDefaultWidgets(NameWidget, ValueWidget); PDGOutputParentActorRow->DisplayName(FText::FromString(TEXT("Output Parent Actor"))); PDGOutputParentActorRow->ToolTip(FText::FromString( TEXT("The PDG Output Actors will be created under this parent actor. If not set, then the PDG Output Actors will be created under a new folder."))); } } // Add bake widgets for PDG output CreatePDGBakeWidgets(InPDGCategory, InPDGAssetLink); // TODO: move this to a better place: the baking code is in HoudiniEngineEditor, the PDG manager (that knows about // when work object results are loaded is in HoudiniEngine and the PDGAssetLink is in HoudiniEngineRuntime). So // we bind an auto-bake helper function here. Maybe the baking code can move to HoudiniEngine? if (InPDGAssetLink->AutoBakeDelegateHandle.IsValid()) InPDGAssetLink->OnWorkResultObjectLoaded.Remove(InPDGAssetLink->AutoBakeDelegateHandle); InPDGAssetLink->AutoBakeDelegateHandle = InPDGAssetLink->OnWorkResultObjectLoaded.AddStatic(FHoudiniEngineBakeUtils::CheckPDGAutoBakeAfterResultObjectLoaded); // WORK ITEM STATUS { FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); // Disable if PDG is not linked DisableIfPDGNotLinked(PDGStatusRow, InPDGAssetLink); FHoudiniPDGDetails::AddWorkItemStatusWidget( PDGStatusRow, TEXT("Asset Work Item Status"), InPDGAssetLink, false); } } bool FHoudiniPDGDetails::GetPDGStatusAndColor( UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutPDGStatusString, FLinearColor& OutPDGStatusColor) { OutPDGStatusString = FString(); OutPDGStatusColor = FLinearColor::White; if (!IsValid(InPDGAssetLink)) return false; switch (InPDGAssetLink->LinkState) { case EPDGLinkState::Linked: OutPDGStatusString = TEXT("PDG is READY"); OutPDGStatusColor = FLinearColor::Green; break; case EPDGLinkState::Linking: OutPDGStatusString = TEXT("PDG is Linking"); OutPDGStatusColor = FLinearColor::Yellow; break; case EPDGLinkState::Error_Not_Linked: OutPDGStatusString = TEXT("PDG is ERRORED"); OutPDGStatusColor = FLinearColor::Red; break; case EPDGLinkState::Inactive: OutPDGStatusString = TEXT("PDG is INACTIVE"); OutPDGStatusColor = FLinearColor::White; break; default: return false; } return true; } void FHoudiniPDGDetails::AddPDGAssetStatus( IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink *InPDGAssetLink) { FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) .WholeRowContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0f) .Padding(2.0f, 0.0f) .VAlign(VAlign_Center) .HAlign(HAlign_Center) [ SNew(STextBlock) .Text_Lambda([InPDGAssetLink]() { FString PDGStatusString; FLinearColor PDGStatusColor; GetPDGStatusAndColor(InPDGAssetLink, PDGStatusString, PDGStatusColor); return FText::FromString(PDGStatusString); }) .ColorAndOpacity_Lambda([InPDGAssetLink]() { FString PDGStatusString; FLinearColor PDGStatusColor; GetPDGStatusAndColor(InPDGAssetLink, PDGStatusString, PDGStatusColor); return FSlateColor(PDGStatusColor); }) ] ]; } void FHoudiniPDGDetails::GetPDGCommandletStatus(FString& OutStatusString, FLinearColor& OutStatusColor) { OutStatusString = FString(); OutStatusColor = FLinearColor::White; switch (FHoudiniEngine::Get().GetPDGCommandletStatus()) { case EHoudiniBGEOCommandletStatus::Connected: OutStatusString = TEXT("Async importer is CONNECTED"); OutStatusColor = FLinearColor::Green; break; case EHoudiniBGEOCommandletStatus::Running: OutStatusString = TEXT("Async importer is Running"); OutStatusColor = FLinearColor::Yellow; break; case EHoudiniBGEOCommandletStatus::Crashed: OutStatusString = TEXT("Async importer has CRASHED"); OutStatusColor = FLinearColor::Red; break; case EHoudiniBGEOCommandletStatus::NotStarted: OutStatusString = TEXT("Async importer is NOT STARTED"); OutStatusColor = FLinearColor::White; break; } } void FHoudiniPDGDetails::AddPDGCommandletStatus( IDetailCategoryBuilder& InPDGCategory, const EHoudiniBGEOCommandletStatus& InCommandletStatus) { FDetailWidgetRow& PDGStatusRow = InPDGCategory.AddCustomRow(FText::GetEmpty()) .WholeRowContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0f) .Padding(2.0f, 0.0f) .VAlign(VAlign_Center) .HAlign(HAlign_Center) [ SNew(STextBlock) .Visibility_Lambda([]() { const UHoudiniRuntimeSettings* Settings = GetDefault(); if (IsValid(Settings)) { return FHoudiniEngineCommands::IsPDGCommandletEnabled() ? EVisibility::Visible : EVisibility::Collapsed; } return EVisibility::Visible; }) .Text_Lambda([]() { FString StatusString; FLinearColor StatusColor; GetPDGCommandletStatus(StatusString, StatusColor); return FText::FromString(StatusString); }) .ColorAndOpacity_Lambda([]() { FString StatusString; FLinearColor StatusColor; GetPDGCommandletStatus(StatusString, StatusColor); return FSlateColor(StatusColor); }) ] ]; } bool FHoudiniPDGDetails::GetWorkItemTallyValueAndColor( UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode, const FString& InTallyItemString, int32& OutValue, FLinearColor& OutColor) { OutValue = 0; OutColor = FLinearColor::White; if (!IsValid(InAssetLink)) return false; bool bFound = false; const FWorkItemTallyBase* TallyPtr = nullptr; if (bInForSelectedNode) { UTOPNode* const TOPNode = InAssetLink->GetSelectedTOPNode(); if (TOPNode && !TOPNode->bHidden) TallyPtr = &(TOPNode->GetWorkItemTally()); } else TallyPtr = &(InAssetLink->WorkItemTally); if (TallyPtr) { if (InTallyItemString == TEXT("WAITING")) { // For now we add waiting and scheduled together, since there is no separate column for scheduled on the UI OutValue = TallyPtr->NumWaitingWorkItems() + TallyPtr->NumScheduledWorkItems(); OutColor = OutValue > 0 ? FLinearColor(0.0f, 1.0f, 1.0f) : FLinearColor::White; bFound = true; } else if (InTallyItemString == TEXT("COOKING")) { OutValue = TallyPtr->NumCookingWorkItems(); OutColor = OutValue > 0 ? FLinearColor::Yellow : FLinearColor::White; bFound = true; } else if (InTallyItemString == TEXT("COOKED")) { OutValue = TallyPtr->NumCookedWorkItems(); OutColor = OutValue > 0 ? FLinearColor::Green : FLinearColor::White; bFound = true; } else if (InTallyItemString == TEXT("FAILED")) { OutValue = TallyPtr->NumErroredWorkItems(); OutColor = OutValue > 0 ? FLinearColor::Red : FLinearColor::White; bFound = true; } } return bFound; } void FHoudiniPDGDetails::AddWorkItemStatusWidget( FDetailWidgetRow& InRow, const FString& InTitleString, UHoudiniPDGAssetLink* InAssetLink, bool bInForSelectedNode) { auto AddGridBox = [InAssetLink, bInForSelectedNode](const FString& Title) -> SHorizontalBox::FSlot& { return SHorizontalBox::Slot() .MaxWidth(500.0f) .Padding(0.0f, 0.0f, 2.0f, 0.0f) .VAlign(VAlign_Center) .HAlign(HAlign_Center) [ SNew(SVerticalBox) + SVerticalBox::Slot() .VAlign(VAlign_Center) .HAlign(HAlign_Center) .AutoHeight() .Padding(FMargin(1.0f, 2.0f)) [ SNew(SBorder) .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) .BorderBackgroundColor(FSlateColor(FLinearColor(0.6, 0.6, 0.6))) .Padding(FMargin(1.0f, 5.0f)) [ SNew(SBox) .WidthOverride(95.0f) .VAlign(VAlign_Center) .HAlign(HAlign_Center) [ SNew(STextBlock) .Text(FText::FromString(Title)) .ColorAndOpacity_Lambda([InAssetLink, bInForSelectedNode, Title]() { int32 Value; FLinearColor Color; GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); return FSlateColor(Color); }) ] ] ] + SVerticalBox::Slot() .VAlign(VAlign_Center) .HAlign(HAlign_Center) .AutoHeight() .Padding(FMargin(1.0f, 2.0f)) [ SNew(SBorder) .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) .BorderBackgroundColor(FSlateColor(FLinearColor(0.8, 0.8, 0.8))) .Padding(FMargin(1.0f, 5.0f)) [ SNew(SBox) .WidthOverride(95.0f) .VAlign(VAlign_Center) .HAlign(HAlign_Center) [ SNew(STextBlock) .Text_Lambda([InAssetLink, bInForSelectedNode, Title]() { int32 Value; FLinearColor Color; GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); return FText::AsNumber(Value); }) .ColorAndOpacity_Lambda([InAssetLink, bInForSelectedNode, Title]() { int32 Value; FLinearColor Color; GetWorkItemTallyValueAndColor(InAssetLink, bInForSelectedNode, Title, Value, Color); return FSlateColor(Color); }) ] ] ] ]; }; InRow.WholeRowContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(0.0f, 0.0f) .AutoWidth() [ SNew(SVerticalBox) +SVerticalBox::Slot() [ SNew(SSpacer) ] + SVerticalBox::Slot() .AutoHeight() .VAlign(VAlign_Center) .HAlign(HAlign_Center) .Padding(FMargin(0.0f, 2.0f)) [ SNew(STextBlock) .IsEnabled_Lambda([InAssetLink]() { return IsPDGLinked(InAssetLink); }) .Text(FText::FromString(InTitleString)) ] + SVerticalBox::Slot() .AutoHeight() .VAlign(VAlign_Center) .HAlign(HAlign_Center) .Padding(FMargin(0.0f, 2.0f)) [ SNew(SHorizontalBox) + AddGridBox(TEXT("WAITING")) + AddGridBox(TEXT("COOKING")) + AddGridBox(TEXT("COOKED")) + AddGridBox(TEXT("FAILED")) ] + SVerticalBox::Slot() [ SNew(SSpacer) ] ] ]; } void FHoudiniPDGDetails::AddTOPNetworkWidget( IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink ) { if (!InPDGAssetLink->GetSelectedTOPNetwork()) return; if (InPDGAssetLink->AllTOPNetworks.Num() <= 0) return; TOPNetworksPtr.Reset(); FString GroupLabel = TEXT("TOP Networks"); IDetailGroup& TOPNetWorkGrp = InPDGCategory.AddGroup(FName(*GroupLabel), FText::FromString(GroupLabel), false, true); // Combobox: TOP Network { FDetailWidgetRow& PDGTOPNetRow = TOPNetWorkGrp.AddWidgetRow(); // DisableIfPDGNotLinked(PDGTOPNetRow, InPDGAssetLink); PDGTOPNetRow.NameWidget.Widget = SNew(SHorizontalBox) + SHorizontalBox::Slot() [ SNew(STextBlock) .Text(FText::FromString(TEXT("TOP Network"))) ]; // Fill the TOP Networks SharedString array TOPNetworksPtr.SetNum(InPDGAssetLink->AllTOPNetworks.Num()); for(int32 Idx = 0; Idx < InPDGAssetLink->AllTOPNetworks.Num(); Idx++) { const UTOPNetwork* Network = InPDGAssetLink->AllTOPNetworks[Idx]; if (!IsValid(Network)) { TOPNetworksPtr[Idx] = MakeShareable(new FTextAndTooltip( Idx, TEXT("Invalid"), TEXT("Invalid") )); } else { TOPNetworksPtr[Idx] = MakeShareable(new FTextAndTooltip( Idx, FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(Network->NodeName, Network->NodePath), Network->NodePath )); } } if(TOPNetworksPtr.Num() <= 0) TOPNetworksPtr.Add(MakeShareable(new FTextAndTooltip(INDEX_NONE, "----"))); // Lambda for selecting another TOPNet auto OnTOPNetChanged = [InPDGAssetLink](TSharedPtr InNewChoice) { if (!InNewChoice.IsValid()) return; const int32 NewChoice = InNewChoice->Value; int32 NewSelectedIndex = -1; if (InPDGAssetLink->AllTOPNetworks.IsValidIndex(NewChoice)) NewSelectedIndex = NewChoice; if (InPDGAssetLink->SelectedTOPNetworkIndex == NewSelectedIndex) return; if (NewSelectedIndex < 0) return; // Record a transaction for undo/redo FScopedTransaction Transaction( TEXT(HOUDINI_MODULE_RUNTIME), LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), InPDGAssetLink); InPDGAssetLink->Modify(); InPDGAssetLink->SelectedTOPNetworkIndex = NewSelectedIndex; FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, SelectedTOPNetworkIndex), InPDGAssetLink); }; TSharedPtr HorizontalBoxTOPNet; TSharedPtr>> ComboBoxTOPNet; int32 SelectedIndex = TOPNetworksPtr.IndexOfByPredicate([InPDGAssetLink](const TSharedPtr& InEntry) { return InEntry.IsValid() && InEntry->Value == InPDGAssetLink->SelectedTOPNetworkIndex; }); if (SelectedIndex < 0) SelectedIndex = 0; PDGTOPNetRow.ValueWidget.Widget = SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(2, 2, 5, 2) .FillWidth(300.f) .MaxWidth(300.f) [ SAssignNew(ComboBoxTOPNet, SComboBox>) .OptionsSource(&TOPNetworksPtr) .InitiallySelectedItem(TOPNetworksPtr[SelectedIndex]) .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) { const FText ChoiceEntryText = FText::FromString(ChoiceEntry->Text); const FText ChoiceEntryToolTip = FText::FromString(ChoiceEntry->ToolTip); return SNew(STextBlock) .Text(ChoiceEntryText) .ToolTipText(ChoiceEntryToolTip) .Margin(2.0f) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); }) .OnSelectionChanged_Lambda([OnTOPNetChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) { return OnTOPNetChanged(NewChoice); }) [ SNew(STextBlock) .Text_Lambda([InPDGAssetLink]() { return FText::FromString(InPDGAssetLink->GetSelectedTOPNetworkName()); }) .ToolTipText_Lambda([InPDGAssetLink]() { UTOPNetwork const * const Network = InPDGAssetLink->GetSelectedTOPNetwork(); if (IsValid(Network)) { if (!Network->NodePath.IsEmpty()) return FText::FromString(Network->NodePath); else return FText::FromString(Network->NodeName); } else { return FText(); } }) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) ] ]; } // Buttons: DIRTY ALL / COOK OUTPUT { TSharedRef DirtyAllHBox = SNew(SHorizontalBox); TSharedPtr CookOutHBox = SNew(SHorizontalBox); FDetailWidgetRow& PDGDirtyCookRow = TOPNetWorkGrp.AddWidgetRow() .WholeRowContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Center) .HAlign(HAlign_Center) [ SNew(SBox) .WidthOverride(200.0f) [ SNew(SButton) //.Text(LOCTEXT("DirtyAll", "Dirty All")) .ToolTipText(LOCTEXT("DirtyAllTooltip", "Dirty all TOP nodes in the selected TOP network and clears all of its work item results.")) .ContentPadding(FMargin(5.0f, 5.0f)) .VAlign(VAlign_Center) .HAlign(HAlign_Center) .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNetwork()); }) .OnClicked_Lambda([InPDGAssetLink]() { if (IsValid(InPDGAssetLink)) { UTOPNetwork* const TOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); if (IsValid(TOPNetwork)) { if (IsPDGLinked(InPDGAssetLink)) { FHoudiniPDGManager::DirtyAll(TOPNetwork); // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); } else { UHoudiniPDGAssetLink::ClearTOPNetworkWorkItemResults(TOPNetwork); } } } return FReply::Handled(); }) .Content() [ SAssignNew(DirtyAllHBox, SHorizontalBox) ] ] ] + SHorizontalBox::Slot() .AutoWidth() [ SNew(SBox) .WidthOverride(200.0f) [ SNew(SButton) //.Text(LOCTEXT("CookOut", "Cook Output")) .ToolTipText(LOCTEXT("CookOutTooltip", "Cooks the output nodes of the selected TOP network")) .ContentPadding(FMargin(5.0f, 5.0f)) .VAlign(VAlign_Center) .HAlign(HAlign_Center) .IsEnabled_Lambda([InPDGAssetLink]() { if (!IsPDGLinked(InPDGAssetLink)) return false; const UTOPNetwork* const SelectedTOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); if (!IsValid(SelectedTOPNet)) return false; // Disable if there any nodes in the network that are already cooking return !SelectedTOPNet->AnyWorkItemsPending(); }) .OnClicked_Lambda([InPDGAssetLink]() { if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) { //InPDGAssetLink->WorkItemTally.ZeroAll(); FHoudiniPDGManager::CookOutput(InPDGAssetLink->GetSelectedTOPNetwork()); // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); } return FReply::Handled(); }) .Content() [ SAssignNew(CookOutHBox, SHorizontalBox) ] ] ] ]; TSharedPtr DirtyAllIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGDirtyAllIconBrush(); if (DirtyAllIconBrush.IsValid()) { TSharedPtr DirtyAllImage; DirtyAllHBox->AddSlot() .MaxWidth(16.0f) [ SNew(SBox) .WidthOverride(16.0f) .HeightOverride(16.0f) [ SAssignNew(DirtyAllImage, SImage) ] ]; DirtyAllImage->SetImage( TAttribute::Create( TAttribute::FGetter::CreateLambda([DirtyAllIconBrush]() { return DirtyAllIconBrush.Get(); }))); } DirtyAllHBox->AddSlot() .Padding(5.0, 0.0, 0.0, 0.0) .VAlign(VAlign_Center) .AutoWidth() [ SNew(STextBlock) .Text(LOCTEXT("DirtyAll", "Dirty All")) ]; TSharedPtr CookOutIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); if (CookOutIconBrush.IsValid()) { TSharedPtr CookOutImage; CookOutHBox->AddSlot() .MaxWidth(16.0f) [ SNew(SBox) .WidthOverride(16.0f) .HeightOverride(16.0f) [ SAssignNew(CookOutImage, SImage) ] ]; CookOutImage->SetImage( TAttribute::Create( TAttribute::FGetter::CreateLambda([CookOutIconBrush]() { return CookOutIconBrush.Get(); }))); } CookOutHBox->AddSlot() .Padding(5.0, 0.0, 0.0, 0.0) .VAlign(VAlign_Center) .AutoWidth() [ SNew(STextBlock) .Text(LOCTEXT("CookOut", "Cook Output")) ]; DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); } // Buttons: PAUSE COOK / CANCEL COOK { TSharedRef PauseHBox = SNew(SHorizontalBox); TSharedPtr CancelHBox = SNew(SHorizontalBox); FDetailWidgetRow& PDGDirtyCookRow = TOPNetWorkGrp.AddWidgetRow() .WholeRowContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ SNew(SBox) .WidthOverride(200.0f) [ SNew(SButton) //.Text(LOCTEXT("Pause", "Pause Cook")) .ToolTipText(LOCTEXT("PauseTooltip", "Pauses cooking for the selected TOP Network")) .ContentPadding(FMargin(5.0f, 2.0f)) .VAlign(VAlign_Center) .HAlign(HAlign_Center) .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) .OnClicked_Lambda([InPDGAssetLink]() { if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) { //InPDGAssetLink->WorkItemTally.ZeroAll(); FHoudiniPDGManager::PauseCook(InPDGAssetLink->GetSelectedTOPNetwork()); // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); } return FReply::Handled(); }) .Content() [ SAssignNew(PauseHBox, SHorizontalBox) ] ] ] + SHorizontalBox::Slot() .AutoWidth() [ SNew(SBox) .WidthOverride(200.0f) [ SNew(SButton) //.Text(LOCTEXT("Cancel", "Cancel Cook")) .ToolTipText(LOCTEXT("CancelTooltip", "Cancels cooking the selected TOP network")) .ContentPadding(FMargin(5.0f, 2.0f)) .VAlign(VAlign_Center) .HAlign(HAlign_Center) .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) .OnClicked_Lambda([InPDGAssetLink]() { if (IsValid(InPDGAssetLink->GetSelectedTOPNetwork())) { //InPDGAssetLink->WorkItemTally.ZeroAll(); FHoudiniPDGManager::CancelCook(InPDGAssetLink->GetSelectedTOPNetwork()); // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); } return FReply::Handled(); }) .Content() [ SAssignNew(CancelHBox, SHorizontalBox) ] ] ] ]; TSharedPtr PauseIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGPauseIconBrush(); if (PauseIconBrush.IsValid()) { TSharedPtr PauseImage; PauseHBox->AddSlot() .MaxWidth(16.0f) [ SNew(SBox) .WidthOverride(16.0f) .HeightOverride(16.0f) [ SAssignNew(PauseImage, SImage) ] ]; PauseImage->SetImage( TAttribute::Create( TAttribute::FGetter::CreateLambda([PauseIconBrush]() { return PauseIconBrush.Get(); }))); } PauseHBox->AddSlot() .Padding(5.0, 0.0, 0.0, 0.0) .VAlign(VAlign_Center) .AutoWidth() [ SNew(STextBlock) .Text(LOCTEXT("Pause", "Pause Cook")) ]; TSharedPtr CancelIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGCancelIconBrush(); if (CancelIconBrush.IsValid()) { TSharedPtr CancelImage; CancelHBox->AddSlot() .MaxWidth(16.0f) [ SNew(SBox) .WidthOverride(16.0f) .HeightOverride(16.0f) [ SAssignNew(CancelImage, SImage) ] ]; CancelImage->SetImage( TAttribute::Create( TAttribute::FGetter::CreateLambda([CancelIconBrush]() { return CancelIconBrush.Get(); }))); } CancelHBox->AddSlot() .Padding(5.0, 0.0, 0.0, 0.0) .VAlign(VAlign_Center) .AutoWidth() [ SNew(STextBlock) .Text(LOCTEXT("Cancel", "Cancel Cook")) ]; DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); } // Buttons: Unload Work Item Objects { FDetailWidgetRow& PDGUnloadLoadWorkItemsRow = TOPNetWorkGrp.AddWidgetRow() .WholeRowContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ SNew(SBox) .IsEnabled_Lambda([InPDGAssetLink]() { return IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNetwork(); }) .WidthOverride(200.0f) [ SNew(SButton) .Text(LOCTEXT("UnloadWorkItemsForNetwork", "Unload All Work Item Objects")) .ToolTipText(LOCTEXT("UnloadWorkItemsForNetworkTooltip", "Unloads / removes loaded work item results from level for all nodes in this network. Not undoable: use the \"Load Work Item Objects\" button on the individual TOP nodes to reload work item results.")) .ContentPadding(FMargin(5.0f, 2.0f)) .VAlign(VAlign_Center) .HAlign(HAlign_Center) .IsEnabled_Lambda([InPDGAssetLink]() { if (!IsValid(InPDGAssetLink)) return false; UTOPNetwork* const SelectedNet = InPDGAssetLink->GetSelectedTOPNetwork(); if (!IsValid(SelectedNet) || INDEX_NONE == SelectedNet->AllTOPNodes.IndexOfByPredicate([](const UTOPNode* InNode) { return IsValid(InNode) && InNode->bCachedHaveLoadedWorkResults; })) return false; return true; }) .OnClicked_Lambda([InPDGAssetLink]() { if (IsValid(InPDGAssetLink)) { UTOPNetwork* const TOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); if (IsValid(TOPNet)) { if (IsPDGLinked(InPDGAssetLink)) { // Set the state to ToDelete, PDGManager will delete it when processing work items TOPNet->SetLoadedWorkResultsToDelete(); } else { // Delete and unload the result objects and actors now TOPNet->DeleteWorkResultOutputObjects(); } } } return FReply::Handled(); }) ] ] ]; } // TOP NODE WIDGETS FHoudiniPDGDetails::AddTOPNodeWidget(TOPNetWorkGrp, InPDGAssetLink); } bool FHoudiniPDGDetails::GetSelectedTOPNodeStatusAndColor(UHoudiniPDGAssetLink* InPDGAssetLink, FString& OutTOPNodeStatus, FLinearColor &OutTOPNodeStatusColor) { OutTOPNodeStatus = FString(); OutTOPNodeStatusColor = FLinearColor::White; if (IsValid(InPDGAssetLink)) { UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); if (IsValid(TOPNode) && !TOPNode->bHidden) { OutTOPNodeStatus = UHoudiniPDGAssetLink::GetTOPNodeStatus(TOPNode); OutTOPNodeStatusColor = UHoudiniPDGAssetLink::GetTOPNodeStatusColor(TOPNode); return true; } } return false; } void FHoudiniPDGDetails::AddTOPNodeWidget( IDetailGroup& InGroup, UHoudiniPDGAssetLink* InPDGAssetLink ) { if (!InPDGAssetLink->GetSelectedTOPNetwork()) return; FString GroupLabel = TEXT("TOP Nodes"); IDetailGroup& TOPNodesGrp = InGroup.AddGroup(FName(*GroupLabel), FText::FromString(GroupLabel), true); // Combobox: TOP Node { FDetailWidgetRow& PDGTOPNodeRow = TOPNodesGrp.AddWidgetRow(); // DisableIfPDGNotLinked(PDGTOPNodeRow, InPDGAssetLink); PDGTOPNodeRow.NameWidget.Widget = SNew(SHorizontalBox) + SHorizontalBox::Slot() [ SNew(STextBlock) .Text(FText::FromString(TEXT("TOP Node"))) ]; // Update the TOP Node SharedString TOPNodesPtr.Reset(); TOPNodesPtr.Add(MakeShareable(new FTextAndTooltip(INDEX_NONE, LOCTEXT("ComboBoxEntryNoSelectedTOPNode", "- Select -").ToString()))); const UTOPNetwork* const SelectedTOPNet = InPDGAssetLink->GetSelectedTOPNetwork(); if (IsValid(SelectedTOPNet)) { const int32 NumTOPNodes = SelectedTOPNet->AllTOPNodes.Num(); for (int32 Idx = 0; Idx < NumTOPNodes; Idx++) { const UTOPNode* const Node = SelectedTOPNet->AllTOPNodes[Idx]; if (!IsValid(Node) || Node->bHidden) continue; TOPNodesPtr.Add(MakeShareable(new FTextAndTooltip( Idx, FHoudiniEngineEditorUtils::GetNodeNamePaddedByPathDepth(Node->NodeName, Node->NodePath), Node->NodePath ))); } } FString NodeErrorText = FString(); FString NodeErrorTooltip = FString(); FLinearColor NodeErrorColor = FLinearColor::White; if (!IsValid(SelectedTOPNet) || SelectedTOPNet->AllTOPNodes.Num() <= 0) { NodeErrorText = TEXT("No valid TOP Node found!"); NodeErrorTooltip = TEXT("There is no valid TOP Node found in the selected TOP Network!"); NodeErrorColor = FLinearColor::Red; } else if(TOPNodesPtr.Num() <= 0) { NodeErrorText = TEXT("No visible TOP Node found!"); NodeErrorTooltip = TEXT("No visible TOP Node found, all nodes in this network are hidden. Please update your TOP Node Filter."); NodeErrorColor = FLinearColor::Yellow; } // Lambda for selecting a TOPNode auto OnTOPNodeChanged = [InPDGAssetLink](TSharedPtr InNewChoice) { UTOPNetwork* const TOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); if (!InNewChoice.IsValid() || !IsValid(TOPNetwork)) return; const int32 NewChoice = InNewChoice->Value; int32 NewSelectedIndex = INDEX_NONE; if (TOPNetwork->AllTOPNodes.IsValidIndex(NewChoice)) NewSelectedIndex = NewChoice; if (TOPNetwork->SelectedTOPIndex != NewSelectedIndex) { // Record a transaction for undo/redo FScopedTransaction Transaction( TEXT(HOUDINI_MODULE_RUNTIME), LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), TOPNetwork); TOPNetwork->Modify(); TOPNetwork->SelectedTOPIndex = NewSelectedIndex; FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( GET_MEMBER_NAME_STRING_CHECKED(UTOPNetwork, SelectedTOPIndex), TOPNetwork); } }; TSharedPtr HorizontalBoxTOPNode; TSharedPtr>> ComboBoxTOPNode; int32 SelectedIndex = 0; UTOPNetwork* const SelectedTOPNetwork = InPDGAssetLink->GetSelectedTOPNetwork(); if (IsValid(SelectedTOPNetwork) && SelectedTOPNetwork->SelectedTOPIndex >= 0) { //SelectedIndex = InPDGAssetLink->GetSelectedTOPNetwork()->SelectedTOPIndex; // We need to match the selection by the index in the AllTopNodes array // Because of the nodefilter, it is possible that the selected index does not match the index in TOPNodesPtr const int32 SelectedTOPNodeIndex = SelectedTOPNetwork->SelectedTOPIndex; // Find the matching UI index for (int32 UIIndex = 0; UIIndex < TOPNodesPtr.Num(); UIIndex++) { if (TOPNodesPtr[UIIndex] && TOPNodesPtr[UIIndex]->Value == SelectedTOPNodeIndex) { // We found the UI Index that matches the current TOP Node! SelectedIndex = UIIndex; break; } } } TSharedPtr ErrorText; PDGTOPNodeRow.ValueWidget.Widget = SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(2, 2, 5, 2) .FillWidth(300.f) .MaxWidth(300.f) [ SAssignNew(ComboBoxTOPNode, SComboBox>) .OptionsSource(&TOPNodesPtr) .InitiallySelectedItem(TOPNodesPtr[SelectedIndex]) .OnGenerateWidget_Lambda([](TSharedPtr ChoiceEntry) { const FText ChoiceEntryText = FText::FromString(ChoiceEntry->Text); const FText ChoiceEntryToolTip = FText::FromString(ChoiceEntry->ToolTip); return SNew(STextBlock) .Text(ChoiceEntryText) .ToolTipText(ChoiceEntryToolTip) .Margin(2.0f) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); }) .OnSelectionChanged_Lambda([OnTOPNodeChanged](TSharedPtr NewChoice, ESelectInfo::Type SelectType) { return OnTOPNodeChanged(NewChoice); }) [ SNew(STextBlock) .Text_Lambda([InPDGAssetLink, ComboBoxTOPNode, Options = TOPNodesPtr]() { if (IsValid(InPDGAssetLink)) return FText::FromString(InPDGAssetLink->GetSelectedTOPNodeName()); else return FText(); }) .ToolTipText_Lambda([InPDGAssetLink]() { UTOPNode const * const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); if (IsValid(TOPNode)) { if (!TOPNode->NodePath.IsEmpty()) return FText::FromString(TOPNode->NodePath); else return FText::FromString(TOPNode->NodeName); } else { return FText(); } }) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) ] ] + SHorizontalBox::Slot() .Padding(2, 2, 5, 2) .AutoWidth() [ SAssignNew(ErrorText, STextBlock) .Text(FText::FromString(NodeErrorText)) .ToolTipText(FText::FromString(NodeErrorText)) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) .ColorAndOpacity(FLinearColor::Red) //.ShadowColorAndOpacity(FLinearColor::Black) ]; // Update the error text if needed ErrorText->SetText(FText::FromString(NodeErrorText)); ErrorText->SetToolTipText(FText::FromString(NodeErrorTooltip)); ErrorText->SetColorAndOpacity(NodeErrorColor); // Hide the combobox if we have an error ComboBoxTOPNode->SetVisibility(NodeErrorText.IsEmpty() ? EVisibility::Visible : EVisibility::Hidden); } // TOP Node State { FDetailWidgetRow& PDGNodeStateResultRow = TOPNodesGrp.AddWidgetRow(); DisableIfPDGNotLinked(PDGNodeStateResultRow, InPDGAssetLink); PDGNodeStateResultRow.NameWidget.Widget = SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f, 0.0f) [ SNew(STextBlock) .Text(FText::FromString(TEXT("TOP Node State"))) ]; PDGNodeStateResultRow.ValueWidget.Widget = SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f, 0.0f) [ SNew(STextBlock) .Text_Lambda([InPDGAssetLink]() { FString TOPNodeStatus = FString(); FLinearColor TOPNodeStatusColor = FLinearColor::White; GetSelectedTOPNodeStatusAndColor(InPDGAssetLink, TOPNodeStatus, TOPNodeStatusColor); return FText::FromString(TOPNodeStatus); }) .ColorAndOpacity_Lambda([InPDGAssetLink]() { FString TOPNodeStatus = FString(); FLinearColor TOPNodeStatusColor = FLinearColor::White; GetSelectedTOPNodeStatusAndColor(InPDGAssetLink, TOPNodeStatus, TOPNodeStatusColor); return FSlateColor(TOPNodeStatusColor); }) ]; } // Checkbox: Load Work Item Output Files { auto ToolTipLambda = [InPDGAssetLink]() { bool bDisabled = false; if (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNode()) { bDisabled = InPDGAssetLink->GetSelectedTOPNode()->bHasChildNodes; } return bDisabled ? FText::FromString(TEXT("This node has child nodes, the auto-load setting must be set on the child nodes individually.")) : FText::FromString(TEXT("When enabled, Output files produced by this TOP Node's Work Items will automatically be loaded when cooked.")); }; FDetailWidgetRow& PDGNodeAutoLoadRow = TOPNodesGrp.AddWidgetRow(); DisableIfPDGNotLinked(PDGNodeAutoLoadRow, InPDGAssetLink); PDGNodeAutoLoadRow.IsEnabledAttr.Bind(TAttribute::FGetter::CreateLambda([InPDGAssetLink]() { if (!IsPDGLinked(InPDGAssetLink)) return false; UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); if (IsValid(Node) && !Node->bHidden && !Node->bHasChildNodes) return true; return false; })); PDGNodeAutoLoadRow.NameWidget.Widget = SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f, 0.0f) [ SNew(STextBlock) .Text(FText::FromString(TEXT("Auto-Load Work Item Output Files"))) .ToolTipText_Lambda(ToolTipLambda) ]; TSharedPtr AutoLoadCheckBox; PDGNodeAutoLoadRow.ValueWidget.Widget = SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f, 0.0f) [ // Checkbox SAssignNew(AutoLoadCheckBox, SCheckBox) .IsChecked_Lambda([InPDGAssetLink]() { return InPDGAssetLink->GetSelectedTOPNode() ? (InPDGAssetLink->GetSelectedTOPNode()->bAutoLoad ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) : ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) { const bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; UTOPNode* TOPNode = InPDGAssetLink->GetSelectedTOPNode(); if (!IsValid(TOPNode) || TOPNode->bAutoLoad == bNewState) return; // Record a transaction for undo/redo FScopedTransaction Transaction( TEXT(HOUDINI_MODULE_RUNTIME), LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), TOPNode); TOPNode->Modify(); TOPNode->bAutoLoad = bNewState; FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( GET_MEMBER_NAME_STRING_CHECKED(UTOPNode, bAutoLoad), TOPNode); // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); }) .ToolTipText_Lambda(ToolTipLambda) ]; } // Checkbox: Work Item Output Files Visible { auto ToolTipLambda = [InPDGAssetLink]() { bool bDisabled = false; if (IsValid(InPDGAssetLink) && InPDGAssetLink->GetSelectedTOPNode()) { bDisabled = InPDGAssetLink->GetSelectedTOPNode()->bHasChildNodes; } return bDisabled ? FText::FromString(TEXT("This node has child nodes, visibility of work item outputs must be set on the child nodes individually.")) : FText::FromString(TEXT("Toggles the visibility of the actors created from this TOP Node's Work Item File Outputs.")); }; FDetailWidgetRow& PDGNodeShowResultRow = TOPNodesGrp.AddWidgetRow(); // DisableIfPDGNotLinked(PDGNodeShowResultRow, InPDGAssetLink); PDGNodeShowResultRow.IsEnabledAttr.Bind(TAttribute::FGetter::CreateLambda([InPDGAssetLink]() { if (!IsValid(InPDGAssetLink)) return false; UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); if (IsValid(Node) && !Node->bHidden && !Node->bHasChildNodes) return true; return false; })); PDGNodeShowResultRow.NameWidget.Widget = SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f, 0.0f) [ SNew(STextBlock) .Text(FText::FromString(TEXT("Work Item Output Files Visible"))) .ToolTipText_Lambda(ToolTipLambda) ]; TSharedPtr ShowResCheckBox; PDGNodeShowResultRow.ValueWidget.Widget = SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() .Padding(2.0f, 0.0f) [ // Checkbox SAssignNew(ShowResCheckBox, SCheckBox) .IsChecked_Lambda([InPDGAssetLink]() { return InPDGAssetLink->GetSelectedTOPNode() ? (InPDGAssetLink->GetSelectedTOPNode()->IsVisibleInLevel() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) : ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) { const bool bNewState = (NewState == ECheckBoxState::Checked) ? true : false; UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); if (!IsValid(TOPNode) || TOPNode->IsVisibleInLevel() == bNewState) return; // Record a transaction for undo/redo FScopedTransaction Transaction( TEXT(HOUDINI_MODULE_RUNTIME), LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), TOPNode); TOPNode->Modify(); TOPNode->SetVisibleInLevel(bNewState); FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty(TEXT("bShow"), TOPNode); // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); }) .ToolTipText_Lambda(ToolTipLambda) ]; } // Buttons: DIRTY NODE / COOK NODE { TSharedRef DirtyHBox = SNew(SHorizontalBox); TSharedPtr CookHBox = SNew(SHorizontalBox); TSharedPtr DirtyButton; TSharedPtr CookButton; FDetailWidgetRow& PDGDirtyCookRow = TOPNodesGrp.AddWidgetRow() .WholeRowContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ SNew(SBox) .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode())); }) .WidthOverride(200.0f) [ SAssignNew(DirtyButton, SButton) //.Text(LOCTEXT("DirtyNode", "Dirty Node")) .ToolTipText(LOCTEXT("DirtyNodeTooltip", "Dirties the selected TOP node and clears its work item results.")) .ContentPadding(FMargin(5.0f, 2.0f)) .VAlign(VAlign_Center) .HAlign(HAlign_Center) .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink) || (IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode())); }) .OnClicked_Lambda([InPDGAssetLink]() { if (IsValid(InPDGAssetLink)) { UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); if (IsValid(TOPNode)) { if (IsPDGLinked(InPDGAssetLink)) { FHoudiniPDGManager::DirtyTOPNode(TOPNode); // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); } else { UHoudiniPDGAssetLink::ClearTOPNodeWorkItemResults(TOPNode); } } } return FReply::Handled(); }) .IsEnabled_Lambda([InPDGAssetLink]() { if (IsValid(InPDGAssetLink->GetSelectedTOPNode()) && !InPDGAssetLink->GetSelectedTOPNode()->bHidden) return true; return false; }) .Content() [ SAssignNew(DirtyHBox, SHorizontalBox) ] ] ] // + SHorizontalBox::Slot() // .AutoWidth() // [ // SNew(SBox) // .WidthOverride(200.0f) // [ // SAssignNew(DirtyButton, SButton) // .Text(LOCTEXT("DirtyAllTasks", "Dirty All Tasks")) // .ToolTipText(LOCTEXT("DirtyAllTasksTooltip", "Dirties all tasks/work items on the selected TOP node.")) // .ContentPadding(FMargin(5.0f, 2.0f)) // .VAlign(VAlign_Center) // .HAlign(HAlign_Center) // .OnClicked_Lambda([InPDGAssetLink]() // { // if(InPDGAssetLink->GetSelectedTOPNode()) // { // FHoudiniPDGManager::DirtyAllTasksOfTOPNode(*(InPDGAssetLink->GetSelectedTOPNode())); // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); // } // // return FReply::Handled(); // }) // ] // ] + SHorizontalBox::Slot() .AutoWidth() [ SNew(SBox) .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) .WidthOverride(200.0f) [ SAssignNew(CookButton, SButton) //.Text(LOCTEXT("CookNode", "Cook Node")) .ToolTipText(LOCTEXT("CookNodeTooltip", "Cooks the selected TOP Node.")) .ContentPadding(FMargin(5.0f, 2.0f)) .VAlign(VAlign_Center) .HAlign(HAlign_Center) .IsEnabled_Lambda([InPDGAssetLink]() { if (!IsPDGLinked(InPDGAssetLink)) return false; UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); if (!IsValid(SelectedNode)) return false; // Disable Cook Node button if the node is already cooking return !SelectedNode->bHidden && SelectedNode->NodeState != EPDGNodeState::Cooking && !SelectedNode->AnyWorkItemsPending(); }) .OnClicked_Lambda([InPDGAssetLink]() { UTOPNode* const Node = InPDGAssetLink->GetSelectedTOPNode(); if (IsValid(Node)) { FHoudiniPDGManager::CookTOPNode(Node); // FHoudiniPDGDetails::RefreshUI(InPDGAssetLink); } return FReply::Handled(); }) .Content() [ SAssignNew(CookHBox, SHorizontalBox) ] ] ] ]; TSharedPtr DirtyIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIPDGDirtyNodeIconBrush(); if (DirtyIconBrush.IsValid()) { TSharedPtr DirtyImage; DirtyHBox->AddSlot() .MaxWidth(16.0f) [ SNew(SBox) .WidthOverride(16.0f) .HeightOverride(16.0f) [ SAssignNew(DirtyImage, SImage) ] ]; DirtyImage->SetImage( TAttribute::Create( TAttribute::FGetter::CreateLambda([DirtyIconBrush]() { return DirtyIconBrush.Get(); }))); } DirtyHBox->AddSlot() .Padding(5.0, 0.0, 0.0, 0.0) .VAlign(VAlign_Center) .AutoWidth() [ SNew(STextBlock) .Text(LOCTEXT("DirtyNode", "Dirty Node")) ]; TSharedPtr CookIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIRecookIconBrush(); if (CookIconBrush.IsValid()) { TSharedPtr CookImage; CookHBox->AddSlot() .MaxWidth(16.0f) [ SNew(SBox) .WidthOverride(16.0f) .HeightOverride(16.0f) [ SAssignNew(CookImage, SImage) ] ]; CookImage->SetImage( TAttribute::Create( TAttribute::FGetter::CreateLambda([CookIconBrush]() { return CookIconBrush.Get(); }))); } CookHBox->AddSlot() .Padding(5.0, 0.0, 0.0, 0.0) .VAlign(VAlign_Center) .AutoWidth() [ SNew(STextBlock) .Text(LOCTEXT("CookNode", "Cook Node")) ]; DisableIfPDGNotLinked(PDGDirtyCookRow, InPDGAssetLink); } // Buttons: Load Work Item Objects / Unload Work Item Objects { TSharedPtr UnloadWorkItemsButton; TSharedPtr LoadWorkItemsButton; FDetailWidgetRow& PDGUnloadLoadWorkItemsRow = TOPNodesGrp.AddWidgetRow() .WholeRowContent() [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .AutoWidth() [ SNew(SBox) .IsEnabled_Lambda([InPDGAssetLink]() { return IsValid(InPDGAssetLink) && IsValid(InPDGAssetLink->GetSelectedTOPNode()); }) .WidthOverride(200.0f) [ SAssignNew(UnloadWorkItemsButton, SButton) .Text(LOCTEXT("UnloadWorkItemsForNode", "Unload Work Item Objects")) .ToolTipText(LOCTEXT("UnloadWorkItemsForNodeTooltip", "Unloads / removes loaded work item results from level. Not undoable: use the \"Load Work Item Objects\" button to reload the results.")) .ContentPadding(FMargin(5.0f, 2.0f)) .VAlign(VAlign_Center) .HAlign(HAlign_Center) .IsEnabled_Lambda([InPDGAssetLink]() { if (!IsValid(InPDGAssetLink)) return false; UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); if (!IsValid(SelectedNode) || SelectedNode->bHidden || !SelectedNode->bCachedHaveLoadedWorkResults) return false; return true; }) .OnClicked_Lambda([InPDGAssetLink]() { if (IsValid(InPDGAssetLink)) { UTOPNode* const TOPNode = InPDGAssetLink->GetSelectedTOPNode(); if (IsValid(TOPNode)) { if (IsPDGLinked(InPDGAssetLink)) { // Set the state to ToDelete, PDGManager will delete it when processing work items TOPNode->SetLoadedWorkResultsToDelete(); } else { // Delete and unload the result objects and actors now TOPNode->DeleteWorkResultOutputObjects(); } } } return FReply::Handled(); }) ] ] + SHorizontalBox::Slot() .AutoWidth() [ SNew(SBox) .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) .WidthOverride(200.0f) [ SAssignNew(LoadWorkItemsButton, SButton) .Text(LOCTEXT("LoadWorkItems", "Load Work Item Objects")) .ToolTipText(LOCTEXT("LoadWorkItemsForNodeTooltip", "Loads any available but not loaded work items objects (this could include items from a previous cook). Creates output actors. Not undoable: use the \"Unload Work Item Objects\" button to unload/remove loaded work item results.")) .ContentPadding(FMargin(5.0f, 2.0f)) .VAlign(VAlign_Center) .HAlign(HAlign_Center) .IsEnabled_Lambda([InPDGAssetLink]() { if (!IsValid(InPDGAssetLink)) return false; UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); if (!IsValid(SelectedNode) || SelectedNode->bHidden || !SelectedNode->bCachedHaveNotLoadedWorkResults) return false; return true; }) .OnClicked_Lambda([InPDGAssetLink]() { if (IsValid(InPDGAssetLink)) { UTOPNode* const SelectedNode = InPDGAssetLink->GetSelectedTOPNode(); if (IsValid(SelectedNode)) { SelectedNode->SetNotLoadedWorkResultsToLoad(true); } } return FReply::Handled(); }) ] ] ]; } // TOP Node WorkItem Status { if (InPDGAssetLink->GetSelectedTOPNode()) { FDetailWidgetRow& PDGNodeWorkItemStatsRow = TOPNodesGrp.AddWidgetRow(); DisableIfPDGNotLinked(PDGNodeWorkItemStatsRow, InPDGAssetLink); FHoudiniPDGDetails::AddWorkItemStatusWidget( PDGNodeWorkItemStatsRow, TEXT("TOP Node Work Item Status"), InPDGAssetLink, true); } } } void FHoudiniPDGDetails::RefreshPDGAssetLink(UHoudiniPDGAssetLink* InPDGAssetLink) { // Repopulate the network and nodes for the assetlink if (!FHoudiniPDGManager::UpdatePDGAssetLink(InPDGAssetLink)) return; FHoudiniPDGDetails::RefreshUI(InPDGAssetLink, true); } void FHoudiniPDGDetails::RefreshUI(UHoudiniPDGAssetLink* InPDGAssetLink, const bool& InFullUpdate) { if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) return; // Update the workitem stats InPDGAssetLink->UpdateWorkItemTally(); // Update the editor properties FHoudiniEngineUtils::UpdateEditorProperties(InPDGAssetLink, InFullUpdate); } void FHoudiniPDGDetails::CreatePDGBakeWidgets(IDetailCategoryBuilder& InPDGCategory, UHoudiniPDGAssetLink* InPDGAssetLink) { if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) return; FHoudiniEngineDetails::AddHeaderRowForHoudiniPDGAssetLink(InPDGCategory, InPDGAssetLink, HOUDINI_ENGINE_UI_SECTION_PDG_BAKE); if (!InPDGAssetLink->bBakeMenuExpanded) return; auto OnBakeButtonClickedLambda = [InPDGAssetLink]() { switch (InPDGAssetLink->HoudiniEngineBakeOption) { case EHoudiniEngineBakeOption::ToActor: { // if (InPDGAssetLink->bIsReplace) // FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithActors(InPDGAssetLink); // else FHoudiniEngineBakeUtils::BakePDGAssetLinkOutputsKeepActors(InPDGAssetLink, InPDGAssetLink->PDGBakeSelectionOption, InPDGAssetLink->PDGBakePackageReplaceMode, InPDGAssetLink->bRecenterBakedActors); } break; case EHoudiniEngineBakeOption::ToBlueprint: { // if (InPDGAssetLink->bIsReplace) // FHoudiniEngineBakeUtils::ReplaceWithBlueprint(InPDGAssetLink); // else FHoudiniEngineBakeUtils::BakePDGAssetLinkBlueprints(InPDGAssetLink, InPDGAssetLink->PDGBakeSelectionOption, InPDGAssetLink->PDGBakePackageReplaceMode, InPDGAssetLink->bRecenterBakedActors); } break; // // case EHoudiniEngineBakeOption::ToFoliage: // { // if (InPDGAssetLink->bIsReplace) // FHoudiniEngineBakeUtils::ReplaceHoudiniActorWithFoliage(InPDGAssetLink); // else // FHoudiniEngineBakeUtils::BakeHoudiniActorToFoliage(InPDGAssetLink); // } // break; // // case EHoudiniEngineBakeOption::ToWorldOutliner: // { // if (InPDGAssetLink->bIsReplace) // { // // Todo // } // else // { // //Todo // } // } // break; } return FReply::Handled(); }; auto OnBakeFolderTextCommittedLambda = [InPDGAssetLink](const FText& Val, ETextCommit::Type TextCommitType) { FString NewPathStr = Val.ToString(); if (NewPathStr.IsEmpty()) return; // Record a transaction for undo/redo FScopedTransaction Transaction( TEXT(HOUDINI_MODULE_RUNTIME), LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), InPDGAssetLink); //Todo? Check if the new Bake folder path is valid InPDGAssetLink->Modify(); InPDGAssetLink->BakeFolder.Path = NewPathStr; FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, BakeFolder), InPDGAssetLink); }; // Button Row FDetailWidgetRow & ButtonRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); DisableIfPDGNotLinked(ButtonRow, InPDGAssetLink); TSharedRef ButtonRowHorizontalBox = SNew(SHorizontalBox); // Bake Button TSharedRef BakeHBox = SNew(SHorizontalBox); TSharedPtr BakeButton; ButtonRowHorizontalBox->AddSlot() /*.AutoWidth()*/ .Padding(15.f, 0.0f, 0.0f, 0.0f) .MaxWidth(75.0f) [ SNew(SBox) .WidthOverride(75.0f) [ SAssignNew(BakeButton, SButton) //.Text(FText::FromString("Bake")) .VAlign(VAlign_Center) .HAlign(HAlign_Center) //.ToolTipText(LOCTEXT("HoudiniPDGDetailsBakeButton", "Bake the Houdini PDG TOP Node(s)")) .ToolTipText_Lambda([InPDGAssetLink]() { switch (InPDGAssetLink->HoudiniEngineBakeOption) { case EHoudiniEngineBakeOption::ToActor: { return LOCTEXT( "HoudiniEnginePDGBakeButtonBakeToActorToolTip", "Bake this Houdini PDG Asset's output assets and seperate the output actors from the PDG asset link."); } break; case EHoudiniEngineBakeOption::ToBlueprint: { return LOCTEXT( "HoudiniEnginePDGBakeButtonBakeToBlueprintToolTip", "Bake this Houdini PDG Asset's output assets to blueprints and remove temporary output actors that no " "longer has output components from the PDG asset link."); } break; default: { return FText(); } } }) .Visibility(EVisibility::Visible) .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) .OnClicked_Lambda(OnBakeButtonClickedLambda) .Content() [ SAssignNew(BakeHBox, SHorizontalBox) ] ] ]; TSharedPtr BakeIconBrush = FHoudiniEngineEditor::Get().GetHoudiniEngineUIBakeIconBrush(); if (BakeIconBrush.IsValid()) { TSharedPtr BakeImage; BakeHBox->AddSlot() .MaxWidth(16.0f) [ SNew(SBox) .WidthOverride(16.0f) .HeightOverride(16.0f) [ SAssignNew(BakeImage, SImage) ] ]; BakeImage->SetImage( TAttribute::Create( TAttribute::FGetter::CreateLambda([BakeIconBrush]() { return BakeIconBrush.Get(); }))); } BakeHBox->AddSlot() .Padding(5.0, 0.0, 0.0, 0.0) .VAlign(VAlign_Center) .AutoWidth() [ SNew(STextBlock) .Text(FText::FromString("Bake")) ]; // bake Type ComboBox TSharedPtr>> TypeComboBox; TArray>* OptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakeTypeOptionsLabels(); TSharedPtr IntialSelec; if (OptionSource) { // IntialSelec = (*OptionSource)[(int)InPDGAssetLink->HoudiniEngineBakeOption]; const FString DefaultStr = FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(InPDGAssetLink->HoudiniEngineBakeOption); const TSharedPtr* DefaultOption = OptionSource->FindByPredicate( [DefaultStr](TSharedPtr InStringPtr) { return InStringPtr.IsValid() && *InStringPtr == DefaultStr; } ); if (DefaultOption) IntialSelec = *DefaultOption; } ButtonRowHorizontalBox->AddSlot() /*.AutoWidth()*/ .Padding(3.0, 0.0, 4.0f, 0.0f) .MaxWidth(93.f) [ SNew(SBox) .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) .WidthOverride(93.f) [ SAssignNew(TypeComboBox, SComboBox>) .OptionsSource(OptionSource) .InitiallySelectedItem(IntialSelec) .OnGenerateWidget_Lambda( [](TSharedPtr< FString > InItem) { FText ChoiceEntryText = FText::FromString(*InItem); return SNew(STextBlock) .Text(ChoiceEntryText) .ToolTipText(ChoiceEntryText) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); }) .OnSelectionChanged_Lambda( [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) { if (!NewChoice.IsValid() || SelectType == ESelectInfo::Type::Direct) return; const EHoudiniEngineBakeOption NewOption = FHoudiniEngineEditor::Get().StringToHoudiniEngineBakeOption(*NewChoice.Get()); if (NewOption != InPDGAssetLink->HoudiniEngineBakeOption) { // Record a transaction for undo/redo FScopedTransaction Transaction( TEXT(HOUDINI_MODULE_RUNTIME), LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), InPDGAssetLink); InPDGAssetLink->Modify(); InPDGAssetLink->HoudiniEngineBakeOption = NewOption; FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, HoudiniEngineBakeOption), InPDGAssetLink); } }) [ SNew(STextBlock) .Text_Lambda([InPDGAssetLink, TypeComboBox, OptionSource]() { return FText::FromString(FHoudiniEngineEditor::Get().GetStringFromHoudiniEngineBakeOption(InPDGAssetLink->HoudiniEngineBakeOption)); }) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) ] ] ]; // bake selection ComboBox TSharedPtr>> BakeSelectionComboBox; TArray>* PDGBakeSelectionOptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakeSelectionOptionsLabels(); TSharedPtr PDGBakeSelectionIntialSelec; if (PDGBakeSelectionOptionSource) { PDGBakeSelectionIntialSelec = (*PDGBakeSelectionOptionSource)[(int)InPDGAssetLink->PDGBakeSelectionOption]; } ButtonRowHorizontalBox->AddSlot() /*.AutoWidth()*/ .Padding(3.0, 0.0, 4.0f, 0.0f) .MaxWidth(163.f) [ SNew(SBox) .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) .WidthOverride(163.f) [ SAssignNew(TypeComboBox, SComboBox>) .OptionsSource(PDGBakeSelectionOptionSource) .InitiallySelectedItem(PDGBakeSelectionIntialSelec) .OnGenerateWidget_Lambda( [](TSharedPtr< FString > InItem) { FText ChoiceEntryText = FText::FromString(*InItem); return SNew(STextBlock) .Text(ChoiceEntryText) .ToolTipText(ChoiceEntryText) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); }) .OnSelectionChanged_Lambda( [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) { if (!NewChoice.IsValid()) return; const EPDGBakeSelectionOption NewOption = FHoudiniEngineEditor::Get().StringToPDGBakeSelectionOption(*NewChoice.Get()); if (NewOption != InPDGAssetLink->PDGBakeSelectionOption) { // Record a transaction for undo/redo FScopedTransaction Transaction( TEXT(HOUDINI_MODULE_RUNTIME), LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), InPDGAssetLink); InPDGAssetLink->Modify(); InPDGAssetLink->PDGBakeSelectionOption = NewOption; FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakeSelectionOption), InPDGAssetLink); } }) [ SNew(STextBlock) .Text_Lambda([InPDGAssetLink]() { return FText::FromString( FHoudiniEngineEditor::Get().GetStringFromPDGBakeTargetOption(InPDGAssetLink->PDGBakeSelectionOption)); }) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) ] ] ]; ButtonRow.WholeRowWidget.Widget = ButtonRowHorizontalBox; // Bake package replacement mode row FDetailWidgetRow & BakePackageReplaceRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); DisableIfPDGNotLinked(BakePackageReplaceRow, InPDGAssetLink); TSharedRef BakePackageReplaceRowHorizontalBox = SNew(SHorizontalBox); BakePackageReplaceRowHorizontalBox->AddSlot() /*.AutoWidth()*/ .Padding(30.0f, 0.0f, 6.0f, 0.0f) .MaxWidth(155.0f) [ SNew(SBox) .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) .WidthOverride(155.0f) [ SNew(STextBlock) .Text(LOCTEXT("HoudiniEnginePDGBakePackageReplacementModeLabel", "Replace Mode")) .ToolTipText( LOCTEXT("HoudiniEnginePDGBakePackageReplacementModeTooltip", "Replacement mode " "during baking. Create new assets, using numerical suffixes in package names when necessary, or " "replace existing assets with matching names. Also replaces the previous bake's output actors in " "replacement mode vs creating incremental ones.")) ] ]; // bake package replace mode ComboBox TSharedPtr>> BakePackageReplaceModeComboBox; TArray>* PDGBakePackageReplaceModeOptionSource = FHoudiniEngineEditor::Get().GetHoudiniEnginePDGBakePackageReplaceModeOptionsLabels(); TSharedPtr PDGBakePackageReplaceModeInitialSelec; if (PDGBakePackageReplaceModeOptionSource) { const FString DefaultStr = FHoudiniEngineEditor::Get().GetStringFromPDGBakePackageReplaceModeOption(InPDGAssetLink->PDGBakePackageReplaceMode); const TSharedPtr* DefaultOption = PDGBakePackageReplaceModeOptionSource->FindByPredicate( [DefaultStr](TSharedPtr InStringPtr) { return InStringPtr.IsValid() && *InStringPtr == DefaultStr; } ); if (DefaultOption) PDGBakePackageReplaceModeInitialSelec = *DefaultOption; } BakePackageReplaceRowHorizontalBox->AddSlot() /*.AutoWidth()*/ .Padding(3.0, 0.0, 4.0f, 0.0f) .MaxWidth(163.f) [ SNew(SBox) .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) .WidthOverride(163.f) [ SAssignNew(TypeComboBox, SComboBox>) .OptionsSource(PDGBakePackageReplaceModeOptionSource) .InitiallySelectedItem(PDGBakePackageReplaceModeInitialSelec) .OnGenerateWidget_Lambda( [](TSharedPtr< FString > InItem) { const FText ChoiceEntryText = FText::FromString(*InItem); return SNew(STextBlock) .Text(ChoiceEntryText) .ToolTipText(ChoiceEntryText) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))); }) .OnSelectionChanged_Lambda( [InPDGAssetLink](TSharedPtr< FString > NewChoice, ESelectInfo::Type SelectType) { if (!NewChoice.IsValid()) return; const EPDGBakePackageReplaceModeOption NewOption = FHoudiniEngineEditor::Get().StringToPDGBakePackageReplaceModeOption(*NewChoice.Get()); if (NewOption != InPDGAssetLink->PDGBakePackageReplaceMode) { // Record a transaction for undo/redo FScopedTransaction Transaction( TEXT(HOUDINI_MODULE_RUNTIME), LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), InPDGAssetLink); InPDGAssetLink->Modify(); InPDGAssetLink->PDGBakePackageReplaceMode = NewOption; FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, PDGBakePackageReplaceMode), InPDGAssetLink); } }) [ SNew(STextBlock) .Text_Lambda([InPDGAssetLink]() { return FText::FromString( FHoudiniEngineEditor::Get().GetStringFromPDGBakePackageReplaceModeOption(InPDGAssetLink->PDGBakePackageReplaceMode)); }) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) ] ] ]; BakePackageReplaceRow.WholeRowWidget.Widget = BakePackageReplaceRowHorizontalBox; // Bake Folder Row FDetailWidgetRow & BakeFolderRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); DisableIfPDGNotLinked(BakeFolderRow, InPDGAssetLink); TSharedRef BakeFolderRowHorizontalBox = SNew(SHorizontalBox); BakeFolderRowHorizontalBox->AddSlot() /*.AutoWidth()*/ .Padding(30.0f, 0.0f, 6.0f, 0.0f) .MaxWidth(155.0f) [ SNew(SBox) .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) .WidthOverride(155.0f) [ SNew(STextBlock) .Text(LOCTEXT("HoudiniEngineBakeFolderLabel", "Bake Folder")) .ToolTipText(LOCTEXT( "HoudiniEnginePDGBakeFolderTooltip", "The folder used to store the objects that are generated by this Houdini PDG Asset when baking, if the " "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " "plugin settings is used.")) ] ]; BakeFolderRowHorizontalBox->AddSlot() /*.AutoWidth()*/ .MaxWidth(235.0) [ SNew(SBox) .IsEnabled_Lambda([InPDGAssetLink]() { return IsPDGLinked(InPDGAssetLink); }) .WidthOverride(235.0f) [ SNew(SEditableTextBox) .MinDesiredWidth(HAPI_UNREAL_DESIRED_ROW_VALUE_WIDGET_WIDTH) .ToolTipText(LOCTEXT( "HoudiniEnginePDGBakeFolderTooltip", "The folder used to store the objects that are generated by this Houdini PDG Asset when baking, if the " "unreal_bake_folder attribute is not set on the geometry. If this value is blank, the default from the " "plugin settings is used.")) .HintText(LOCTEXT("HoudiniEngineBakeFolderHintText", "Input to set bake folder")) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) .Text_Lambda([InPDGAssetLink](){ return FText::FromString(InPDGAssetLink->BakeFolder.Path); }) .OnTextCommitted_Lambda(OnBakeFolderTextCommittedLambda) ] ]; BakeFolderRow.WholeRowWidget.Widget = BakeFolderRowHorizontalBox; // Add additional bake options FDetailWidgetRow & AdditionalBakeSettingsRow = InPDGCategory.AddCustomRow(FText::GetEmpty()); TSharedRef AdditionalBakeSettingsRowHorizontalBox = SNew(SHorizontalBox); TSharedPtr CheckBoxAutoBake; TSharedPtr CheckBoxRecenterBakedActors; TSharedPtr LeftColumnVerticalBox; TSharedPtr RightColumnVerticalBox; AdditionalBakeSettingsRowHorizontalBox->AddSlot() .Padding(30.0f, 5.0f, 0.0f, 0.0f) .MaxWidth(200.f) [ SNew(SBox) .WidthOverride(200.f) [ SAssignNew(LeftColumnVerticalBox, SVerticalBox) ] ]; AdditionalBakeSettingsRowHorizontalBox->AddSlot() .Padding(20.0f, 5.0f, 0.0f, 0.0f) .MaxWidth(200.f) [ SNew(SBox) [ SAssignNew(RightColumnVerticalBox, SVerticalBox) ] ]; LeftColumnVerticalBox->AddSlot() .AutoHeight() .Padding(0.0f, 0.0f, 0.0f, 3.5f) [ SNew(SBox) .WidthOverride(160.f) [ SAssignNew(CheckBoxRecenterBakedActors, SCheckBox) .Content() [ SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBox", "Recenter Baked Actors")) .ToolTipText(LOCTEXT("HoudiniEngineUIRecenterBakedActorsCheckBoxToolTip", "After baking recenter the baked actors to their bounding box center.")) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) ] .IsChecked_Lambda([InPDGAssetLink]() { return InPDGAssetLink->bRecenterBakedActors ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) { const bool bNewState = (NewState == ECheckBoxState::Checked); // Record a transaction for undo/redo FScopedTransaction Transaction( TEXT(HOUDINI_MODULE_RUNTIME), LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), InPDGAssetLink); InPDGAssetLink->Modify(); InPDGAssetLink->bRecenterBakedActors = bNewState; // Notify that we have changed the property FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bRecenterBakedActors), InPDGAssetLink); }) ] ]; RightColumnVerticalBox->AddSlot() .AutoHeight() .Padding(0.0f, 0.0f, 0.0f, 3.5f) [ SNew(SBox) .WidthOverride(160.f) [ SAssignNew(CheckBoxAutoBake, SCheckBox) .Content() [ SNew(STextBlock).Text(LOCTEXT("HoudiniEngineUIAutoBakeCheckBox", "Auto Bake")) .ToolTipText(LOCTEXT("HoudiniEngineUIAutoBakeCheckBoxToolTip", "Automatically bake work result object as they are loaded.")) .Font(FEditorStyle::GetFontStyle(TEXT("PropertyWindow.NormalFont"))) ] .IsChecked_Lambda([InPDGAssetLink]() { return InPDGAssetLink->bBakeAfterAllWorkResultObjectsLoaded ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }) .OnCheckStateChanged_Lambda([InPDGAssetLink](ECheckBoxState NewState) { const bool bNewState = (NewState == ECheckBoxState::Checked); if (!InPDGAssetLink || InPDGAssetLink->IsPendingKill()) return; // Record a transaction for undo/redo FScopedTransaction Transaction( TEXT(HOUDINI_MODULE_RUNTIME), LOCTEXT("HoudiniPDGAssetLinkParameterChange", "Houdini PDG Asset Link Parameter: Changing a value"), InPDGAssetLink); InPDGAssetLink->Modify(); InPDGAssetLink->bBakeAfterAllWorkResultObjectsLoaded = bNewState; // Notify that we have changed the property FHoudiniEngineEditorUtils::NotifyPostEditChangeProperty( GET_MEMBER_NAME_STRING_CHECKED(UHoudiniPDGAssetLink, bBakeAfterAllWorkResultObjectsLoaded), InPDGAssetLink); }) ] ]; AdditionalBakeSettingsRow.WholeRowWidget.Widget = AdditionalBakeSettingsRowHorizontalBox; } FTextAndTooltip::FTextAndTooltip(int32 InValue, const FString& InText) : Text(InText) , Value(InValue) { } FTextAndTooltip::FTextAndTooltip(int32 InValue, const FString& InText, const FString &InToolTip) : Text(InText) , ToolTip(InToolTip) , Value(InValue) { } FTextAndTooltip::FTextAndTooltip(int32 InValue, FString&& InText) : Text(InText) , Value(InValue) { } FTextAndTooltip::FTextAndTooltip(int32 InValue, FString&& InText, FString&& InToolTip) : Text(InText) , ToolTip(InToolTip) , Value(InValue) { } #undef LOCTEXT_NAMESPACE