Files
UnrealEngineUWP/Engine/Source/Editor/AnimGraph/Private/AnimGraphNode_SubInstance.cpp
lina halper 93d3255859 Copying //UE4/Dev-Anim to //UE4/Dev-Main (Source: //UE4/Dev-Anim [at] 4335583)
#lockdown Nick.Penwarden
#rb:none
============================
  MAJOR FEATURES & CHANGES
============================

Change 4300079 by Thomas.Sarkanen

	Fixed incorrect sub instance error reporting when multiple sub-instances of the same class are used

	#jira UE-62613 - Using a Sub Anim Instance causes a "Sub Instance Loop" Compile Error

Change 4301787 by Lina.Halper

	Fix crash when multi select with bone driven controller

	- make sure you can't continue with mismatching types of classes, code becomes very vulnerable

	#jira: UE-58531

Change 4302310 by Lina.Halper

	Fix for anim slot window of small icons

	#jira: UE-54822

Change 4302385 by Lina.Halper

	remove constructor female data from control rig example

Change 4303861 by Lina.Halper

	PR #4298: Avoid division by 0 in AnimLinkableElement (Contributed by DSDambuster)


	#jira: UE-53260

Change 4304915 by Lina.Halper

	PR #4842: UE-52767: UAnimComposite doesn't inherit from UAnimSequence (Contributed by projectgheist)

	Fixes issue with stepping not working with AnimComposite
	- it also loops around


	#jira: UE-61367

Change 4307241 by Lina.Halper

	Control rig : invalid FOV issue

	#jira: UE-62799

Change 4310924 by Lina.Halper

	Only display native control rig class for parent class picker

	#jira: UE-62798

Change 4314164 by Jurre.deBaare

	Fix issue with propagating vertex colors to static meshes in some cases not working correctly
	#fix Using .OverrideVertexColors instead of .OverrideMapBuildData to do pointer comparison

	#jira none

Change 4321197 by Lina.Halper

	fix aim offset assets

	- removed aimtest2 as it doens't really do anything

Change 4321673 by Martin.Wilson

	Autoload Animation Blueprint compiler module in editor so that we can set up logging category (following how normal blueprint compiler works)

	#jira UE-59319

Change 4321898 by Martin.Wilson

	Change Live Link tab to nomad

	#jira UE-54412

Change 4322333 by Thomas.Sarkanen

	Fix additional params being displayed on blutility asset/actor actions dialog

	#jira UE-62063 - Blutility context menu popup shows all pins inside function

Change 4322338 by Thomas.Sarkanen

	Fixed mis-reported HLOD build indicator

	#jira UE-62648 - HLOD cluster needs to be rebuilt even though they have been build

Change 4322368 by Lina.Halper

	fixed build error

Change 4322378 by Lina.Halper

	Fix issue where preview asset is not saved and not loaded unless it's loaded manually

	#jira: UE-63254

Change 4322490 by Lina.Halper

	- added an option to refresh transform as that can solve quick issues where ref pose goes wrong
	- fixed search

	#jira: UE-63136, UE-63181

Change 4322824 by Martin.Wilson

	Fix possible crash due to retarget asset being cleaned up on ULiveLinkInstance

	#jira UE-62265

Change 4325357 by Thomas.Sarkanen

	Perform anim update even when we have zero bones, but still skip evaluation

	This ensures that notifies still get triggered in all update-relevant tick modes, even on dedicated servers.

	#jira UE-63204 - Dedicated servers do not trigger anim notifies in anim sequences

Change 4325358 by Thomas.Sarkanen

	Fix incorrect interpolations when using URO and CopyPoseFromMesh

	This ensures that CopyPoseFromMesh uses the correct target pose to inteprolate to, rather than the already-interpolated output pose.

	#jira UE-62588 - URO is incompatible with CopyPoseFromMesh in certain circumstances

Change 4325552 by Martin.Wilson

	Fix doubling of playrate in marker sync code

	#jira UE-59008

Change 4331507 by Thomas.Sarkanen

	Fixed crash when creating a new notify in certain circumstances

	When creating a notify causes a timing order change, then the timing panel wasnt getting refreshed and was accessing the wrong notify to do its drawing.

	#jira UE-63476 - Editor crashes if you add a notify to montage

#ROBOMERGE-OWNER: jason.bestimt
#ROBOMERGE-SOURCE: CL 4335668 in //UE4/Main/...
#ROBOMERGE-BOT: DEVVR (Main -> Dev-VR)

[CL 4335669 by lina halper in Dev-VR branch]
2018-08-30 20:29:58 -04:00

550 lines
16 KiB
C++

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "AnimGraphNode_SubInstance.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Input/SCheckBox.h"
#include "EdGraphSchema_K2.h"
#include "Kismet2/CompilerResultsLog.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "PropertyHandle.h"
#include "DetailLayoutBuilder.h"
#include "DetailCategoryBuilder.h"
#include "Animation/AnimNode_SubInput.h"
#include "PropertyCustomizationHelpers.h"
#include "ScopedTransaction.h"
#include "AnimationGraphSchema.h"
#define LOCTEXT_NAMESPACE "SubInstanceNode"
namespace SubInstanceGraphNodeConstants
{
FLinearColor TitleColor(0.2f, 0.2f, 0.8f);
}
FLinearColor UAnimGraphNode_SubInstance::GetNodeTitleColor() const
{
return SubInstanceGraphNodeConstants::TitleColor;
}
FText UAnimGraphNode_SubInstance::GetTooltipText() const
{
return LOCTEXT("ToolTip", "Runs a sub-anim instance to process animation");
}
FText UAnimGraphNode_SubInstance::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
UClass* TargetClass = *Node.InstanceClass;
FFormatNamedArguments Args;
Args.Add(TEXT("NodeTitle"), LOCTEXT("Title", "Sub Anim Instance"));
Args.Add(TEXT("TargetClass"), TargetClass ? FText::FromString(TargetClass->GetName()) : LOCTEXT("ClassNone", "None"));
if(TitleType == ENodeTitleType::MenuTitle)
{
return LOCTEXT("NodeTitle", "Sub Anim Instance");
}
if(TitleType == ENodeTitleType::ListView)
{
if(Node.Tag != NAME_None)
{
Args.Add(TEXT("Tag"), FText::FromName(Node.Tag));
return FText::Format(LOCTEXT("TitleListFormatTagged", "{NodeTitle} - Target Class: {TargetClass} - Tag: {Tag}"), Args);
}
else
{
return FText::Format(LOCTEXT("TitleListFormat", "{NodeTitle} - Target Class: {TargetClass}"), Args);
}
}
else
{
if(Node.Tag != NAME_None)
{
Args.Add(TEXT("Tag"), FText::FromName(Node.Tag));
return FText::Format(LOCTEXT("TitleFormatTagged", "{NodeTitle}\nTarget Class: {TargetClass}\nTag: {Tag}"), Args);
}
else
{
return FText::Format(LOCTEXT("TitleFormat", "{NodeTitle}\nTarget Class: {TargetClass}"), Args);
}
}
}
void UAnimGraphNode_SubInstance::ValidateAnimNodeDuringCompilation(USkeleton* ForSkeleton, FCompilerResultsLog& MessageLog)
{
Super::ValidateAnimNodeDuringCompilation(ForSkeleton, MessageLog);
UAnimBlueprint* AnimBP = CastChecked<UAnimBlueprint>(GetBlueprint());
UObject* OriginalNode = MessageLog.FindSourceObject(this);
// Check we have a class set
if(!*Node.InstanceClass)
{
MessageLog.Error(TEXT("Sub instance node @@ has no valid instance class to spawn."), this);
}
if(HasInstanceLoop())
{
MessageLog.Error(TEXT("Detected loop in sub instance chain starting at @@ inside class @@"), this, AnimBP->GetAnimBlueprintGeneratedClass());
}
// Check for cycles from other sub instance nodes
TArray<UEdGraph*> Graphs;
AnimBP->GetAllGraphs(Graphs);
// Check for duplicate tags in this anim blueprint
for(UEdGraph* Graph : Graphs)
{
TArray<UAnimGraphNode_SubInstance*> SubInstanceNodes;
Graph->GetNodesOfClass(SubInstanceNodes);
for(UAnimGraphNode_SubInstance* SubInstanceNode : SubInstanceNodes)
{
if(SubInstanceNode == OriginalNode)
{
continue;
}
FAnimNode_SubInstance& InnerNode = SubInstanceNode->Node;
if(InnerNode.Tag != NAME_None && InnerNode.Tag == Node.Tag)
{
MessageLog.Error(*FText::Format(LOCTEXT("DuplicateTagErrorFormat", "Node @@ and node @@ both have the same tag '{0}'."), FText::FromName(Node.Tag)).ToString(), this, SubInstanceNode);
}
}
}
// Check we don't try to spawn our own blueprint
if(*Node.InstanceClass == AnimBP->GetAnimBlueprintGeneratedClass())
{
MessageLog.Error(TEXT("Sub instance node @@ targets instance class @@ which it is inside, this would cause a loop."), this, AnimBP->GetAnimBlueprintGeneratedClass());
}
}
void UAnimGraphNode_SubInstance::ReallocatePinsDuringReconstruction(TArray<UEdGraphPin*>& OldPins)
{
Super::ReallocatePinsDuringReconstruction(OldPins);
UClass* TargetClass = *Node.InstanceClass;
if(!TargetClass)
{
// Nothing to search for properties
return;
}
// Need the schema to extract pin types
const UEdGraphSchema_K2* Schema = CastChecked<UEdGraphSchema_K2>(GetSchema());
// Default anim schema for util funcions
const UAnimationGraphSchema* AnimGraphDefaultSchema = GetDefault<UAnimationGraphSchema>();
bool bShowPose = false;
// Scan the target class for a sub input node, we only want to show the pose input if
// we have that node available
for(TFieldIterator<UProperty> It(TargetClass); It; ++It)
{
UProperty* CurrentProp = *It;
if(UStructProperty* StructProp = Cast<UStructProperty>(CurrentProp))
{
if(StructProp->Struct->IsChildOf(FAnimNode_SubInput::StaticStruct()))
{
// Found a pose input
bShowPose = true;
break;
}
}
}
if(bShowPose)
{
if(UProperty* PoseProperty = FindField<UProperty>(FAnimNode_SubInstance::StaticStruct(), GET_MEMBER_NAME_CHECKED(FAnimNode_SubInstance, InPose)))
{
FEdGraphPinType PinType;
if(Schema->ConvertPropertyToPinType(PoseProperty, PinType))
{
UEdGraphPin* NewPin = CreatePin(EEdGraphPinDirection::EGPD_Input, PinType, PoseProperty->GetFName());
NewPin->PinFriendlyName = PoseProperty->GetDisplayNameText();
CustomizePinData(NewPin, PoseProperty->GetFName(), INDEX_NONE);
}
}
}
// Grab the list of properties we can expose
TArray<UProperty*> ExposablePropeties;
GetExposableProperties(ExposablePropeties);
// We'll track the names we encounter by removing from this list, if anything remains the properties
// have been removed from the target class and we should remove them too
TArray<FName> BeginExposableNames = KnownExposableProperties;
for(UProperty* Property : ExposablePropeties)
{
FName PropertyName = Property->GetFName();
BeginExposableNames.Remove(PropertyName);
if(!KnownExposableProperties.Contains(PropertyName))
{
// New property added to the target class
KnownExposableProperties.Add(PropertyName);
}
if(ExposedPropertyNames.Contains(PropertyName) && FBlueprintEditorUtils::PropertyStillExists(Property))
{
FEdGraphPinType PinType;
verify(Schema->ConvertPropertyToPinType(Property, PinType));
UEdGraphPin* NewPin = CreatePin(EEdGraphPinDirection::EGPD_Input, PinType, Property->GetFName());
NewPin->PinFriendlyName = Property->GetDisplayNameText();
// Need to grab the default value for the property from the target generated class CDO
FString CDODefaultValueString;
uint8* ContainerPtr = reinterpret_cast<uint8*>(TargetClass->GetDefaultObject());
if(FBlueprintEditorUtils::PropertyValueToString(Property, ContainerPtr, CDODefaultValueString))
{
// If we successfully pulled a value, set it to the pin
Schema->TrySetDefaultValue(*NewPin, CDODefaultValueString);
}
CustomizePinData(NewPin, PropertyName, INDEX_NONE);
}
}
// Remove any properties that no longer exist on the target class
for(FName& RemovedPropertyName : BeginExposableNames)
{
KnownExposableProperties.Remove(RemovedPropertyName);
}
}
void UAnimGraphNode_SubInstance::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
bool bRequiresNodeReconstruct = false;
UProperty* ChangedProperty = PropertyChangedEvent.Property;
if(ChangedProperty)
{
if(ChangedProperty->GetFName() == GET_MEMBER_NAME_CHECKED(FAnimNode_SubInstance, InstanceClass))
{
bRequiresNodeReconstruct = true;
RebuildExposedProperties(*Node.InstanceClass);
}
}
if(bRequiresNodeReconstruct)
{
ReconstructNode();
}
}
void UAnimGraphNode_SubInstance::GetInstancePinProperty(const UClass* InOwnerInstanceClass, UEdGraphPin* InInputPin, UProperty*& OutProperty)
{
// The actual name of the instance property
FString FullName = GetPinTargetVariableName(InInputPin);
if(UProperty* Property = FindField<UProperty>(InOwnerInstanceClass, *FullName))
{
OutProperty = Property;
}
else
{
OutProperty = nullptr;
}
}
FString UAnimGraphNode_SubInstance::GetPinTargetVariableName(const UEdGraphPin* InPin) const
{
return TEXT("__SUBINSTANCE_") + InPin->PinName.ToString() + TEXT("_") + NodeGuid.ToString();
}
void UAnimGraphNode_SubInstance::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
Super::CustomizeDetails(DetailBuilder);
// We dont allow multi-select here
if(DetailBuilder.GetSelectedObjects().Num() > 1)
{
return;
}
TArray<UProperty*> ExposableProperties;
GetExposableProperties(ExposableProperties);
if(ExposableProperties.Num() > 0)
{
IDetailCategoryBuilder& CategoryBuilder = DetailBuilder.EditCategory(FName(TEXT("Sub Instance Properties")));
for(UProperty* Property : ExposableProperties)
{
FDetailWidgetRow& WidgetRow = CategoryBuilder.AddCustomRow(FText::FromString(Property->GetName()));
FName PropertyName = Property->GetFName();
FText PropertyTypeText = GetPropertyTypeText(Property);
FFormatNamedArguments Args;
Args.Add(TEXT("PropertyName"), FText::FromName(PropertyName));
Args.Add(TEXT("PropertyType"), PropertyTypeText);
FText TooltipText = FText::Format(LOCTEXT("PropertyTooltipText", "{PropertyName}\nType: {PropertyType}"), Args);
WidgetRow.NameContent()
[
SNew(STextBlock)
.Text(FText::FromString(Property->GetName()))
.ToolTipText(TooltipText)
];
WidgetRow.ValueContent()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
[
SNew(STextBlock)
.Text(LOCTEXT("ExposePropertyValue", "Expose: "))
]
+SHorizontalBox::Slot()
[
SNew(SCheckBox)
.IsChecked_UObject(this, &UAnimGraphNode_SubInstance::IsPropertyExposed, PropertyName)
.OnCheckStateChanged_UObject(this, &UAnimGraphNode_SubInstance::OnPropertyExposeCheckboxChanged, PropertyName)
]
];
}
}
TSharedRef<IPropertyHandle> ClassHandle = DetailBuilder.GetProperty(TEXT("Node.InstanceClass"), GetClass());
if(ClassHandle->IsValidHandle())
{
ClassHandle->SetOnPropertyValueChanged(FSimpleDelegate::CreateUObject(this, &UAnimGraphNode_SubInstance::OnInstanceClassChanged, &DetailBuilder));
}
ClassHandle->MarkHiddenByCustomization();
IDetailCategoryBuilder& CategoryBuilder = DetailBuilder.EditCategory(FName(TEXT("Settings")));
FDetailWidgetRow& ClassWidgetRow = CategoryBuilder.AddCustomRow(LOCTEXT("FilterString", "Instance Class"));
ClassWidgetRow.NameContent()
[
ClassHandle->CreatePropertyNameWidget()
];
ClassWidgetRow.ValueContent()
[
SNew(SObjectPropertyEntryBox)
.ObjectPath_UObject(this, &UAnimGraphNode_SubInstance::GetCurrentInstanceBlueprintPath)
.AllowedClass(UAnimBlueprint::StaticClass())
.NewAssetFactories(TArray<UFactory*>())
.OnShouldFilterAsset(FOnShouldFilterAsset::CreateUObject(this, &UAnimGraphNode_SubInstance::OnShouldFilterInstanceBlueprint))
.OnObjectChanged(FOnSetObject::CreateUObject(this, &UAnimGraphNode_SubInstance::OnSetInstanceBlueprint, ClassHandle))
];
}
FText UAnimGraphNode_SubInstance::GetPropertyTypeText(UProperty* Property)
{
FText PropertyTypeText;
if(UStructProperty* StructProperty = Cast<UStructProperty>(Property))
{
PropertyTypeText = StructProperty->Struct->GetDisplayNameText();
}
else if(UObjectProperty* ObjectProperty = Cast<UObjectProperty>(Property))
{
PropertyTypeText = ObjectProperty->PropertyClass->GetDisplayNameText();
}
else if(UClass* PropClass = Property->GetClass())
{
PropertyTypeText = PropClass->GetDisplayNameText();
}
else
{
PropertyTypeText = LOCTEXT("PropertyTypeUnknown", "Unknown");
}
return PropertyTypeText;
}
void UAnimGraphNode_SubInstance::RebuildExposedProperties(UClass* InNewClass)
{
ExposedPropertyNames.Empty();
KnownExposableProperties.Empty();
if(InNewClass)
{
TArray<UProperty*> ExposableProperties;
GetExposableProperties(ExposableProperties);
for(UProperty* Property : ExposableProperties)
{
KnownExposableProperties.Add(Property->GetFName());
}
}
}
bool UAnimGraphNode_SubInstance::HasInstanceLoop()
{
TArray<FGuid> VisitedList;
TArray<FGuid> CurrentStack;
return HasInstanceLoop_Recursive(this, VisitedList, CurrentStack);
}
bool UAnimGraphNode_SubInstance::HasInstanceLoop_Recursive(UAnimGraphNode_SubInstance* CurrNode, TArray<FGuid>& VisitedNodes, TArray<FGuid>& NodeStack)
{
if(!VisitedNodes.Contains(CurrNode->NodeGuid))
{
VisitedNodes.Add(CurrNode->NodeGuid);
NodeStack.Add(CurrNode->NodeGuid);
if(UAnimBlueprint* AnimBP = Cast<UAnimBlueprint>(UBlueprint::GetBlueprintFromClass(CurrNode->Node.InstanceClass)))
{
// Check for cycles from other sub instance nodes
TArray<UEdGraph*> Graphs;
AnimBP->GetAllGraphs(Graphs);
for(UEdGraph* Graph : Graphs)
{
TArray<UAnimGraphNode_SubInstance*> SubInstanceNodes;
Graph->GetNodesOfClass(SubInstanceNodes);
for(UAnimGraphNode_SubInstance* SubInstanceNode : SubInstanceNodes)
{
// If we haven't visited this node, then check it for loops, otherwise if we're pointing to a previously visited node that is in the current instance stack we have a loop
if((!VisitedNodes.Contains(SubInstanceNode->NodeGuid) && HasInstanceLoop_Recursive(SubInstanceNode, VisitedNodes, NodeStack)) || NodeStack.Contains(SubInstanceNode->NodeGuid))
{
return true;
}
}
}
}
}
NodeStack.Remove(CurrNode->NodeGuid);
return false;
}
ECheckBoxState UAnimGraphNode_SubInstance::IsPropertyExposed(FName PropertyName) const
{
return ExposedPropertyNames.Contains(PropertyName) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
}
void UAnimGraphNode_SubInstance::OnPropertyExposeCheckboxChanged(ECheckBoxState NewState, FName PropertyName)
{
if(NewState == ECheckBoxState::Checked)
{
ExposedPropertyNames.AddUnique(PropertyName);
}
else if(NewState == ECheckBoxState::Unchecked)
{
ExposedPropertyNames.Remove(PropertyName);
}
ReconstructNode();
}
void UAnimGraphNode_SubInstance::OnInstanceClassChanged(IDetailLayoutBuilder* DetailBuilder)
{
if(DetailBuilder)
{
DetailBuilder->ForceRefreshDetails();
}
}
FString UAnimGraphNode_SubInstance::GetCurrentInstanceBlueprintPath() const
{
UClass* InstanceClass = *Node.InstanceClass;
if(InstanceClass)
{
UBlueprint* ActualBlueprint = UBlueprint::GetBlueprintFromClass(InstanceClass);
if(ActualBlueprint)
{
return ActualBlueprint->GetPathName();
}
}
return FString();
}
bool UAnimGraphNode_SubInstance::OnShouldFilterInstanceBlueprint(const FAssetData& AssetData) const
{
if(const FString* SkeletonName = AssetData.TagsAndValues.Find(TEXT("TargetSkeleton")))
{
if(UAnimBlueprint* CurrentBlueprint = Cast<UAnimBlueprint>(GetBlueprint()))
{
FString TargetSkeletonName = FString::Printf(TEXT("%s'%s'"), *CurrentBlueprint->TargetSkeleton->GetClass()->GetName(), *CurrentBlueprint->TargetSkeleton->GetPathName());
return *SkeletonName != TargetSkeletonName;
}
}
return false;
}
void UAnimGraphNode_SubInstance::OnSetInstanceBlueprint(const FAssetData& AssetData, TSharedRef<IPropertyHandle> InstanceClassPropHandle)
{
if(UAnimBlueprint* Blueprint = Cast<UAnimBlueprint>(AssetData.GetAsset()))
{
FScopedTransaction Transaction(LOCTEXT("SetBP", "Set Instance Blueprint"));
Modify();
InstanceClassPropHandle->SetValue(Blueprint->GetAnimBlueprintGeneratedClass());
}
}
UObject* UAnimGraphNode_SubInstance::GetJumpTargetForDoubleClick() const
{
UClass* InstanceClass = *Node.InstanceClass;
if(InstanceClass)
{
return InstanceClass->ClassGeneratedBy;
}
return nullptr;
}
bool UAnimGraphNode_SubInstance::HasExternalDependencies(TArray<class UStruct*>* OptionalOutput /*= NULL*/) const
{
UClass* InstanceClassToUse = *Node.InstanceClass;
// Add our instance class... If that changes we need a recompile
if(InstanceClassToUse && OptionalOutput)
{
OptionalOutput->AddUnique(InstanceClassToUse);
}
bool bSuperResult = Super::HasExternalDependencies(OptionalOutput);
return InstanceClassToUse || bSuperResult;
}
void UAnimGraphNode_SubInstance::GetExposableProperties(TArray<UProperty*>& OutExposableProperties) const
{
OutExposableProperties.Empty();
UClass* TargetClass = *Node.InstanceClass;
if(TargetClass)
{
const UEdGraphSchema_K2* Schema = CastChecked<UEdGraphSchema_K2>(GetSchema());
for(TFieldIterator<UProperty> It(TargetClass, EFieldIteratorFlags::IncludeSuper); It; ++It)
{
UProperty* CurProperty = *It;
FEdGraphPinType PinType;
if(CurProperty->HasAllPropertyFlags(CPF_Edit | CPF_BlueprintVisible) && CurProperty->HasAllFlags(RF_Public) && Schema->ConvertPropertyToPinType(CurProperty, PinType))
{
OutExposableProperties.Add(CurProperty);
}
}
}
}
#undef LOCTEXT_NAMESPACE