// Copyright Epic Games, Inc. All Rights Reserved. #include "RigVMModel/RigVMController.h" #include "RigVMModel/RigVMControllerActions.h" #include "RigVMModel/Nodes/RigVMFunctionEntryNode.h" #include "RigVMModel/Nodes/RigVMFunctionReturnNode.h" #include "RigVMModel/Nodes/RigVMFunctionReferenceNode.h" #include "RigVMModel/Nodes/RigVMAggregateNode.h" #include "RigVMModel/Nodes/RigVMDispatchNode.h" #include "RigVMCore/RigVMRegistry.h" #include "RigVMCore/RigVMExecuteContext.h" #include "RigVMCore/RigVMUnknownType.h" #include "RigVMCore/RigVMByteCode.h" #include "RigVMFunctions/RigVMFunction_ControlFlow.h" #include "RigVMCompiler/RigVMCompiler.h" #include "RigVMDeveloperModule.h" #include "UObject/PropertyPortFlags.h" #include "UObject/Package.h" #include "UObject/StrongObjectPtr.h" #include "Misc/CoreMisc.h" #include "Algo/Sort.h" #include "Algo/Count.h" #include "Algo/Transform.h" #include "RigVMPythonUtils.h" #include "RigVMTypeUtils.h" #include "Engine/UserDefinedStruct.h" #include "RigVMFunctions/RigVMDispatch_If.h" #include "RigVMFunctions/RigVMDispatch_Select.h" #include "RigVMFunctions/RigVMDispatch_Array.h" #include "RigVMModel/RigVMClient.h" #include "RigVMModel/Nodes/RigVMBranchNode.h" #include "RigVMModel/Nodes/RigVMArrayNode.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(RigVMController) #if WITH_EDITOR #include "Exporters/Exporter.h" #include "UnrealExporter.h" #include "Factories.h" #include "UObject/CoreRedirects.h" #include "Framework/Notifications/NotificationManager.h" #include "Widgets/Notifications/SNotificationList.h" #include "Styling/AppStyle.h" #include "AssetRegistry/AssetRegistryModule.h" #endif TMap URigVMController::PinPathCoreRedirectors; FRigVMControllerCompileBracketScope::FRigVMControllerCompileBracketScope(URigVMController* InController) : Graph(nullptr), bSuspendNotifications(InController->bSuspendNotifications) { check(InController); Graph = InController->GetGraph(); check(Graph); if (bSuspendNotifications) { return; } Graph->Notify(ERigVMGraphNotifType::InteractionBracketOpened, nullptr); } FRigVMControllerCompileBracketScope::~FRigVMControllerCompileBracketScope() { check(Graph); if (bSuspendNotifications) { return; } Graph->Notify(ERigVMGraphNotifType::InteractionBracketClosed, nullptr); } void FRigVMClientPatchResult::Merge(const FRigVMClientPatchResult& InOther) { bSucceeded = Succeeded() && InOther.Succeeded(); bChangedContent = ChangedContent() || InOther.ChangedContent(); bRequiresToMarkPackageDirty = RequiresToMarkPackageDirty() || InOther.RequiresToMarkPackageDirty(); ErrorMessages.Append(InOther.GetErrorMessages()); RemovedNodes.Append(InOther.GetRemovedNodes()); AddedNodes.Append(InOther.GetAddedNodes()); } URigVMController::URigVMController() : bValidatePinDefaults(true) , bSuspendNotifications(false) , bReportWarningsAndErrors(true) , bIgnoreRerouteCompactnessChanges(false) , UserLinkDirection(ERigVMPinDirection::Invalid) , bEnableTypeCasting(true) , bIsTransacting(false) , bIsRunningUnitTest(false) , bIsFullyResolvingTemplateNode(false) , bSuspendTemplateComputation(false) #if WITH_EDITOR , bRegisterTemplateNodeUsage(true) #endif { } URigVMController::URigVMController(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , bValidatePinDefaults(true) , bSuspendNotifications(false) , bReportWarningsAndErrors(true) , bIgnoreRerouteCompactnessChanges(false) , UserLinkDirection(ERigVMPinDirection::Invalid) , bEnableTypeCasting(true) , bIsTransacting(false) , bIsRunningUnitTest(false) , bIsFullyResolvingTemplateNode(false) , bSuspendTemplateComputation(false) #if WITH_EDITOR , bRegisterTemplateNodeUsage(true) #endif { ActionStack = CreateDefaultSubobject(TEXT("ActionStack")); ActionStack->OnModified().AddLambda([&](ERigVMGraphNotifType NotifType, URigVMGraph* InGraph, UObject* InSubject) -> void { Notify(NotifType, InSubject); }); } URigVMController::~URigVMController() { } #if WITH_EDITORONLY_DATA void URigVMController::DeclareConstructClasses(TArray& OutConstructClasses, const UClass* SpecificSubclass) { Super::DeclareConstructClasses(OutConstructClasses, SpecificSubclass); OutConstructClasses.Add(FTopLevelAssetPath(URigVMActionStack::StaticClass())); OutConstructClasses.Add(FTopLevelAssetPath(URigVMInjectionInfo::StaticClass())); OutConstructClasses.Add(FTopLevelAssetPath(URigVMPin::StaticClass())); OutConstructClasses.Add(FTopLevelAssetPath(URigVMVariableNode::StaticClass())); OutConstructClasses.Add(FTopLevelAssetPath(URigVMLink::StaticClass())); } #endif URigVMGraph* URigVMController::GetGraph() const { if (Graphs.Num() == 0) { return nullptr; } return Graphs.Last(); } void URigVMController::SetGraph(URigVMGraph* InGraph) { ensure(Graphs.Num() < 2); URigVMGraph* LastGraph = GetGraph(); if (LastGraph) { if(LastGraph == InGraph) { return; } LastGraph->OnModified().RemoveAll(this); } Graphs.Reset(); if (InGraph != nullptr) { PushGraph(InGraph, false); } HandleModifiedEvent(ERigVMGraphNotifType::GraphChanged, GetGraph(), nullptr); } void URigVMController::PushGraph(URigVMGraph* InGraph, bool bSetupUndoRedo) { URigVMGraph* LastGraph = GetGraph(); if (LastGraph) { LastGraph->OnModified().RemoveAll(this); } check(InGraph); Graphs.Push(InGraph); InGraph->OnModified().AddUObject(this, &URigVMController::HandleModifiedEvent); if (bSetupUndoRedo) { ActionStack->AddAction(FRigVMPushGraphAction(InGraph)); } } URigVMGraph* URigVMController::PopGraph(bool bSetupUndoRedo) { ensure(Graphs.Num() > 1); URigVMGraph* LastGraph = GetGraph(); if (LastGraph) { LastGraph->OnModified().RemoveAll(this); } Graphs.Pop(); URigVMGraph* CurrentGraph = GetGraph(); if (CurrentGraph) { CurrentGraph->OnModified().AddUObject(this, &URigVMController::HandleModifiedEvent); } if (bSetupUndoRedo) { ActionStack->AddAction(FRigVMPopGraphAction(LastGraph)); } return LastGraph; } URigVMGraph* URigVMController::GetTopLevelGraph() const { URigVMGraph* Graph = GetGraph(); UObject* Outer = Graph->GetOuter(); while (Outer) { if (URigVMGraph* OuterGraph = Cast(Outer)) { Graph = OuterGraph; Outer = Outer->GetOuter(); } else if (URigVMLibraryNode* LibraryNode = Cast(Outer)) { Outer = Outer->GetOuter(); } else { break; } } return Graph; } FRigVMGraphModifiedEvent& URigVMController::OnModified() { return ModifiedEventStatic; } void URigVMController::Notify(ERigVMGraphNotifType InNotifType, UObject* InSubject) const { if (bSuspendNotifications) { return; } if (URigVMGraph* Graph = GetGraph()) { Graph->Notify(InNotifType, InSubject); } } void URigVMController::ResendAllNotifications() { if (URigVMGraph* Graph = GetGraph()) { for (URigVMLink* Link : Graph->Links) { Notify(ERigVMGraphNotifType::LinkRemoved, Link); } for (URigVMNode* Node : Graph->Nodes) { Notify(ERigVMGraphNotifType::NodeRemoved, Node); } for (URigVMNode* Node : Graph->Nodes) { Notify(ERigVMGraphNotifType::NodeAdded, Node); if (URigVMCommentNode* CommentNode = Cast(Node)) { Notify(ERigVMGraphNotifType::CommentTextChanged, Node); } } for (URigVMLink* Link : Graph->Links) { Notify(ERigVMGraphNotifType::LinkAdded, Link); } } } void URigVMController::SetIsRunningUnitTest(bool bIsRunning) { bIsRunningUnitTest = bIsRunning; if(URigVMBuildData* BuildData = URigVMBuildData::Get()) { BuildData->SetIsRunningUnitTest(bIsRunning); } } URigVMController::FPinInfo::FPinInfo() : ParentIndex(INDEX_NONE) , Name(NAME_None) , Direction(ERigVMPinDirection::Invalid) , TypeIndex(INDEX_NONE) , bIsArray(false) , Property(nullptr) , bIsExpanded(false) , bIsConstant(false) , bIsDynamicArray(false) { } URigVMController::FPinInfo::FPinInfo(const URigVMPin* InPin, int32 InParentIndex, ERigVMPinDirection InDirection) : ParentIndex(InParentIndex) , Name(InPin->GetName()) , Direction(InDirection == ERigVMPinDirection::Invalid ? InPin->GetDirection() : InDirection) , TypeIndex(InPin->GetTypeIndex()) , bIsArray(InPin->IsArray()) , Property(nullptr) , bIsExpanded(InPin->IsExpanded()) , bIsConstant(InPin->IsDefinedAsConstant()) , bIsDynamicArray(InPin->IsDynamicArray()) { // this method describes the info as currently represented in the model. CorrectExecuteTypeIndex(); DefaultValue = InPin->GetDefaultValue(); if(DefaultValue.IsEmpty() && (InPin->IsArray() || InPin->IsStruct())) { static const FString EmptyBraces(TEXT("()")); DefaultValue = EmptyBraces; } } URigVMController::FPinInfo::FPinInfo(FProperty* InProperty, ERigVMPinDirection InDirection, int32 InParentIndex, const uint8* InDefaultValueMemory) : ParentIndex(InParentIndex) , Name(InProperty->GetFName()) , Direction(InDirection) , TypeIndex(INDEX_NONE) , bIsArray(InProperty->IsA()) , Property(InProperty) , bIsExpanded(false) , bIsConstant(false) , bIsDynamicArray(false) { // this method describes the info as needed based on the property structure if (Direction == ERigVMPinDirection::Invalid) { Direction = FRigVMStruct::GetPinDirectionFromProperty(Property); } #if WITH_EDITOR if (CastField(InProperty->GetOwnerProperty()) == nullptr) { const FString DisplayNameText = InProperty->GetDisplayNameText().ToString(); if (!DisplayNameText.IsEmpty()) { DisplayName = *DisplayNameText; } } bIsConstant = InProperty->HasMetaData(TEXT("Constant")); CustomWidgetName = InProperty->GetMetaData(TEXT("CustomWidget")); if (InProperty->HasMetaData(FRigVMStruct::ExpandPinByDefaultMetaName)) { bIsExpanded = true; } #endif #if WITH_EDITOR if (Direction == ERigVMPinDirection::Hidden) { if (!InProperty->HasMetaData(TEXT("ArraySize"))) { bIsDynamicArray = true; } } if (bIsDynamicArray) { if (InProperty->HasMetaData(FRigVMStruct::SingletonMetaName)) { bIsDynamicArray = false; } } #endif UObject* CPPTypeObject = nullptr; FProperty* PropertyForType = InProperty; if (const FArrayProperty* ArrayProperty = CastField(PropertyForType)) { PropertyForType = ArrayProperty->Inner; } if (const FStructProperty* StructProperty = CastField(PropertyForType)) { CPPTypeObject = StructProperty->Struct; } else if (const FObjectProperty* ObjectProperty = CastField(PropertyForType)) { if(RigVMCore::SupportsUObjects()) { CPPTypeObject = ObjectProperty->PropertyClass; } } else if (const FInterfaceProperty* InterfaceProperty = CastField(PropertyForType)) { if (RigVMCore::SupportsUInterfaces()) { CPPTypeObject = InterfaceProperty->InterfaceClass; } } else { if (const FEnumProperty* EnumProperty = CastField(PropertyForType)) { CPPTypeObject = EnumProperty->GetEnum(); } if (const FByteProperty* ByteProperty = CastField(PropertyForType)) { CPPTypeObject = ByteProperty->Enum; } if(InDefaultValueMemory) { InProperty->ExportText_Direct(DefaultValue, InDefaultValueMemory, InDefaultValueMemory, nullptr, PPF_None, nullptr); } } FString ExtendedCppType; FString CPPType = InProperty->GetCPPType(&ExtendedCppType); CPPType += ExtendedCppType; CPPType = RigVMTypeUtils::PostProcessCPPType(CPPType, CPPTypeObject); TypeIndex = FRigVMRegistry::Get().GetTypeIndexFromCPPType(CPPType); CorrectExecuteTypeIndex(); } void URigVMController::FPinInfo::CorrectExecuteTypeIndex() { const FRigVMRegistry& Registry = FRigVMRegistry::Get(); if(Registry.IsExecuteType(TypeIndex)) { TRigVMTypeIndex DefaultExecuteType = RigVMTypeUtils::TypeIndex::Execute; if(Registry.IsArrayType(TypeIndex)) { DefaultExecuteType = Registry.GetArrayTypeFromBaseTypeIndex(DefaultExecuteType); } TypeIndex = DefaultExecuteType; } } uint32 GetTypeHash(const URigVMController::FPinInfo& InPin) { uint32 Hash = 0; //GetTypeHash(InPin.ParentIndex); Hash = HashCombine(Hash, GetTypeHash(InPin.Name)); Hash = HashCombine(Hash, GetTypeHash((int32)InPin.Direction)); Hash = HashCombine(Hash, GetTypeHash((int32)InPin.TypeIndex)); Hash = HashCombine(Hash, GetTypeHash(InPin.bIsArray)); // we are not hashing the parent index, pinpath, default value or the property since // it doesn't matter for the structure validity of the node return Hash; } URigVMController::FPinInfoArray::FPinInfoArray(const URigVMNode* InNode) { // this method adds all pins as currently represented in the model. for(const URigVMPin* Pin : InNode->GetPins()) { (void)AddPin(Pin, INDEX_NONE); } } int32 URigVMController::FPinInfoArray::AddPin(const URigVMPin* InPin, int32 InParentIndex, ERigVMPinDirection InDirection) { // this method adds all pins as currently represented in the model. const int32 Index = Pins.Emplace(InPin, InParentIndex, InDirection); for(const URigVMPin* SubPin : InPin->GetSubPins()) { const int32 SubPinIndex = AddPin(SubPin, Index, InDirection); Pins[Index].SubPins.Add(SubPinIndex); } return Index; } URigVMController::FPinInfoArray::FPinInfoArray(const URigVMNode* InNode, URigVMController* InController, const FPinInfoArray* InPreviousPinInfos) { // this method adds pins as needed based on the property structure for(const URigVMPin* Pin : InNode->GetPins()) { const FString DefaultValue = Pin->GetDefaultValue(); (void)AddPin(InController, INDEX_NONE, Pin->GetFName(), Pin->GetDirection(), Pin->GetTypeIndex(), DefaultValue, nullptr, InPreviousPinInfos); } } URigVMController::FPinInfoArray::FPinInfoArray(const FRigVMGraphFunctionHeader& FunctionHeader, URigVMController* InController, const FPinInfoArray* InPreviousPinInfos) { // this method adds pins as needed based on the property structure const FRigVMRegistry& Registry = FRigVMRegistry::Get(); for(const FRigVMGraphFunctionArgument& FunctionArgument : FunctionHeader.Arguments) { const TRigVMTypeIndex TypeIndex = Registry.GetTypeIndexFromCPPType(FunctionArgument.CPPType.ToString()); (void)AddPin(InController, INDEX_NONE, FunctionArgument.Name, FunctionArgument.Direction, TypeIndex, FunctionArgument.DefaultValue, nullptr, InPreviousPinInfos); } } int32 URigVMController::FPinInfoArray::AddPin(FProperty* InProperty, URigVMController* InController, ERigVMPinDirection InDirection, int32 InParentIndex, const uint8* InDefaultValueMemory) { // this method adds pins as needed based on the property structure check(InDefaultValueMemory); const int32 Index = Pins.Emplace(InProperty, InDirection, InParentIndex, InDefaultValueMemory); if(InParentIndex != INDEX_NONE) { Pins[InParentIndex].SubPins.Add(Index); } if (const FStructProperty* StructProperty = CastField(InProperty)) { (void)AddPins(StructProperty->Struct, InController, Pins[Index].Direction, Index, InDefaultValueMemory); } else if (const FArrayProperty* ArrayProperty = CastField(InProperty)) { FScriptArrayHelper ArrayHelper(ArrayProperty, InDefaultValueMemory); for(int32 ElementIndex = 0; ElementIndex < ArrayHelper.Num(); ElementIndex++) { const uint8* ElementDefaultValueMemory = ArrayHelper.GetRawPtr(ElementIndex); const int32 SubIndex = AddPin(ArrayProperty->Inner, InController, Pins[Index].Direction, Index, ElementDefaultValueMemory); Pins[SubIndex].Name = *FString::FormatAsNumber(ElementIndex); } } return Index; } int32 URigVMController::FPinInfoArray::AddPin(URigVMController* InController, int32 InParentIndex, const FName& InName, ERigVMPinDirection InDirection, TRigVMTypeIndex InTypeIndex, const FString& InDefaultValue, const uint8* InDefaultValueMemory, const FPinInfoArray* InPreviousPinInfos) { const FRigVMRegistry& Registry = FRigVMRegistry::Get(); FPinInfo Info; Info.ParentIndex = InParentIndex; Info.Name = InName; Info.Direction = InDirection; Info.TypeIndex = InTypeIndex; Info.bIsArray = Registry.IsArrayType(InTypeIndex); Info.DefaultValue = InDefaultValue; Info.CorrectExecuteTypeIndex(); const int32 Index = Pins.Add(Info); if(InPreviousPinInfos) { const FString& PinPath = GetPinPath(Index); const int32 PreviousIndex = InPreviousPinInfos->GetIndexFromPinPath(PinPath); if(PreviousIndex != INDEX_NONE) { const FPinInfo& PreviousPin = (*InPreviousPinInfos)[PreviousIndex]; if(PreviousPin.TypeIndex == InTypeIndex) { Pins[Index].DefaultValue = PreviousPin.DefaultValue; } } } const FRigVMTemplateArgumentType& Type = Registry.GetType(InTypeIndex); if(!Type.IsWildCard()) { if(Info.bIsArray) { const TRigVMTypeIndex& ElementTypeIndex = Registry.GetBaseTypeFromArrayTypeIndex(InTypeIndex); const FRigVMTemplateArgumentType& ElementType = Registry.GetType(ElementTypeIndex); const TArray Elements = URigVMPin::SplitDefaultValue(Pins[Index].DefaultValue); for(int32 ElementIndex = 0; ElementIndex < Elements.Num(); ElementIndex++) { const FString& ElementDefaultValue = Elements[ElementIndex]; const uint8* ElementDefaultValueMemory = nullptr; FStructOnScope ElementDefaultValueMemoryScope; if(UScriptStruct* ElementScriptStruct = Cast(ElementType.CPPTypeObject)) { ElementDefaultValueMemoryScope = FStructOnScope(ElementScriptStruct); FRigVMPinDefaultValueImportErrorContext ErrorPipe; ElementScriptStruct->ImportText(*ElementDefaultValue, ElementDefaultValueMemoryScope.GetStructMemory(), nullptr, PPF_None, &ErrorPipe, FString()); ElementDefaultValueMemory = ElementDefaultValueMemoryScope.GetStructMemory(); } (void)AddPin(InController, Index, *FString::FormatAsNumber(ElementIndex), InDirection, ElementTypeIndex, ElementDefaultValue, ElementDefaultValueMemory, InPreviousPinInfos); } } else if(UScriptStruct* ScriptStruct = Cast(Type.CPPTypeObject)) { const uint8* DefaultValueMemory = InDefaultValueMemory; FStructOnScope DefaultValueMemoryScope; if(DefaultValueMemory == nullptr) { FRigVMPinDefaultValueImportErrorContext ErrorPipe; DefaultValueMemoryScope = FStructOnScope(ScriptStruct); ScriptStruct->ImportText(*Pins[Index].DefaultValue, DefaultValueMemoryScope.GetStructMemory(), nullptr, PPF_None, &ErrorPipe, FString()); DefaultValueMemory = DefaultValueMemoryScope.GetStructMemory(); } AddPins(ScriptStruct, InController, InDirection, Index, DefaultValueMemory); } } if(InParentIndex != INDEX_NONE) { Pins[InParentIndex].SubPins.Add(Index); } return Index; } void URigVMController::FPinInfoArray::AddPins(UScriptStruct* InScriptStruct, URigVMController* InController, ERigVMPinDirection InDirection, int32 InParentIndex, const uint8* InDefaultValueMemory) { if (InController->ShouldStructBeUnfolded(InScriptStruct)) { TArray StructsToVisit = FRigVMTemplate::GetSuperStructs(InScriptStruct, true); for(UStruct* StructToVisit : StructsToVisit) { // using EFieldIterationFlags::None excludes the // properties of the super struct in this iterator. for (TFieldIterator It(StructToVisit, EFieldIterationFlags::None); It; ++It) { const uint8* DefaultValueMemory = nullptr; if(InDefaultValueMemory) { DefaultValueMemory = It->ContainerPtrToValuePtr(InDefaultValueMemory); } (void)AddPin(*It, InController, InDirection, InParentIndex, DefaultValueMemory); } } } } const FString& URigVMController::FPinInfoArray::GetPinPath(const int32 InIndex) const { if(!Pins.IsValidIndex(InIndex)) { static const FString EmptyString; return EmptyString; } if(Pins[InIndex].PinPath.IsEmpty()) { if(Pins[InIndex].ParentIndex == INDEX_NONE) { Pins[InIndex].PinPath = Pins[InIndex].Name.ToString(); } else { Pins[InIndex].PinPath = URigVMPin::JoinPinPath(GetPinPath(Pins[InIndex].ParentIndex), Pins[InIndex].Name.ToString()); } } return Pins[InIndex].PinPath; } int32 URigVMController::FPinInfoArray::GetIndexFromPinPath(const FString& InPinPath) const { if(PinPathLookup.Num() != Num()) { PinPathLookup.Reset(); for(int32 Index=0; Index < Num(); Index++) { PinPathLookup.Add(GetPinPath(Index), Index); } } if(const int32* Index = PinPathLookup.Find(InPinPath)) { return *Index; } return INDEX_NONE; } const URigVMController::FPinInfo* URigVMController::FPinInfoArray::GetPinFromPinPath(const FString& InPinPath) const { const int32 Index = GetIndexFromPinPath(InPinPath); if(Pins.IsValidIndex(Index)) { return &Pins[Index]; } return nullptr; } int32 URigVMController::FPinInfoArray::GetRootIndex(const int32 InIndex) const { if(Pins.IsValidIndex(InIndex)) { if(Pins[InIndex].ParentIndex == INDEX_NONE) { return InIndex; } return GetRootIndex(Pins[InIndex].ParentIndex); } return INDEX_NONE; } uint32 GetTypeHash(const URigVMController::FPinInfoArray& InPins) { TArray Hashes; Hashes.Reserve(InPins.Num()); uint32 OverAllHash = GetTypeHash(InPins.Num()); for(const URigVMController::FPinInfo& Info : InPins) { uint32 PinHash = GetTypeHash(Info); if(Info.ParentIndex != INDEX_NONE) { PinHash = HashCombine(PinHash, Hashes[Info.ParentIndex]); } Hashes.Add(PinHash); OverAllHash = HashCombine(OverAllHash, PinHash); } return OverAllHash; } void URigVMController::HandleModifiedEvent(ERigVMGraphNotifType InNotifType, URigVMGraph* InGraph, UObject* InSubject) { switch (InNotifType) { case ERigVMGraphNotifType::GraphChanged: case ERigVMGraphNotifType::NodeAdded: case ERigVMGraphNotifType::NodeRemoved: case ERigVMGraphNotifType::LinkAdded: case ERigVMGraphNotifType::LinkRemoved: case ERigVMGraphNotifType::PinArraySizeChanged: { if (InGraph) { InGraph->ClearAST(); } break; } case ERigVMGraphNotifType::PinDefaultValueChanged: { if (InGraph->RuntimeAST.IsValid()) { URigVMPin* RootPin = CastChecked(InSubject)->GetRootPin(); FRigVMASTProxy RootPinProxy = FRigVMASTProxy::MakeFromUObject(RootPin); const FRigVMExprAST* Expression = InGraph->GetRuntimeAST()->GetExprForSubject(RootPinProxy); if (Expression == nullptr) { InGraph->ClearAST(); break; } else if(Expression->NumParents() > 1) { InGraph->ClearAST(); break; } } break; } case ERigVMGraphNotifType::VariableAdded: case ERigVMGraphNotifType::VariableRemoved: case ERigVMGraphNotifType::VariableRemappingChanged: { URigVMGraph* RootGraph = InGraph->GetRootGraph(); if(URigVMFunctionLibrary* FunctionLibrary = Cast(RootGraph->GetRootGraph())) { URigVMNode* Node = CastChecked(InSubject); check(Node); if(URigVMLibraryNode* Function = FunctionLibrary->FindFunctionForNode(Node)) { FunctionLibrary->ForEachReference(Function->GetFName(), [this](URigVMFunctionReferenceNode* Reference) { FRigVMControllerGraphGuard GraphGuard(this, Reference->GetGraph(), false); Reference->GetGraph()->Notify(ERigVMGraphNotifType::VariableRemappingChanged, Reference); }); } } } } ModifiedEventStatic.Broadcast(InNotifType, InGraph, InSubject); if (ModifiedEventDynamic.IsBound()) { ModifiedEventDynamic.Broadcast(InNotifType, InGraph, InSubject); } } TArray URigVMController::GeneratePythonCommands() { TArray Commands; const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); // Add local variables for (const FRigVMGraphVariableDescription& Variable : GetGraph()->LocalVariables) { const FString VariableName = GetSanitizedVariableName(Variable.Name.ToString()); if (Variable.CPPTypeObject) { // FRigVMGraphVariableDescription AddLocalVariable(const FName& InVariableName, const FString& InCPPType, UObject* InCPPTypeObject, const FString& InDefaultValue, bool bSetupUndoRedo = true); Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_local_variable_from_object_path('%s', '%s', '%s', '%s')"), *GraphName, *VariableName, *Variable.CPPType, Variable.CPPTypeObject ? *Variable.CPPTypeObject->GetPathName() : TEXT(""), *Variable.DefaultValue)); } else { // FRigVMGraphVariableDescription AddLocalVariable(const FName& InVariableName, const FString& InCPPType, UObject* InCPPTypeObject, const FString& InDefaultValue, bool bSetupUndoRedo = true); Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_local_variable('%s', '%s', None, '%s')"), *GraphName, *VariableName, *Variable.CPPType, *Variable.DefaultValue)); } } // All nodes for (URigVMNode* Node : GetGraph()->GetNodes()) { Commands.Append(GetAddNodePythonCommands(Node)); } // All links for (URigVMLink* Link : GetGraph()->GetLinks()) { URigVMPin* SourcePin = Link->GetSourcePin(); URigVMPin* TargetPin = Link->GetTargetPin(); if (SourcePin->GetInjectedNodes().Num() > 0 || TargetPin->GetInjectedNodes().Num() > 0) { continue; } const FString SourcePinPath = GetSanitizedPinPath(SourcePin->GetPinPath()); const FString TargetPinPath = GetSanitizedPinPath(TargetPin->GetPinPath()); //bool AddLink(const FString& InOutputPinPath, const FString& InInputPinPath, bool bSetupUndoRedo = true); Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_link('%s', '%s')"), *GraphName, *SourcePinPath, *TargetPinPath)); } // Reroutes { TArray Reroutes; for (URigVMNode* Node : GetGraph()->GetNodes()) { if (URigVMRerouteNode* Reroute = Cast(Node)) { // SetRerouteCompactnessByName(const FName& InNodeName, bool bShowAsFullNode, bool bSetupUndoRedo = true); Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').set_reroute_compactness_by_name('%s', %s)"), *GraphName, *Reroute->GetName(), Reroute->GetShowsAsFullNode() ? TEXT("True") : TEXT("False"))); } } } return Commands; } TArray URigVMController::GetAddNodePythonCommands(URigVMNode* Node) const { TArray Commands; const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); const FString NodeName = GetSanitizedNodeName(Node->GetName()); auto GetResolveWildcardPinsPythonCommands = [](const FString& InGraphName, const URigVMTemplateNode* InNode, const FRigVMTemplate* InTemplate) { TArray Commands; // Lets minimize the number of commands by stopping when the number of permutations left is 1 (or less) TArray Permutations; Permutations.SetNumUninitialized(InTemplate->NumPermutations()); FRigVMTemplate::FTypeMap TypeMap; for (int32 ArgIndex = 0; ArgIndex < InTemplate->NumArguments(); ++ArgIndex) { if (Permutations.Num() < 2) { break; } const FRigVMTemplateArgument* Argument = InTemplate->GetArgument(ArgIndex); if (!Argument->IsSingleton()) { URigVMPin* Pin = InNode->FindPin(Argument->GetName().ToString()); if (!Pin->IsWildCard()) { Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').resolve_wild_card_pin('%s', '%s', '%s')"), *InGraphName, *Pin->GetPinPath(), *Pin->GetCPPType(), *Pin->GetCPPTypeObject()->GetPathName())); TypeMap.Add(Argument->GetName(), Pin->GetTypeIndex()); InTemplate->Resolve(TypeMap, Permutations, false); } } } return Commands; }; if (const URigVMUnitNode* UnitNode = Cast(Node)) { if (const URigVMInjectionInfo* InjectionInfo = Cast(UnitNode->GetOuter())) { const URigVMPin* InjectionInfoPin = InjectionInfo->GetPin(); const FString InjectionInfoPinPath = GetSanitizedPinPath(InjectionInfoPin->GetPinPath()); const FString InjectionInfoInputPinName = InjectionInfo->InputPin ? GetSanitizedPinName(InjectionInfo->InputPin->GetName()) : FString(); const FString InjectionInfoOutputPinName = InjectionInfo->OutputPin ? GetSanitizedPinName(InjectionInfo->OutputPin->GetName()) : FString(); //URigVMInjectionInfo* AddInjectedNodeFromStructPath(const FString& InPinPath, bool bAsInput, const FString& InScriptStructPath, const FName& InMethodName, const FName& InInputPinName, const FName& InOutputPinName, const FString& InNodeName = TEXT(""), bool bSetupUndoRedo = true); Commands.Add(FString::Printf(TEXT("%s_info = blueprint.get_controller_by_name('%s').add_injected_node_from_struct_path('%s', %s, '%s', '%s', '%s', '%s', '%s')"), *NodeName, *GraphName, *InjectionInfoPinPath, InjectionInfoPin->GetDirection() == ERigVMPinDirection::Input ? TEXT("True") : TEXT("False"), *UnitNode->GetScriptStruct()->GetPathName(), *UnitNode->GetMethodName().ToString(), *InjectionInfoInputPinName, *InjectionInfoOutputPinName, *UnitNode->GetName())); } else if (UnitNode->IsSingleton()) { // add_struct_node_from_struct_path(script_struct_path, method_name, position=[0.0, 0.0], node_name='', undo=True) Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_unit_node_from_struct_path('%s', 'Execute', %s, '%s')"), *GraphName, *UnitNode->GetScriptStruct()->GetPathName(), *RigVMPythonUtils::Vector2DToPythonString(UnitNode->GetPosition()), *NodeName)); } else { // add_template_node(notation, position=[0.0, 0.0], node_name='', undo=True) Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_template_node('%s', %s, '%s')"), *GraphName, *UnitNode->GetNotation().ToString(), *RigVMPythonUtils::Vector2DToPythonString(UnitNode->GetPosition()), *NodeName)); // Try to resolve wildcard pins if (const FRigVMTemplate* Template = UnitNode->GetTemplate()) { Commands.Append(GetResolveWildcardPinsPythonCommands(GraphName, UnitNode, Template)); } } } else if (const URigVMDispatchNode* DispatchNode = Cast(Node)) { // add_template_node(notation, position=[0.0, 0.0], node_name='', undo=True) Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_template_node('%s', %s, '%s')"), *GraphName, *DispatchNode->GetNotation().ToString(), *RigVMPythonUtils::Vector2DToPythonString(DispatchNode->GetPosition()), *NodeName)); // Try to resolve wildcard pins if (const FRigVMTemplate* Template = DispatchNode->GetTemplate()) { Commands.Append(GetResolveWildcardPinsPythonCommands(GraphName, DispatchNode, Template)); } } else if (const URigVMAggregateNode* AggregateNode = Cast(Node)) { TArray InnerNodeCommands = GetAddNodePythonCommands(AggregateNode->GetFirstInnerNode()); Commands.Append(InnerNodeCommands); // set_node_position(node_name, position) Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').set_node_position_by_name('%s', %s)"), *GraphName, *AggregateNode->GetName(), *RigVMPythonUtils::Vector2DToPythonString(AggregateNode->GetPosition()))); // add commands for any additional aggregate pin const TArray AggregatePins = AggregateNode->IsInputAggregate() ? AggregateNode->GetAggregateInputs() : AggregateNode->GetAggregateOutputs(); for(int32 Index = 2; Index < AggregatePins.Num(); Index++) { // add_aggregate_pin(node_name, pin_name) Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_aggregate_pin('%s', '%s')"), *GraphName, *AggregateNode->GetName(), *AggregatePins[Index]->GetName() )); } } else if (const URigVMVariableNode* VariableNode = Cast(Node)) { if (!VariableNode->IsInjected()) { const FString VariableName = GetSanitizedVariableName(VariableNode->GetVariableName().ToString()); // add_variable_node(variable_name, cpp_type, cpp_type_object, is_getter, default_value, position=[0.0, 0.0], node_name='', undo=True) if (VariableNode->GetVariableDescription().CPPTypeObject) { Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_variable_node_from_object_path('%s', '%s', '%s', %s, '%s', %s, '%s')"), *GraphName, *VariableName, *VariableNode->GetVariableDescription().CPPType, *VariableNode->GetVariableDescription().CPPTypeObject->GetPathName(), VariableNode->IsGetter() ? TEXT("True") : TEXT("False"), *VariableNode->GetVariableDescription().DefaultValue, *RigVMPythonUtils::Vector2DToPythonString(VariableNode->GetPosition()), *NodeName)); } else { Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_variable_node('%s', '%s', None, %s, '%s', %s, '%s')"), *GraphName, *VariableName, *VariableNode->GetVariableDescription().CPPType, VariableNode->IsGetter() ? TEXT("True") : TEXT("False"), *VariableNode->GetVariableDescription().DefaultValue, *RigVMPythonUtils::Vector2DToPythonString(VariableNode->GetPosition()), *NodeName)); } } } else if (const URigVMCommentNode* CommentNode = Cast(Node)) { // add_comment_node(comment_text, position=[0.0, 0.0], size=[400.0, 300.0], color=[0.0, 0.0, 0.0, 0.0], node_name='', undo=True) Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_comment_node('%s', %s, %s, %s, '%s')"), *GraphName, *CommentNode->GetCommentText().ReplaceCharWithEscapedChar(), *RigVMPythonUtils::Vector2DToPythonString(CommentNode->GetPosition()), *RigVMPythonUtils::Vector2DToPythonString(CommentNode->GetSize()), *RigVMPythonUtils::LinearColorToPythonString(CommentNode->GetNodeColor()), *NodeName)); } else if (const URigVMRerouteNode* RerouteNode = Cast(Node)) { // add_free_reroute_node(bool bShowAsFullNode, const FString& InCPPType, const FName& InCPPTypeObjectPath, bool bIsConstant, const FName& InCustomWidgetName, const FString& InDefaultValue, const FVector2D& InPosition = FVector2D::ZeroVector, const FString& InNodeName = TEXT(""), bool bSetupUndoRedo = true); Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_free_reroute_node(%s, '%s', '%s', %s, '%s', '%s', %s, '%s')"), *GraphName, RerouteNode->GetShowsAsFullNode() ? TEXT("True") : TEXT("False"), *RerouteNode->GetPins()[0]->GetCPPType(), *RerouteNode->GetPins()[0]->GetCPPTypeObject()->GetPathName(), RerouteNode->GetPins()[0]->IsDefinedAsConstant() ? TEXT("True") : TEXT("False"), *RerouteNode->GetPins()[0]->GetCustomWidgetName().ToString(), *RerouteNode->GetPins()[0]->GetDefaultValue(), *RigVMPythonUtils::Vector2DToPythonString(RerouteNode->GetPosition()), *NodeName)); } else if (const URigVMEnumNode* EnumNode = Cast(Node)) { // add_enum_node(cpp_type_object_path, position=[0.0, 0.0], node_name='', undo=True) Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_enum_node('%s', %s, '%s')"), *GraphName, *EnumNode->GetCPPTypeObject()->GetPathName(), *RigVMPythonUtils::Vector2DToPythonString(EnumNode->GetPosition()), *NodeName)); } else if (const URigVMFunctionReferenceNode* RefNode = Cast(Node)) { if (RefNode->GetReferencedFunctionHeader().LibraryPointer.HostObject == GetGraph()->GetDefaultFunctionLibrary()->GetFunctionHostObjectPath()) { Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_function_reference_node(function_%s, %s, '%s')"), *GraphName, *RigVMPythonUtils::PythonizeName(RefNode->LoadReferencedNode()->GetContainedGraph()->GetGraphName()), *RigVMPythonUtils::Vector2DToPythonString(RefNode->GetPosition()), *NodeName)); } else { Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_external_function_reference_node('%s', '%s', %s, '%s')"), *GraphName, *RefNode->GetReferencedFunctionHeader().LibraryPointer.HostObject.ToString(), *RefNode->GetReferencedFunctionHeader().Name.ToString(), *RigVMPythonUtils::Vector2DToPythonString(RefNode->GetPosition()), *NodeName)); } } else if (const URigVMCollapseNode* CollapseNode = Cast(Node)) { const FString ContainedGraphName = GetSanitizedGraphName(CollapseNode->GetContainedGraph()->GetGraphName()); // AddFunctionReferenceNode(URigVMLibraryNode* InFunctionDefinition, const FVector2D& InNodePosition = FVector2D::ZeroVector, const FString& InNodeName = TEXT(""), bool bSetupUndoRedo = true); Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_function_reference_node(function_%s, %s, '%s')"), *GraphName, *RigVMPythonUtils::PythonizeName(ContainedGraphName), *RigVMPythonUtils::Vector2DToPythonString(CollapseNode->GetPosition()), *NodeName)); Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').promote_function_reference_node_to_collapse_node('%s')"), *GraphName, *NodeName)); Commands.Add(FString::Printf(TEXT("library_controller.remove_function_from_library('%s')"), *ContainedGraphName)); } else if (const URigVMInvokeEntryNode* InvokeEntryNode = Cast(Node)) { // add_invoke_entry_node(entry_name, position=[0.0, 0.0], node_name='', undo=True) Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_invoke_entry_node('%s', %s, '%s')"), *GraphName, *InvokeEntryNode->GetEntryName().ToString(), *RigVMPythonUtils::Vector2DToPythonString(InvokeEntryNode->GetPosition()), *NodeName)); } else if (Node->IsA() || Node->IsA()) { } else { ensure(false); } if (!Commands.IsEmpty()) { for (const URigVMPin* Pin : Node->GetPins()) { if (Pin->GetDirection() == ERigVMPinDirection::Output || Pin->GetDirection() == ERigVMPinDirection::Hidden) { continue; } const FString DefaultValue = Pin->GetDefaultValue(); if (!DefaultValue.IsEmpty() && DefaultValue != TEXT("()")) { const FString PinPath = GetSanitizedPinPath(Pin->GetPinPath()); Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').set_pin_default_value('%s', '%s')"), *GraphName, *PinPath, *Pin->GetDefaultValue())); TArray SubPins = { Pin }; for (int32 i = 0; i < SubPins.Num(); ++i) { if (SubPins[i]->IsStruct() || SubPins[i]->IsArray()) { SubPins.Append(SubPins[i]->GetSubPins()); const FString SubPinPath = GetSanitizedPinPath(SubPins[i]->GetPinPath()); Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').set_pin_expansion('%s', %s)"), *GraphName, *SubPinPath, SubPins[i]->IsExpanded() ? TEXT("True") : TEXT("False"))); } } } if (!Pin->GetBoundVariablePath().IsEmpty()) { const FString PinPath = GetSanitizedPinPath(Pin->GetPinPath()); Commands.Add(FString::Printf(TEXT("blueprint.get_controller_by_name('%s').bind_pin_to_variable('%s', '%s')"), *GraphName, *PinPath, *Pin->GetBoundVariablePath())); } } } return Commands; } #if WITH_EDITOR URigVMUnitNode* URigVMController::AddUnitNode(UScriptStruct* InScriptStruct, const FName& InMethodName, const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if(!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } if (GetGraph()->IsA()) { ReportError(TEXT("Cannot add unit nodes to function library graphs.")); return nullptr; } if (InScriptStruct == nullptr) { ReportError(TEXT("InScriptStruct is null.")); return nullptr; } if (InMethodName == NAME_None) { ReportError(TEXT("InMethodName is None.")); return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); const FRigVMFunction* Function = FRigVMRegistry::Get().FindFunction(InScriptStruct, *InMethodName.ToString()); if (Function == nullptr) { ReportErrorf(TEXT("RIGVM_METHOD '%s::%s' cannot be found."), *InScriptStruct->GetStructCPPName(), *InMethodName.ToString()); return nullptr; } if(IRigVMClientHost* ClientHost = GetImplementingOuter()) { if(const FRigVMClient* Client = ClientHost->GetRigVMClient()) { if(!Function->SupportsExecuteContextStruct(Client->GetExecuteContextStruct())) { ReportErrorf(TEXT("Cannot add node for function '%s' - incompatible execute context: '%s' vs '%s'."), *Function->GetName(), *Function->GetExecuteContextStruct()->GetStructCPPName(), *Client->GetExecuteContextStruct()->GetStructCPPName()); return nullptr; } } } FString StructureError; if (!FRigVMStruct::ValidateStruct(InScriptStruct, &StructureError)) { ReportErrorf(TEXT("Failed to validate struct '%s': %s"), *InScriptStruct->GetName(), *StructureError); return nullptr; } if(const FRigVMTemplate* Template = Function->GetTemplate()) { if(bSetupUndoRedo) { OpenUndoBracket(FString::Printf(TEXT("Add %s Node"), *Template->GetName().ToString())); } const FString Name = GetValidNodeName(InNodeName.IsEmpty() ? InScriptStruct->GetName() : InNodeName); URigVMUnitNode* TemplateNode = Cast(AddTemplateNode(Template->GetNotation(), InPosition, Name, bSetupUndoRedo, bPrintPythonCommand)); if(TemplateNode == nullptr) { CancelUndoBracket(); return nullptr; } int32 PermutationIndex = Template->FindPermutation(Function); FRigVMTemplateTypeMap Types = Template->GetTypesForPermutation(PermutationIndex); for (TPair& Pair : Types) { if (URigVMPin* Pin = TemplateNode->FindPin(Pair.Key.ToString())) { if (Pin->IsWildCard()) { ResolveWildCardPin(Pin, Pair.Value, bSetupUndoRedo); } } if (!TemplateNode->HasWildCardPin()) { break; } } if (UnitNodeCreatedContext.IsValid()) { if (TSharedPtr StructScope = TemplateNode->ConstructStructInstance()) { TGuardValue NodeNameScope(UnitNodeCreatedContext.NodeName, TemplateNode->GetFName()); FRigVMStruct* StructInstance = (FRigVMStruct*)StructScope->GetStructMemory(); StructInstance->OnUnitNodeCreated(UnitNodeCreatedContext); } } if(bSetupUndoRedo) { CloseUndoBracket(); } return TemplateNode; } FStructOnScope StructOnScope(InScriptStruct); FRigVMStruct* StructMemory = (FRigVMStruct*)StructOnScope.GetStructMemory(); const bool bIsEventNode = (!StructMemory->GetEventName().IsNone()); if (bIsEventNode) { // don't allow event nodes in anything but top level graphs if (!Graph->IsTopLevelGraph()) { ReportAndNotifyError(TEXT("Event nodes can only be added to top level graphs.")); return nullptr; } if(StructMemory->CanOnlyExistOnce()) { // don't allow several event nodes in the main graph TObjectPtr EventNode = FindEventNode(InScriptStruct); if (EventNode != nullptr) { const FString ErrorMessage = FString::Printf(TEXT("Rig Graph can only contain one single %s node."), *InScriptStruct->GetDisplayNameText().ToString()); ReportAndNotifyError(ErrorMessage); return Cast(EventNode); } } } FString Name = GetValidNodeName(InNodeName.IsEmpty() ? InScriptStruct->GetName() : InNodeName); URigVMUnitNode* Node = NewObject(Graph, *Name); Node->ResolvedFunctionName = Function->GetName(); Node->Position = InPosition; Node->NodeTitle = InScriptStruct->GetMetaData(TEXT("DisplayName")); FString NodeColorMetadata; InScriptStruct->GetStringMetaDataHierarchical(*URigVMNode::NodeColorName, &NodeColorMetadata); if (!NodeColorMetadata.IsEmpty()) { Node->NodeColor = GetColorFromMetadata(NodeColorMetadata); } FString ExportedDefaultValue; CreateDefaultValueForStructIfRequired(InScriptStruct, ExportedDefaultValue); { TGuardValue SuspendNotifications(bSuspendNotifications, true); AddPinsForStruct(InScriptStruct, Node, nullptr, ERigVMPinDirection::Invalid, ExportedDefaultValue, true); } Graph->Nodes.Add(Node); if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } FRigVMControllerCompileBracketScope CompileScope(this); FRigVMAddUnitNodeAction Action; if (bSetupUndoRedo) { Action = FRigVMAddUnitNodeAction(Node); Action.Title = FString::Printf(TEXT("Add %s Node"), *Node->GetNodeTitle()); ActionStack->BeginAction(Action); } Notify(ERigVMGraphNotifType::NodeAdded, Node); if (UnitNodeCreatedContext.IsValid()) { if (TSharedPtr StructScope = Node->ConstructStructInstance()) { TGuardValue NodeNameScope(UnitNodeCreatedContext.NodeName, Node->GetFName()); FRigVMStruct* StructInstance = (FRigVMStruct*)StructScope->GetStructMemory(); StructInstance->OnUnitNodeCreated(UnitNodeCreatedContext); } } if (bSetupUndoRedo) { ActionStack->EndAction(Action); } if (bPrintPythonCommand) { TArray Commands = GetAddNodePythonCommands(Node); for (const FString& Command : Commands) { RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("%s"), *Command)); } } return Node; } URigVMUnitNode* URigVMController::AddUnitNodeFromStructPath(const FString& InScriptStructPath, const FName& InMethodName, const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if(!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } UScriptStruct* ScriptStruct = RigVMTypeUtils::FindObjectFromCPPTypeObjectPath(InScriptStructPath); if (ScriptStruct == nullptr) { ReportErrorf(TEXT("Cannot find struct for path '%s'."), *InScriptStructPath); return nullptr; } return AddUnitNode(ScriptStruct, InMethodName, InPosition, InNodeName, bSetupUndoRedo, bPrintPythonCommand); } URigVMUnitNode* URigVMController::AddUnitNodeWithDefaults(UScriptStruct* InScriptStruct, const FString& InDefaults, const FName& InMethodName, const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if(InScriptStruct == nullptr) { return nullptr; } FStructOnScope StructOnScope; if(!InDefaults.IsEmpty()) { StructOnScope = FStructOnScope(InScriptStruct); FRigVMPinDefaultValueImportErrorContext ErrorPipe; InScriptStruct->ImportText(*InDefaults, StructOnScope.GetStructMemory(), nullptr, PPF_None, &ErrorPipe, FString()); if(ErrorPipe.NumErrors > 0) { return nullptr; } } return AddUnitNodeWithDefaults(InScriptStruct, StructOnScope, InMethodName, InPosition, InNodeName, bSetupUndoRedo, bPrintPythonCommand); } URigVMUnitNode* URigVMController::AddUnitNodeWithDefaults(UScriptStruct* InScriptStruct, const FRigStructScope& InDefaults, const FName& InMethodName, const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if(InScriptStruct == nullptr) { return nullptr; } const bool bSetPinDefaults = InDefaults.IsValid() && (InDefaults.GetScriptStruct() == InScriptStruct); if(bSetPinDefaults) { static constexpr TCHAR AddUnitNodeTitle[] = TEXT("Add Unit Node"); OpenUndoBracket(AddUnitNodeTitle); } URigVMUnitNode* Node = AddUnitNode(InScriptStruct, InMethodName, InPosition, InNodeName, bSetupUndoRedo, bPrintPythonCommand); if(Node == nullptr) { if(bSetPinDefaults) { CancelUndoBracket(); } return nullptr; } if(bSetPinDefaults) { if(!SetUnitNodeDefaults(Node, InDefaults)) { CancelUndoBracket(); } } CloseUndoBracket(); return Node; } bool URigVMController::SetUnitNodeDefaults(URigVMUnitNode* InNode, const FString& InDefaults, bool bSetupUndoRedo, bool bPrintPythonCommand) { if(InNode == nullptr) { return false; } UScriptStruct* ScriptStruct = InNode->GetScriptStruct(); if(ScriptStruct == nullptr) { return false; } FStructOnScope StructOnScope(ScriptStruct); FRigVMPinDefaultValueImportErrorContext ErrorPipe; ScriptStruct->ImportText(*InDefaults, StructOnScope.GetStructMemory(), nullptr, PPF_None, &ErrorPipe, FString()); if(ErrorPipe.NumErrors > 0) { return false; } return SetUnitNodeDefaults(InNode, StructOnScope, bSetupUndoRedo, bPrintPythonCommand); } bool URigVMController::SetUnitNodeDefaults(URigVMUnitNode* InNode, const FRigStructScope& InDefaults, bool bSetupUndoRedo, bool bPrintPythonCommand) { if(InNode == nullptr || !InDefaults.IsValid()) { return false; } if(InNode->GetScriptStruct() != InDefaults.GetScriptStruct()) { return false; } static constexpr TCHAR SetUnitNodeDefaultsTitle[] = TEXT("Set Unit Node Defaults"); OpenUndoBracket(SetUnitNodeDefaultsTitle); for(URigVMPin* Pin : InNode->GetPins()) { if(Pin->GetDirection() != ERigVMPinDirection::Input && Pin->GetDirection() != ERigVMPinDirection::IO && Pin->GetDirection() != ERigVMPinDirection::Visible) { continue; } if(const FProperty* Property = InDefaults.GetScriptStruct()->FindPropertyByName(Pin->GetFName())) { const uint8* MemberMemoryPtr = Property->ContainerPtrToValuePtr(InDefaults.GetMemory()); const FString NewDefault = FRigVMStruct::ExportToFullyQualifiedText(Property, MemberMemoryPtr); if(NewDefault != Pin->GetDefaultValue()) { SetPinDefaultValue(Pin->GetPinPath(), NewDefault, true, bSetupUndoRedo, false, bPrintPythonCommand); } } } CloseUndoBracket(); return true; } URigVMVariableNode* URigVMController::AddVariableNode(const FName& InVariableName, const FString& InCPPType, UObject* InCPPTypeObject, bool bIsGetter, const FString& InDefaultValue, const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); if (Graph->IsA()) { ReportError(TEXT("Cannot add variables nodes to function library graphs.")); return nullptr; } // check if the operation will cause to dirty assets if(bSetupUndoRedo) { if(URigVMFunctionLibrary* OuterLibrary = Graph->GetTypedOuter()) { if(URigVMLibraryNode* OuterFunction = OuterLibrary->FindFunctionForNode(Graph->GetTypedOuter())) { // Make sure there is no local variable with that name bool bFoundLocalVariable = false; for (FRigVMGraphVariableDescription& LocalVariable : OuterFunction->GetContainedGraph()->LocalVariables) { if (LocalVariable.Name == InVariableName) { bFoundLocalVariable = true; break; } } if (!bFoundLocalVariable) { // Make sure there is no external variable with that name TArray ExternalVariables = OuterFunction->GetContainedGraph()->GetExternalVariables(); bool bFoundExternalVariable = false; for(const FRigVMExternalVariable& ExternalVariable : ExternalVariables) { if(ExternalVariable.Name == InVariableName) { bFoundExternalVariable = true; break; } } if(!bFoundExternalVariable) { // Warn the user the changes are not undoable if(RequestBulkEditDialogDelegate.IsBound()) { FRigVMController_BulkEditResult Result = RequestBulkEditDialogDelegate.Execute(OuterFunction, ERigVMControllerBulkEditType::AddVariable); if(Result.bCanceled) { return nullptr; } bSetupUndoRedo = Result.bSetupUndoRedo; } } } } } } if (InCPPTypeObject == nullptr) { InCPPTypeObject = URigVMCompiler::GetScriptStructForCPPType(InCPPType); } if (InCPPTypeObject == nullptr) { InCPPTypeObject = RigVMTypeUtils::FindObjectFromCPPTypeObjectPath(InCPPType); } FString CPPType = RigVMTypeUtils::PostProcessCPPType(InCPPType, InCPPTypeObject); FString Name = GetValidNodeName(InNodeName.IsEmpty() ? FString(TEXT("VariableNode")) : InNodeName); URigVMVariableNode* Node = NewObject(Graph, *Name); Node->Position = InPosition; if (!bIsGetter) { URigVMPin* ExecutePin = MakeExecutePin(Node, FRigVMStruct::ExecuteContextName); ExecutePin->Direction = ERigVMPinDirection::IO; AddNodePin(Node, ExecutePin); } URigVMPin* VariablePin = NewObject(Node, *URigVMVariableNode::VariableName); VariablePin->CPPType = RigVMTypeUtils::FNameType; VariablePin->Direction = ERigVMPinDirection::Hidden; VariablePin->DefaultValue = InVariableName.ToString(); VariablePin->CustomWidgetName = TEXT("VariableName"); AddNodePin(Node, VariablePin); URigVMPin* ValuePin = NewObject(Node, *URigVMVariableNode::ValueName); FRigVMExternalVariable ExternalVariable = GetVariableByName(InVariableName); if(ExternalVariable.IsValid(true)) { ValuePin->CPPType = ExternalVariable.TypeName.ToString(); ValuePin->CPPTypeObject = ExternalVariable.TypeObject; if (ValuePin->CPPTypeObject) { ValuePin->CPPTypeObjectPath = *ValuePin->CPPTypeObject->GetPathName(); } ValuePin->bIsDynamicArray = ExternalVariable.bIsArray; if(ValuePin->bIsDynamicArray && !RigVMTypeUtils::IsArrayType(ValuePin->CPPType)) { ValuePin->CPPType = RigVMTypeUtils::ArrayTypeFromBaseType(*ValuePin->CPPType); } } else { ValuePin->CPPType = CPPType; if (UClass* Class = Cast(InCPPTypeObject)) { ValuePin->CPPTypeObject = Class; ValuePin->CPPTypeObjectPath = *ValuePin->CPPTypeObject->GetPathName(); } else if (UScriptStruct* ScriptStruct = Cast(InCPPTypeObject)) { ValuePin->CPPTypeObject = ScriptStruct; ValuePin->CPPTypeObjectPath = *ValuePin->CPPTypeObject->GetPathName(); } else if (UEnum* Enum = Cast(InCPPTypeObject)) { ValuePin->CPPTypeObject = Enum; ValuePin->CPPTypeObjectPath = *ValuePin->CPPTypeObject->GetPathName(); } } ValuePin->Direction = bIsGetter ? ERigVMPinDirection::Output : ERigVMPinDirection::Input; AddNodePin(Node, ValuePin); Graph->Nodes.Add(Node); if (ValuePin->IsStruct()) { FString DefaultValue = InDefaultValue; CreateDefaultValueForStructIfRequired(ValuePin->GetScriptStruct(), DefaultValue); { TGuardValue SuspendNotifications(bSuspendNotifications, true); AddPinsForStruct(ValuePin->GetScriptStruct(), Node, ValuePin, ValuePin->Direction, DefaultValue, false); } } else if (!InDefaultValue.IsEmpty() && InDefaultValue != TEXT("()")) { SetPinDefaultValue(ValuePin, InDefaultValue, true, false, false); } ForEveryPinRecursively(Node, [](URigVMPin* Pin) { Pin->bIsExpanded = false; }); if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } FRigVMControllerCompileBracketScope CompileScope(this); FRigVMAddVariableNodeAction Action; if (bSetupUndoRedo) { Action = FRigVMAddVariableNodeAction(Node); Action.Title = FString::Printf(TEXT("Add %s Variable"), *InVariableName.ToString()); ActionStack->BeginAction(Action); } Notify(ERigVMGraphNotifType::NodeAdded, Node); Notify(ERigVMGraphNotifType::VariableAdded, Node); if (bSetupUndoRedo) { ActionStack->EndAction(Action); } if (bPrintPythonCommand) { TArray Commands = GetAddNodePythonCommands(Node); for (const FString& Command : Commands) { RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("%s"), *Command)); } } return Node; } URigVMVariableNode* URigVMController::AddVariableNodeFromObjectPath(const FName& InVariableName, const FString& InCPPType, const FString& InCPPTypeObjectPath, bool bIsGetter, const FString& InDefaultValue, const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } UObject* CPPTypeObject = nullptr; if (!InCPPTypeObjectPath.IsEmpty()) { CPPTypeObject = RigVMTypeUtils::FindObjectFromCPPTypeObjectPath(InCPPTypeObjectPath); if (CPPTypeObject == nullptr) { ReportErrorf(TEXT("Cannot find cpp type object for path '%s'."), *InCPPTypeObjectPath); return nullptr; } } return AddVariableNode(InVariableName, InCPPType, CPPTypeObject, bIsGetter, InDefaultValue, InPosition, InNodeName, bSetupUndoRedo, bPrintPythonCommand); } void URigVMController::RefreshVariableNode(const FName& InNodeName, const FName& InVariableName, const FString& InCPPType, UObject* InCPPTypeObject, bool bSetupUndoRedo, bool bSetupOrphanPins) { if (!IsValidGraph()) { return; } if (!bIsTransacting && !IsGraphEditable()) { return; } URigVMGraph* Graph = GetGraph(); check(Graph); if (URigVMVariableNode* VariableNode = Cast(Graph->FindNodeByName(InNodeName))) { if (URigVMPin* VariablePin = VariableNode->FindPin(URigVMVariableNode::VariableName)) { if (VariablePin->Direction == ERigVMPinDirection::Visible) { if (bSetupUndoRedo) { VariablePin->Modify(); } VariablePin->Direction = ERigVMPinDirection::Hidden; Notify(ERigVMGraphNotifType::PinDirectionChanged, VariablePin); } if (InVariableName.IsValid() && VariablePin->DefaultValue != InVariableName.ToString()) { SetPinDefaultValue(VariablePin, InVariableName.ToString(), false, bSetupUndoRedo, false); Notify(ERigVMGraphNotifType::PinDefaultValueChanged, VariablePin); Notify(ERigVMGraphNotifType::VariableRenamed, VariableNode); } if (!InCPPType.IsEmpty()) { if (URigVMPin* ValuePin = VariableNode->FindPin(URigVMVariableNode::ValueName)) { if (ValuePin->CPPType != InCPPType || ValuePin->GetCPPTypeObject() != InCPPTypeObject) { if (bSetupUndoRedo) { ValuePin->Modify(); } // if this is an unsupported datatype... if (InCPPType == FName(NAME_None).ToString()) { RemoveNode(VariableNode, bSetupUndoRedo); return; } FString CPPTypeObjectPath; if(InCPPTypeObject) { CPPTypeObjectPath = InCPPTypeObject->GetPathName(); } ChangePinType(ValuePin, InCPPType, *CPPTypeObjectPath, bSetupUndoRedo, bSetupOrphanPins); } } } } } } void URigVMController::OnExternalVariableRemoved(const FName& InVarName, bool bSetupUndoRedo) { if (!IsValidGraph()) { return; } if (!bIsTransacting && !IsGraphEditable()) { return; } if (!InVarName.IsValid()) { return; } URigVMGraph* Graph = GetGraph(); check(Graph); // When transacting, the action stack will deal with the deletion of variable nodes if(GIsTransacting) { return; } for (const FRigVMGraphVariableDescription& LocalVariable : Graph->GetLocalVariables(true)) { if (InVarName == LocalVariable.Name) { return; } } const FString VarNameStr = InVarName.ToString(); FRigVMControllerCompileBracketScope CompileScope(this); if (bSetupUndoRedo) { OpenUndoBracket(TEXT("Remove Variable Nodes")); } TArray Nodes = Graph->GetNodes(); for (URigVMNode* Node : Nodes) { if (URigVMVariableNode* VariableNode = Cast(Node)) { if (URigVMPin* VariablePin = VariableNode->FindPin(URigVMVariableNode::VariableName)) { if (VariablePin->GetDefaultValue() == VarNameStr) { RemoveNode(Node, bSetupUndoRedo, true); continue; } } } else if(URigVMCollapseNode* CollapseNode = Cast(Node)) { FRigVMControllerGraphGuard GraphGuard(this, CollapseNode->GetContainedGraph(), bSetupUndoRedo); TGuardValue GuardEditGraph(CollapseNode->ContainedGraph->bEditable, true); // call this function for the contained graph recursively OnExternalVariableRemoved(InVarName, bSetupUndoRedo); // if we are a function we need to notify all references! if(URigVMFunctionLibrary* FunctionLibrary = Cast(Graph)) { FunctionLibrary->ForEachReference(CollapseNode->GetFName(), [this, InVarName](URigVMFunctionReferenceNode* Reference) { if(Reference->VariableMap.Contains(InVarName)) { Reference->Modify(); Reference->VariableMap.Remove(InVarName); FRigVMControllerGraphGuard GraphGuard(this, Reference->GetGraph(), false); Notify(ERigVMGraphNotifType::VariableRemappingChanged, Reference); } }); } } else if(URigVMFunctionReferenceNode* FunctionReferenceNode = Cast(Node)) { TMap VariableMap = FunctionReferenceNode->GetVariableMap(); for(const TPair& VariablePair : VariableMap) { if(VariablePair.Value == InVarName) { SetRemappedVariable(FunctionReferenceNode, VariablePair.Key, NAME_None, bSetupUndoRedo); } } } } if (bSetupUndoRedo) { CloseUndoBracket(); } } bool URigVMController::OnExternalVariableRenamed(const FName& InOldVarName, const FName& InNewVarName, bool bSetupUndoRedo) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } if (!InOldVarName.IsValid() || !InNewVarName.IsValid()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); for (const FRigVMGraphVariableDescription& LocalVariable : Graph->GetLocalVariables(true)) { if (InOldVarName == LocalVariable.Name) { return false; } } const FString VarNameStr = InOldVarName.ToString(); FRigVMControllerCompileBracketScope CompileScope(this); if (bSetupUndoRedo) { OpenUndoBracket(TEXT("Rename Variable Nodes")); } TArray Nodes = Graph->GetNodes(); for (URigVMNode* Node : Nodes) { if (URigVMVariableNode* VariableNode = Cast(Node)) { if (URigVMPin* VariablePin = VariableNode->FindPin(URigVMVariableNode::VariableName)) { if (VariablePin->GetDefaultValue() == VarNameStr) { RefreshVariableNode(Node->GetFName(), InNewVarName, FString(), nullptr, bSetupUndoRedo, false); continue; } } } else if(URigVMCollapseNode* CollapseNode = Cast(Node)) { FRigVMControllerGraphGuard GraphGuard(this, CollapseNode->GetContainedGraph(), bSetupUndoRedo); TGuardValue GuardEditGraph(CollapseNode->ContainedGraph->bEditable, true); OnExternalVariableRenamed(InOldVarName, InNewVarName, bSetupUndoRedo); // if we are a function we need to notify all references! if(URigVMFunctionLibrary* FunctionLibrary = Cast(Graph)) { FunctionLibrary->ForEachReference(CollapseNode->GetFName(), [this, InOldVarName, InNewVarName](URigVMFunctionReferenceNode* Reference) { if(Reference->VariableMap.Contains(InOldVarName)) { Reference->Modify(); FName MappedVariable = Reference->VariableMap.FindChecked(InOldVarName); Reference->VariableMap.Remove(InOldVarName); Reference->VariableMap.FindOrAdd(InNewVarName) = MappedVariable; FRigVMControllerGraphGuard GraphGuard(this, Reference->GetGraph(), false); Notify(ERigVMGraphNotifType::VariableRemappingChanged, Reference); } }); } } else if(URigVMFunctionReferenceNode* FunctionReferenceNode = Cast(Node)) { TMap VariableMap = FunctionReferenceNode->GetVariableMap(); for(const TPair& VariablePair : VariableMap) { if(VariablePair.Value == InOldVarName) { SetRemappedVariable(FunctionReferenceNode, VariablePair.Key, InNewVarName, bSetupUndoRedo); } } } } if (bSetupUndoRedo) { CloseUndoBracket(); } return true; } void URigVMController::OnExternalVariableTypeChanged(const FName& InVarName, const FString& InCPPType, UObject* InCPPTypeObject, bool bSetupUndoRedo) { if (!IsValidGraph()) { return; } if (!bIsTransacting && !IsGraphEditable()) { return; } if (!InVarName.IsValid()) { return; } URigVMGraph* Graph = GetGraph(); check(Graph); for (const FRigVMGraphVariableDescription& LocalVariable : Graph->GetLocalVariables(true)) { if (InVarName == LocalVariable.Name) { return; } } const FString VarNameStr = InVarName.ToString(); FRigVMControllerCompileBracketScope CompileScope(this); if (bSetupUndoRedo) { OpenUndoBracket(TEXT("Change Variable Nodes Type")); } TArray Nodes = Graph->GetNodes(); for (URigVMNode* Node : Nodes) { if (URigVMVariableNode* VariableNode = Cast(Node)) { if (URigVMPin* VariablePin = VariableNode->FindPin(URigVMVariableNode::VariableName)) { if (VariablePin->GetDefaultValue() == VarNameStr) { RefreshVariableNode(Node->GetFName(), InVarName, InCPPType, InCPPTypeObject, bSetupUndoRedo, false); continue; } } } else if(URigVMCollapseNode* CollapseNode = Cast(Node)) { FRigVMControllerGraphGuard GraphGuard(this, CollapseNode->GetContainedGraph(), bSetupUndoRedo); TGuardValue GuardEditGraph(CollapseNode->ContainedGraph->bEditable, true); OnExternalVariableTypeChanged(InVarName, InCPPType, InCPPTypeObject, bSetupUndoRedo); // if we are a function we need to notify all references! if(URigVMFunctionLibrary* FunctionLibrary = Cast(Graph)) { FunctionLibrary->ForEachReference(CollapseNode->GetFName(), [this, InVarName](URigVMFunctionReferenceNode* Reference) { if(Reference->VariableMap.Contains(InVarName)) { Reference->Modify(); Reference->VariableMap.Remove(InVarName); FRigVMControllerGraphGuard GraphGuard(this, Reference->GetGraph(), false); Notify(ERigVMGraphNotifType::VariableRemappingChanged, Reference); } }); } } else if(URigVMFunctionReferenceNode* FunctionReferenceNode = Cast(Node)) { TMap VariableMap = FunctionReferenceNode->GetVariableMap(); for(const TPair& VariablePair : VariableMap) { if(VariablePair.Value == InVarName) { SetRemappedVariable(FunctionReferenceNode, VariablePair.Key, NAME_None, bSetupUndoRedo); } } } TArray AllPins = Node->GetAllPinsRecursively(); for (URigVMPin* Pin : AllPins) { if (Pin->GetBoundVariableName() == InVarName.ToString()) { FString BoundVariablePath = Pin->GetBoundVariablePath(); UnbindPinFromVariable(Pin, bSetupUndoRedo); // try to bind it again - maybe it can be bound (due to cast rules etc) BindPinToVariable(Pin, BoundVariablePath, bSetupUndoRedo); } } } if (bSetupUndoRedo) { CloseUndoBracket(); } } void URigVMController::OnExternalVariableTypeChangedFromObjectPath(const FName& InVarName, const FString& InCPPType, const FString& InCPPTypeObjectPath, bool bSetupUndoRedo) { if (!IsValidGraph()) { return; } if (!bIsTransacting && !IsGraphEditable()) { return; } UObject* CPPTypeObject = nullptr; if (!InCPPTypeObjectPath.IsEmpty()) { CPPTypeObject = RigVMTypeUtils::FindObjectFromCPPTypeObjectPath(InCPPTypeObjectPath); if (CPPTypeObject == nullptr) { ReportErrorf(TEXT("Cannot find cpp type object for path '%s'."), *InCPPTypeObjectPath); return; } } OnExternalVariableTypeChanged(InVarName, InCPPType, CPPTypeObject, bSetupUndoRedo); } URigVMVariableNode* URigVMController::ReplaceParameterNodeWithVariable(const FName& InNodeName, const FName& InVariableName, const FString& InCPPType, UObject* InCPPTypeObject, bool bSetupUndoRedo) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); if (URigVMParameterNode* ParameterNode = Cast(Graph->FindNodeByName(InNodeName))) { URigVMPin* ParameterValuePin = ParameterNode->FindPin(URigVMParameterNode::ValueName); check(ParameterValuePin); FRigVMGraphParameterDescription Description = ParameterNode->GetParameterDescription(); URigVMVariableNode* VariableNode = AddVariableNode( InVariableName, InCPPType, InCPPTypeObject, ParameterValuePin->GetDirection() == ERigVMPinDirection::Output, ParameterValuePin->GetDefaultValue(), ParameterNode->GetPosition(), FString(), bSetupUndoRedo); if (VariableNode) { URigVMPin* VariableValuePin = VariableNode->FindPin(URigVMVariableNode::ValueName); RewireLinks( ParameterValuePin, VariableValuePin, ParameterValuePin->GetDirection() == ERigVMPinDirection::Input, bSetupUndoRedo ); RemoveNode(ParameterNode, bSetupUndoRedo, true); return VariableNode; } } return nullptr; } bool URigVMController::UnresolveTemplateNodes(const TArray& InNodeNames, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } TArray Nodes; for (const FName& NodeName : InNodeNames) { if (URigVMNode* Node = GetGraph()->FindNodeByName(NodeName)) { Nodes.Add(Node); } } if(UnresolveTemplateNodes(Nodes, bSetupUndoRedo)) { if(bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); TArray NodeNames; for(const FName& NodeName : InNodeNames) { NodeNames.Add(GetSanitizedNodeName(NodeName.ToString())); } const FString NodeNamesJoined = FString::Join(NodeNames, TEXT("','")); // UnresolveTemplateNodes(const TArray& InNodeNames) RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').unresolve_template_nodes(['%s'])"), *GraphName, *NodeNamesJoined)); } return true; } return false; } bool URigVMController::UnresolveTemplateNodes(const TArray& InNodes, bool bSetupUndoRedo) { if (!IsValidGraph() || InNodes.IsEmpty()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } // check if any of the nodes needs to be unresolved const bool bHasNodeToResolve = InNodes.ContainsByPredicate( [](const URigVMNode* Node) -> bool { if (const URigVMTemplateNode* TemplateNode = Cast(Node)) { return !TemplateNode->IsFullyUnresolved(); } return false; }); if (!bHasNodeToResolve) { return false; } FRigVMBaseAction Action; if(bSetupUndoRedo) { Action.Title = TEXT("Unresolve nodes"); ActionStack->BeginAction(Action); } { const FRigVMRegistry& Registry = FRigVMRegistry::Get(); for (URigVMNode* Node : InNodes) { EjectAllInjectedNodes(Node, bSetupUndoRedo); TArray Links = Node->GetLinks(); for (int32 i=0; iGetSourcePin(); URigVMPin* TargetPin = Link->GetTargetPin(); URigVMNode* OtherNode = SourcePin->GetNode() == Node ? TargetPin->GetNode() : SourcePin->GetNode(); if (!InNodes.Contains(OtherNode)) { const URigVMPin* PinOnNode = SourcePin->GetNode() == Node ? SourcePin : TargetPin; if(PinOnNode->IsExecuteContext()) { continue; } if (const URigVMTemplateNode* TemplateNode = Cast(Node)) { if(const FRigVMTemplate* Template = TemplateNode->GetTemplate()) { const URigVMPin* RootPin = PinOnNode->GetRootPin(); if(const FRigVMTemplateArgument* Argument = Template->FindArgument(RootPin->GetFName())) { if(Argument->IsSingleton()) { continue; } } } } BreakLink(SourcePin, TargetPin, bSetupUndoRedo); } } if (URigVMTemplateNode* TemplateNode = Cast(Node)) { TemplateNode->InvalidateCache(); TemplateNode->ResolvedFunctionName.Reset(); TemplateNode->ResolvedPermutation = INDEX_NONE; if (const FRigVMTemplate* Template = TemplateNode->GetTemplate()) { for (int32 i=0; iNumArguments(); ++i) { const FRigVMTemplateArgument* Argument = Template->GetArgument(i); if (!Argument->IsSingleton()) { if (URigVMPin* Pin = TemplateNode->FindPin(Argument->Name.ToString())) { TRigVMTypeIndex OldTypeIndex = Pin->GetTypeIndex(); TRigVMTypeIndex NewTypeIndex = RigVMTypeUtils::TypeIndex::WildCard; while (Registry.IsArrayType(OldTypeIndex)) { OldTypeIndex = Registry.GetBaseTypeFromArrayTypeIndex(OldTypeIndex); NewTypeIndex = Registry.GetArrayTypeFromBaseTypeIndex(NewTypeIndex); } ChangePinType(Pin, NewTypeIndex, bSetupUndoRedo, false, true, false); } } } UpdateTemplateNodePinTypes(TemplateNode, bSetupUndoRedo); } } } } if(bSetupUndoRedo) { ActionStack->EndAction(Action); } return true; } TArray URigVMController::UpgradeNodes(const TArray& InNodeNames, bool bRecursive, bool bSetupUndoRedo, bool bPrintPythonCommand) { TArray Nodes; if (!IsValidGraph()) { return Nodes; } if (!bIsTransacting && !IsGraphEditable()) { return Nodes; } for (const FName& NodeName : InNodeNames) { if (URigVMNode* Node = GetGraph()->FindNodeByName(NodeName)) { Nodes.Add(Node); } } Nodes = UpgradeNodes(Nodes, bRecursive, bSetupUndoRedo); if(bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); TArray NodeNames; for(const FName& NodeName : InNodeNames) { NodeNames.Add(GetSanitizedNodeName(NodeName.ToString())); } const FString NodeNamesJoined = FString::Join(NodeNames, TEXT("','")); // UpgradeNodes(const TArray& InNodeNames) RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').upgrade_nodes(['%s'])"), *GraphName, *NodeNamesJoined)); } // log a warning for all nodes which are still marked deprecated for(URigVMNode* Node : Nodes) { if(URigVMUnitNode* UnitNode = Cast(Node)) { if(UnitNode->IsDeprecated()) { ReportWarningf(TEXT("Node %s cannot be upgraded. There is no automatic upgrade path available."), *UnitNode->GetNodePath()); } } } return Nodes; } TArray URigVMController::UpgradeNodes(const TArray& InNodes, bool bRecursive, bool bSetupUndoRedo) { if (!IsValidGraph()) { return TArray(); } if (!bIsTransacting && !IsGraphEditable()) { return TArray(); } bool bFoundAnyNodeToUpgrade = false; for(URigVMNode* Node : InNodes) { if(!IsValidNodeForGraph(Node)) { return TArray(); } bFoundAnyNodeToUpgrade |= Node->CanBeUpgraded(); } if(!bFoundAnyNodeToUpgrade) { return InNodes; } FRigVMBaseAction Action; if(bSetupUndoRedo) { Action.Title = TEXT("Upgrade nodes"); ActionStack->BeginAction(Action); } // find all links affecting the nodes to upgrade TArray> LinkedPaths = GetLinkedPinPaths(InNodes); if(!BreakLinkedPaths(LinkedPaths, bSetupUndoRedo)) { if(bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return TArray(); } TArray UpgradedNodes; TMap RemapPinDelegates; for(URigVMNode* Node : InNodes) { FRigVMController_PinPathRemapDelegate RemapPinDelegate; URigVMNode* UpgradedNode = UpgradeNode(Node, bSetupUndoRedo, &RemapPinDelegate); if(UpgradedNode) { UpgradedNodes.Add(UpgradedNode); if(RemapPinDelegate.IsBound()) { RemapPinDelegates.Add(UpgradedNode->GetName(), RemapPinDelegate); } } } RestoreLinkedPaths(LinkedPaths, {}, RemapPinDelegates, bSetupUndoRedo); if(bRecursive) { UpgradedNodes = UpgradeNodes(UpgradedNodes, bRecursive, bSetupUndoRedo); } if(bSetupUndoRedo) { ActionStack->EndAction(Action); } return UpgradedNodes; } URigVMNode* URigVMController::UpgradeNode(URigVMNode* InNode, bool bSetupUndoRedo, FRigVMController_PinPathRemapDelegate* OutRemapPinDelegate) { if(!IsValidNodeForGraph(InNode)) { return nullptr; } if(!InNode->CanBeUpgraded()) { return InNode; } TMap RedirectedPinPaths; TMap PinStates = GetPinStates(InNode, true); EjectAllInjectedNodes(InNode, bSetupUndoRedo); const FString NodeName = InNode->GetName(); const FVector2D NodePosition = InNode->GetPosition(); FRigVMBaseAction Action; if(bSetupUndoRedo) { Action.Title = TEXT("Upgrade node"); ActionStack->BeginAction(Action); } URigVMNode* UpgradedNode = nullptr; const FRigVMStructUpgradeInfo UpgradeInfo = InNode->GetUpgradeInfo(); check(UpgradeInfo.IsValid()); FName MethodName = TEXT("Execute"); if(URigVMUnitNode* UnitNode = Cast(InNode)) { MethodName = UnitNode->GetMethodName(); } if(OutRemapPinDelegate) { *OutRemapPinDelegate = FRigVMController_PinPathRemapDelegate::CreateLambda([UpgradeInfo](const FString& InPinPath, bool bIsInput) -> FString { return UpgradeInfo.RemapPin(InPinPath, bIsInput, true); }); } if(!RemoveNode(InNode, bSetupUndoRedo, true, false, false)) { if(bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } ReportErrorf(TEXT("Unable to remove node %s."), *NodeName); return nullptr; } URigVMNode* NewNode = nullptr; if(UpgradeInfo.GetNewStruct()->IsChildOf(FRigVMStruct::StaticStruct())) { NewNode = AddUnitNode(UpgradeInfo.GetNewStruct(), MethodName, NodePosition, NodeName, bSetupUndoRedo, false); } else if(UpgradeInfo.GetNewStruct()->IsChildOf(FRigVMDispatchFactory::StaticStruct()) && !UpgradeInfo.NewDispatchFunction.IsNone()) { if(const FRigVMFunction* Function = FRigVMRegistry::Get().FindFunction(*UpgradeInfo.NewDispatchFunction.ToString())) { if(const FRigVMTemplate* Template = Function->GetTemplate()) { if(const FRigVMDispatchFactory* Factory = Template->GetDispatchFactory()) { if(Factory->GetScriptStruct() == UpgradeInfo.GetNewStruct()) { NewNode = AddTemplateNode(Template->GetNotation(), NodePosition, NodeName, bSetupUndoRedo, false); if(NewNode) { for(int32 ArgumentIndex=0;ArgumentIndexGetArguments().Num();ArgumentIndex++) { const FRigVMFunctionArgument& Argument = Function->GetArguments()[ArgumentIndex]; if(URigVMPin* Pin = NewNode->FindPin(Argument.Name)) { if(Pin->IsWildCard()) { ResolveWildCardPin(Pin, Function->GetArgumentTypeIndices()[ArgumentIndex], bSetupUndoRedo, false); } } } } } } } } } if(NewNode == nullptr) { if(bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } ReportErrorf(TEXT("Unable to upgrade node %s."), *NodeName); return nullptr; } const TArray& AggregatePins = UpgradeInfo.GetAggregatePins(); for(const FString& AggregatePinName : AggregatePins) { const FName PreviousName = NewNode->GetFName(); AddAggregatePin(PreviousName.ToString(), AggregatePinName, FString(), bSetupUndoRedo, false); NewNode = GetGraph()->FindNodeByName(PreviousName); } for(URigVMPin* Pin : NewNode->GetPins()) { const FString DefaultValue = UpgradeInfo.GetDefaultValueForPin(Pin->GetFName()); if(!DefaultValue.IsEmpty()) { SetPinDefaultValue(Pin, DefaultValue, true, bSetupUndoRedo, false); if(FPinState* PinState = PinStates.Find(Pin->GetPinPath())) { PinState->DefaultValue.Reset(); } } } // redirect pin state paths for(TPair& PinState : PinStates) { for(int32 TrueFalse = 0; TrueFalse < 2; TrueFalse++) { const FString RemappedInputPath = UpgradeInfo.RemapPin(PinState.Key, TrueFalse == 0, false); if(RemappedInputPath != PinState.Key) { if(!RedirectedPinPaths.Contains(PinState.Key)) { RedirectedPinPaths.Add(PinState.Key, RemappedInputPath); } } } } UpgradedNode = NewNode; check(UpgradedNode); ApplyPinStates(UpgradedNode, PinStates, RedirectedPinPaths, bSetupUndoRedo); if(bSetupUndoRedo) { ActionStack->EndAction(Action); } return UpgradedNode; } URigVMParameterNode* URigVMController::AddParameterNode(const FName& InParameterName, const FString& InCPPType, UObject* InCPPTypeObject, bool bIsInput, const FString& InDefaultValue, const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { AddVariableNode(InParameterName, InCPPType, InCPPTypeObject, bIsInput, InDefaultValue, InPosition, InNodeName, bSetupUndoRedo, bPrintPythonCommand); ReportWarning(TEXT("AddParameterNode has been deprecated. Adding a variable node instead.")); return nullptr; } URigVMParameterNode* URigVMController::AddParameterNodeFromObjectPath(const FName& InParameterName, const FString& InCPPType, const FString& InCPPTypeObjectPath, bool bIsInput, const FString& InDefaultValue, const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } UObject* CPPTypeObject = nullptr; if (!InCPPTypeObjectPath.IsEmpty()) { CPPTypeObject = RigVMTypeUtils::FindObjectFromCPPTypeObjectPath(InCPPTypeObjectPath); if (CPPTypeObject == nullptr) { ReportErrorf(TEXT("Cannot find cpp type object for path '%s'."), *InCPPTypeObjectPath); return nullptr; } } return AddParameterNode(InParameterName, InCPPType, CPPTypeObject, bIsInput, InDefaultValue, InPosition, InNodeName, bSetupUndoRedo, bPrintPythonCommand); } URigVMCommentNode* URigVMController::AddCommentNode(const FString& InCommentText, const FVector2D& InPosition, const FVector2D& InSize, const FLinearColor& InColor, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); if (Graph->IsA()) { ReportError(TEXT("Cannot add comment nodes to function library graphs.")); return nullptr; } FString Name = GetValidNodeName(InNodeName.IsEmpty() ? FString(TEXT("CommentNode")) : InNodeName); URigVMCommentNode* Node = NewObject(Graph, *Name); Node->Position = InPosition; Node->Size = InSize; Node->NodeColor = InColor; Node->CommentText = InCommentText; Graph->Nodes.Add(Node); if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } FRigVMControllerCompileBracketScope CompileScope(this); FRigVMAddCommentNodeAction Action; if (bSetupUndoRedo) { Action = FRigVMAddCommentNodeAction(Node); Action.Title = FString::Printf(TEXT("Add Comment")); ActionStack->BeginAction(Action); } Notify(ERigVMGraphNotifType::NodeAdded, Node); if (bSetupUndoRedo) { ActionStack->EndAction(Action); } if (bPrintPythonCommand) { TArray Commands = GetAddNodePythonCommands(Node); for (const FString& Command : Commands) { RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("%s"), *Command)); } } return Node; } URigVMRerouteNode* URigVMController::AddRerouteNodeOnLink(URigVMLink* InLink, bool bShowAsFullNode, const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if(!IsValidLinkForGraph(InLink)) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } if (GetGraph()->IsA()) { ReportError(TEXT("Cannot add reroutes to function library graphs.")); return nullptr; } URigVMPin* SourcePin = InLink->GetSourcePin(); const URigVMPin* TargetPin = InLink->GetTargetPin(); TGuardValue GuardCompactness(bIgnoreRerouteCompactnessChanges, true); FRigVMControllerCompileBracketScope CompileScope(this); FRigVMBaseAction Action; if (bSetupUndoRedo) { Action.Title = FString::Printf(TEXT("Add Reroute")); ActionStack->BeginAction(Action); } URigVMRerouteNode* Node = AddRerouteNodeOnPin(TargetPin->GetPinPath(), true, bShowAsFullNode, InPosition, InNodeName, bSetupUndoRedo); if (Node == nullptr) { if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return nullptr; } URigVMPin* ValuePin = Node->Pins[0]; AddLink(SourcePin, ValuePin, bSetupUndoRedo); if (bSetupUndoRedo) { ActionStack->EndAction(Action); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); const FString NodeName = GetSanitizedNodeName(Node->GetName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_reroute_node_on_link_path('%s', %s, %s, '%s')"), *GraphName, *InLink->GetPinPathRepresentation(), (bShowAsFullNode) ? TEXT("True") : TEXT("False"), *RigVMPythonUtils::Vector2DToPythonString(Node->GetPosition()), *NodeName)); } return Node; } URigVMRerouteNode* URigVMController::AddRerouteNodeOnLinkPath(const FString& InLinkPinPathRepresentation, bool bShowAsFullNode, const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMLink* Link = Graph->FindLink(InLinkPinPathRepresentation); return AddRerouteNodeOnLink(Link, bShowAsFullNode, InPosition, InNodeName, bSetupUndoRedo, bPrintPythonCommand); } URigVMRerouteNode* URigVMController::AddRerouteNodeOnPin(const FString& InPinPath, bool bAsInput, bool bShowAsFullNode, const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); if (Graph->IsA()) { ReportError(TEXT("Cannot add reroutes to function library graphs.")); return nullptr; } URigVMPin* Pin = Graph->FindPin(InPinPath); if(Pin == nullptr) { return nullptr; } TGuardValue GuardCompactness(bIgnoreRerouteCompactnessChanges, true); FRigVMControllerCompileBracketScope CompileScope(this); FRigVMBaseAction Action; if (bSetupUndoRedo) { Action.Title = FString::Printf(TEXT("Add Reroute")); ActionStack->BeginAction(Action); } //in case an injected node is present, use its pins for any new links URigVMPin *PinForLink = Pin->GetPinForLink(); if (bAsInput) { BreakAllLinks(PinForLink, bAsInput, bSetupUndoRedo); } FString Name = GetValidNodeName(InNodeName.IsEmpty() ? FString(TEXT("RerouteNode")) : InNodeName); URigVMRerouteNode* Node = NewObject(Graph, *Name); Node->Position = InPosition; Node->bShowAsFullNode = bShowAsFullNode; URigVMPin* ValuePin = NewObject(Node, *URigVMRerouteNode::ValueName); ConfigurePinFromPin(ValuePin, Pin); ValuePin->Direction = ERigVMPinDirection::IO; AddNodePin(Node, ValuePin); if (ValuePin->IsStruct()) { TGuardValue SuspendNotifications(bSuspendNotifications, true); AddPinsForStruct(ValuePin->GetScriptStruct(), Node, ValuePin, ValuePin->Direction, FString(), false); } FString DefaultValue = Pin->GetDefaultValue(); if (!DefaultValue.IsEmpty()) { SetPinDefaultValue(ValuePin, Pin->GetDefaultValue(), true, false, false); } ForEveryPinRecursively(ValuePin, [](URigVMPin* Pin) { Pin->bIsExpanded = true; }); Graph->Nodes.Add(Node); if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } if (bSetupUndoRedo) { ActionStack->AddAction(FRigVMAddRerouteNodeAction(Node)); } Notify(ERigVMGraphNotifType::NodeAdded, Node); if (bAsInput) { AddLink(ValuePin, PinForLink, bSetupUndoRedo); } else { AddLink(PinForLink, ValuePin, bSetupUndoRedo); } if (bSetupUndoRedo) { ActionStack->EndAction(Action); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); const FString NodeName = GetSanitizedNodeName(Node->GetName()); // AddRerouteNodeOnPin(const FString& InPinPath, bool bAsInput, bool bShowAsFullNode, const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_reroute_node_on_pin('%s', %s, %s, %s '%s')"), *GraphName, *GetSanitizedPinPath(InPinPath), (bAsInput) ? TEXT("True") : TEXT("False"), (bShowAsFullNode) ? TEXT("True") : TEXT("False"), *RigVMPythonUtils::Vector2DToPythonString(Node->GetPosition()), *NodeName)); } return Node; } URigVMInjectionInfo* URigVMController::AddInjectedNode(const FString& InPinPath, bool bAsInput, UScriptStruct* InScriptStruct, const FName& InMethodName, const FName& InInputPinName, const FName& InOutputPinName, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); if (Graph->IsA()) { ReportError(TEXT("Cannot add injected nodes to function library graphs.")); return nullptr; } URigVMPin* Pin = Graph->FindPin(InPinPath); if (Pin == nullptr) { return nullptr; } if (Pin->IsArray()) { return nullptr; } if (bAsInput && !(Pin->GetDirection() == ERigVMPinDirection::Input || Pin->GetDirection() == ERigVMPinDirection::IO)) { ReportError(TEXT("Pin is not an input / cannot add injected input node.")); return nullptr; } if (!bAsInput && !(Pin->GetDirection() == ERigVMPinDirection::Output)) { ReportError(TEXT("Pin is not an output / cannot add injected output node.")); return nullptr; } if (InScriptStruct == nullptr) { ReportError(TEXT("InScriptStruct is null.")); return nullptr; } if (InMethodName == NAME_None) { ReportError(TEXT("InMethodName is None.")); return nullptr; } // find the input and output pins to use FProperty* InputProperty = InScriptStruct->FindPropertyByName(InInputPinName); if (InputProperty == nullptr) { ReportErrorf(TEXT("Cannot find property '%s' on struct type '%s'."), *InInputPinName.ToString(), *InScriptStruct->GetName()); return nullptr; } if (!InputProperty->HasMetaData(FRigVMStruct::InputMetaName)) { ReportErrorf(TEXT("Property '%s' on struct type '%s' is not marked as an input."), *InInputPinName.ToString(), *InScriptStruct->GetName()); return nullptr; } FProperty* OutputProperty = InScriptStruct->FindPropertyByName(InOutputPinName); if (OutputProperty == nullptr) { ReportErrorf(TEXT("Cannot find property '%s' on struct type '%s'."), *InOutputPinName.ToString(), *InScriptStruct->GetName()); return nullptr; } if (!OutputProperty->HasMetaData(FRigVMStruct::OutputMetaName)) { ReportErrorf(TEXT("Property '%s' on struct type '%s' is not marked as an output."), *InOutputPinName.ToString(), *InScriptStruct->GetName()); return nullptr; } // 1.- Create unit node // 2.- Rewire links // 3.- Inject node into pin FRigVMControllerCompileBracketScope CompileScope(this); FRigVMBaseAction Action; if (bSetupUndoRedo) { Action.Title = FString::Printf(TEXT("Add Injected Node")); ActionStack->BeginAction(Action); } // 1.- Create unit node URigVMUnitNode* UnitNode = nullptr; URigVMPin* InputPin = nullptr; URigVMPin* OutputPin = nullptr; { { TGuardValue GuardNotifications(bSuspendNotifications, true); UnitNode = AddUnitNode(InScriptStruct, InMethodName, FVector2D::ZeroVector, InNodeName, bSetupUndoRedo); } if (UnitNode == nullptr) { if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return nullptr; } else if (UnitNode->IsMutable()) { ReportErrorf(TEXT("Injected node %s is mutable."), *InScriptStruct->GetName()); RemoveNode(UnitNode, false); if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return nullptr; } InputPin = UnitNode->FindPin(InInputPinName.ToString()); check(InputPin); OutputPin = UnitNode->FindPin(InOutputPinName.ToString()); check(OutputPin); if (InputPin->GetCPPType() != OutputPin->GetCPPType() || InputPin->IsArray() != OutputPin->IsArray()) { ReportErrorf(TEXT("Injected node %s is using incompatible input and output pins."), *InScriptStruct->GetName()); if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return nullptr; } if (InputPin->GetCPPType() != Pin->GetCPPType() || InputPin->IsArray() != Pin->IsArray()) { ReportErrorf(TEXT("Injected node %s is using incompatible pin."), *InScriptStruct->GetName()); if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return nullptr; } } // 2.- Rewire links TArray NewLinks; { URigVMPin* PreviousInputPin = Pin; URigVMPin* PreviousOutputPin = Pin; if (Pin->InjectionInfos.Num() > 0) { PreviousInputPin = Pin->InjectionInfos.Last()->InputPin; PreviousOutputPin = Pin->InjectionInfos.Last()->OutputPin; } if (bAsInput) { FString PinDefaultValue = PreviousInputPin->GetDefaultValue(); if (!PinDefaultValue.IsEmpty()) { SetPinDefaultValue(InputPin, PinDefaultValue, true, bSetupUndoRedo, false); } TArray Links = PreviousInputPin->GetSourceLinks(true /* recursive */); if (Links.Num() > 0) { RewireLinks(PreviousInputPin, InputPin, true, bSetupUndoRedo, Links); NewLinks = InputPin->GetSourceLinks(); } AddLink(OutputPin, PreviousInputPin, bSetupUndoRedo); } else { TArray Links = PreviousOutputPin->GetTargetLinks(true /* recursive */); if (Links.Num() > 0) { RewireLinks(PreviousOutputPin, OutputPin, false, bSetupUndoRedo, Links); NewLinks = OutputPin->GetTargetLinks(); } AddLink(PreviousOutputPin, InputPin, bSetupUndoRedo); } } // 3.- Inject node into pin URigVMInjectionInfo* InjectionInfo = InjectNodeIntoPin(InPinPath, bAsInput, InInputPinName, InOutputPinName, bSetupUndoRedo); if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } if (bSetupUndoRedo) { ActionStack->EndAction(Action); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_injected_node_from_struct_path('%s', %s, '%s', '%s', '%s', '%s', '%s')"), *GraphName, *GetSanitizedPinPath(InPinPath), (bAsInput) ? TEXT("True") : TEXT("False"), *InScriptStruct->GetPathName(), *InMethodName.ToString(), *GetSanitizedPinName(InInputPinName.ToString()), *GetSanitizedPinName(InOutputPinName.ToString()), *GetSanitizedNodeName(InNodeName))); } return InjectionInfo; } URigVMInjectionInfo* URigVMController::AddInjectedNodeFromStructPath(const FString& InPinPath, bool bAsInput, const FString& InScriptStructPath, const FName& InMethodName, const FName& InInputPinName, const FName& InOutputPinName, const FString& InNodeName, bool bSetupUndoRedo) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } UScriptStruct* ScriptStruct = RigVMTypeUtils::FindObjectFromCPPTypeObjectPath(InScriptStructPath); if (ScriptStruct == nullptr) { ReportErrorf(TEXT("Cannot find struct for path '%s'."), *InScriptStructPath); return nullptr; } return AddInjectedNode(InPinPath, bAsInput, ScriptStruct, InMethodName, InInputPinName, InOutputPinName, InNodeName, bSetupUndoRedo); } bool URigVMController::RemoveInjectedNode(const FString& InPinPath, bool bAsInput, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); if (Graph->IsA()) { ReportError(TEXT("Cannot add injected nodes to function library graphs.")); return false; } URigVMPin* Pin = Graph->FindPin(InPinPath); if (Pin == nullptr) { return false; } if (!Pin->HasInjectedNodes()) { return false; } // 1.- Eject node // 2.- Rewire links // 3.- Remove node FRigVMControllerCompileBracketScope CompileScope(this); FRigVMBaseAction Action; if (bSetupUndoRedo) { Action.Title = FString::Printf(TEXT("Remove Injected Node")); ActionStack->BeginAction(Action); } URigVMInjectionInfo* InjectionInfo = Pin->InjectionInfos.Last(); URigVMPin* InputPin = InjectionInfo->InputPin; URigVMPin* OutputPin = InjectionInfo->OutputPin; // 1.- Eject node URigVMNode* NodeEjected = EjectNodeFromPin(InPinPath, bSetupUndoRedo); if (!NodeEjected) { ActionStack->CancelAction(Action, this); return false; } // 2.- Rewire links if (bAsInput) { BreakLink(OutputPin, Pin, bSetupUndoRedo); if (InputPin) { TArray Links = InputPin->GetSourceLinks(); RewireLinks(InputPin, Pin, true, bSetupUndoRedo, Links); } } else { BreakLink(Pin, InputPin, bSetupUndoRedo); TArray Links = InputPin->GetTargetLinks(); RewireLinks(OutputPin, Pin, false, bSetupUndoRedo, Links); } // 3.- Remove node if (!RemoveNode(NodeEjected)) { ActionStack->CancelAction(Action, this); return false; } if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } if (bSetupUndoRedo) { ActionStack->EndAction(Action); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').remove_injected_node('%s', %s)"), *GraphName, *GetSanitizedPinPath(InPinPath), (bAsInput) ? TEXT("True") : TEXT("False"))); } return true; } URigVMInjectionInfo* URigVMController::InjectNodeIntoPin(const FString& InPinPath, bool bAsInput, const FName& InInputPinName, const FName& InOutputPinName, bool bSetupUndoRedo) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMPin* Pin = Graph->FindPin(InPinPath); if (!Pin) { return nullptr; } return InjectNodeIntoPin(Pin, bAsInput, InInputPinName, InOutputPinName, bSetupUndoRedo); } URigVMInjectionInfo* URigVMController::InjectNodeIntoPin(URigVMPin* InPin, bool bAsInput, const FName& InInputPinName, const FName& InOutputPinName, bool bSetupUndoRedo) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); if (Graph->IsA()) { ReportError(TEXT("Cannot inject nodes in function library graphs.")); return nullptr; } URigVMPin* PinForLink = InPin->GetPinForLink(); URigVMNode* NodeToInject = nullptr; TArray ConnectedPins = bAsInput ? PinForLink->GetLinkedSourcePins(true) : PinForLink->GetLinkedTargetPins(true); if (ConnectedPins.Num() < 1) { ReportErrorf(TEXT("Cannot find node connected to pin '%s' as %s."), *InPin->GetPinPath(), bAsInput ? TEXT("input") : TEXT("output")); return nullptr; } NodeToInject = ConnectedPins[0]->GetNode(); for (int32 i = 1; i < ConnectedPins.Num(); ++i) { if (ConnectedPins[i]->GetNode() != NodeToInject) { ReportErrorf(TEXT("Found more than one node connected to pin '%s' as %s."), *InPin->GetPinPath(), bAsInput ? TEXT("input") : TEXT("output")); return nullptr; } } URigVMPin* InputPin = nullptr; URigVMPin* OutputPin = nullptr; if (NodeToInject->IsA()) { InputPin = NodeToInject->FindPin(InInputPinName.ToString()); if (!InputPin) { ReportErrorf(TEXT("Could not find pin '%s' in node %s."), *InInputPinName.ToString(), *NodeToInject->GetNodePath()); return nullptr; } } OutputPin = NodeToInject->FindPin(InOutputPinName.ToString()); if (!OutputPin) { ReportErrorf(TEXT("Could not find pin '%s' in node %s."), *InOutputPinName.ToString(), *NodeToInject->GetNodePath()); return nullptr; } FRigVMControllerCompileBracketScope CompileScope(this); FRigVMBaseAction Action; if (bSetupUndoRedo) { Action.Title = FString::Printf(TEXT("Inject Node")); ActionStack->BeginAction(Action); } URigVMInjectionInfo* InjectionInfo = NewObject(InPin); { Notify(ERigVMGraphNotifType::NodeRemoved, NodeToInject); // re-parent the unit node to be under the injection info RenameObject(NodeToInject, nullptr, InjectionInfo); InjectionInfo->Node = NodeToInject; InjectionInfo->bInjectedAsInput = bAsInput; InjectionInfo->InputPin = InputPin; InjectionInfo->OutputPin = OutputPin; InPin->InjectionInfos.Add(InjectionInfo); Notify(ERigVMGraphNotifType::NodeAdded, NodeToInject); } // Notify the change in links (after the node is injected) { TArray NewLinks; if (bAsInput) { if (InputPin) { NewLinks = InputPin->GetSourceLinks(); } } else { NewLinks = OutputPin->GetTargetLinks(); } for (URigVMLink* Link : NewLinks) { Notify(ERigVMGraphNotifType::LinkAdded, Link); } } if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } if (bSetupUndoRedo) { ActionStack->AddAction(FRigVMInjectNodeIntoPinAction(InjectionInfo)); ActionStack->EndAction(Action); } return InjectionInfo; } URigVMNode* URigVMController::EjectNodeFromPin(const FString& InPinPath, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMPin* Pin = Graph->FindPin(InPinPath); if (!Pin) { return nullptr; } return EjectNodeFromPin(Pin, bSetupUndoRedo, bPrintPythonCommand); } URigVMNode* URigVMController::EjectNodeFromPin(URigVMPin* InPin, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); if (Graph->IsA()) { ReportError(TEXT("Cannot eject nodes in function library graphs.")); return nullptr; } if (!InPin->HasInjectedNodes()) { ReportErrorf(TEXT("Pin '%s' has no injected nodes."), *InPin->GetPinPath()); return nullptr; } URigVMInjectionInfo* Injection = InPin->InjectionInfos.Last(); FRigVMControllerCompileBracketScope CompileScope(this); FRigVMInverseAction InverseAction; if (bSetupUndoRedo) { InverseAction.Title = TEXT("Eject node"); ActionStack->BeginAction(InverseAction); ActionStack->AddAction(FRigVMInjectNodeIntoPinAction(Injection)); } FVector2D Position = InPin->GetNode()->GetPosition() + FVector2D(0.f, 12.f) * float(InPin->GetPinIndex()); if (InPin->GetDirection() == ERigVMPinDirection::Output) { Position += FVector2D(250.f, 0.f); } else { Position -= FVector2D(250.f, 0.f); } URigVMNode* NodeToEject = Injection->Node; URigVMPin* InputPin = Injection->InputPin; URigVMPin* OutputPin = Injection->OutputPin; Notify(ERigVMGraphNotifType::NodeRemoved, NodeToEject); if (Injection->bInjectedAsInput) { if (InputPin) { TArray SourceLinks = InputPin->GetSourceLinks(true); if (SourceLinks.Num() > 0) { Notify(ERigVMGraphNotifType::LinkRemoved, SourceLinks[0]); } } } else { TArray TargetLinks = OutputPin->GetTargetLinks(true); if (TargetLinks.Num() > 0) { Notify(ERigVMGraphNotifType::LinkRemoved, TargetLinks[0]); } } RenameObject(NodeToEject, nullptr, Graph); SetNodePosition(NodeToEject, Position, false); InPin->InjectionInfos.Remove(Injection); DestroyObject(Injection); Notify(ERigVMGraphNotifType::NodeAdded, NodeToEject); if (InputPin) { TArray SourceLinks = InputPin->GetSourceLinks(true); if (SourceLinks.Num() > 0) { Notify(ERigVMGraphNotifType::LinkAdded, SourceLinks[0]); } } TArray TargetLinks = OutputPin->GetTargetLinks(true); if (TargetLinks.Num() > 0) { Notify(ERigVMGraphNotifType::LinkAdded, TargetLinks[0]); } if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } if (bSetupUndoRedo) { ActionStack->EndAction(InverseAction); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').eject_node_from_pin('%s')"), *GraphName, *GetSanitizedPinPath(InPin->GetPinPath()))); } return NodeToEject; } bool URigVMController::EjectAllInjectedNodes(URigVMNode* InNode, bool bSetupUndoRedo, bool bPrintPythonCommands) { if(!IsValidNodeForGraph(InNode)) { return false; } bool bHasAnyInjectedNode = false; for(URigVMPin* Pin : InNode->GetPins()) { bHasAnyInjectedNode = bHasAnyInjectedNode || Pin->HasInjectedNodes(); } if(!bHasAnyInjectedNode) { return false; } FRigVMBaseAction EjectAllInjectedNodesAction; if (bSetupUndoRedo) { ActionStack->BeginAction(EjectAllInjectedNodesAction); } for(URigVMPin* Pin : InNode->GetPins()) { if(Pin->HasInjectedNodes()) { if(!EjectNodeFromPin(Pin, bSetupUndoRedo, bPrintPythonCommands)) { return false; } } } if(bSetupUndoRedo) { ActionStack->EndAction(EjectAllInjectedNodesAction); } return true; } bool URigVMController::Undo() { if (!IsValidGraph()) { return false; } return ActionStack->Undo(this); } bool URigVMController::Redo() { if (!IsValidGraph()) { return false; } return ActionStack->Redo(this); } bool URigVMController::OpenUndoBracket(const FString& InTitle) { if (!IsValidGraph()) { return false; } return ActionStack->OpenUndoBracket(InTitle); } bool URigVMController::CloseUndoBracket() { if (!IsValidGraph()) { return false; } return ActionStack->CloseUndoBracket(this); } bool URigVMController::CancelUndoBracket() { if (!IsValidGraph()) { return false; } return ActionStack->CancelUndoBracket(this); } FString URigVMController::ExportNodesToText(const TArray& InNodeNames) { if (!IsValidGraph()) { return FString(); } URigVMGraph* Graph = GetGraph(); check(Graph); UnMarkAllObjects(EObjectMark(OBJECTMARK_TagExp | OBJECTMARK_TagImp)); FStringOutputDevice Archive; const FExportObjectInnerContext Context; TArray AllNodeNames = InNodeNames; for (const FName& NodeName : InNodeNames) { if (URigVMNode* Node = Graph->FindNodeByName(NodeName)) { for (URigVMPin* Pin : Node->GetPins()) { for (URigVMInjectionInfo* Injection : Pin->GetInjectedNodes()) { AllNodeNames.AddUnique(Injection->Node->GetFName()); } } } } // Export each of the selected nodes for (const FName& NodeName : InNodeNames) { if (URigVMNode* Node = Graph->FindNodeByName(NodeName)) { UExporter::ExportToOutputDevice(&Context, Node, NULL, Archive, TEXT("copy"), 0, PPF_ExportsNotFullyQualified | PPF_Copy | PPF_Delimited, false, Node->GetOuter()); } } for (URigVMLink* Link : Graph->Links) { URigVMPin* SourcePin = Link->GetSourcePin(); URigVMPin* TargetPin = Link->GetTargetPin(); if (SourcePin && TargetPin && IsValid(SourcePin) && IsValid(TargetPin)) { if (!AllNodeNames.Contains(SourcePin->GetNode()->GetFName())) { continue; } if (!AllNodeNames.Contains(TargetPin->GetNode()->GetFName())) { continue; } Link->PrepareForCopy(); UExporter::ExportToOutputDevice(&Context, Link, NULL, Archive, TEXT("copy"), 0, PPF_ExportsNotFullyQualified | PPF_Copy | PPF_Delimited, false, Link->GetOuter()); } } return MoveTemp(Archive); } FString URigVMController::ExportSelectedNodesToText() { if (!IsValidGraph()) { return FString(); } URigVMGraph* Graph = GetGraph(); check(Graph); return ExportNodesToText(Graph->GetSelectNodes()); } struct FRigVMControllerObjectFactory : public FCustomizableTextObjectFactory { public: URigVMController* Controller; TArray CreatedNodes; TArray CreateNodeNames; TMap NodeNameMap; TArray CreatedLinks; public: FRigVMControllerObjectFactory(URigVMController* InController) : FCustomizableTextObjectFactory(GWarn) , Controller(InController) { } protected: virtual bool CanCreateClass(UClass* ObjectClass, bool& bOmitSubObjs) const override { if (URigVMNode* DefaultNode = Cast(ObjectClass->GetDefaultObject())) { // bOmitSubObjs = true; return true; } if (URigVMLink* DefaultLink = Cast(ObjectClass->GetDefaultObject())) { return true; } return false; } virtual void UpdateObjectName(UClass* ObjectClass, FName& InOutObjName) override { if (URigVMNode* DefaultNode = Cast(ObjectClass->GetDefaultObject())) { URigVMGraph* Graph = Controller->GetGraph(); check(Graph); const FName ValidName = Controller->GetUniqueName(InOutObjName, [Graph, this](const FName& InName) { return !CreateNodeNames.Contains(InName) && Graph->IsNameAvailable(InName.ToString()); }, false, true); NodeNameMap.Add(InOutObjName, ValidName); CreateNodeNames.Add(ValidName); InOutObjName = ValidName; } } virtual void ProcessConstructedObject(UObject* CreatedObject) override { if (URigVMNode* CreatedNode = Cast(CreatedObject)) { CreatedNodes.AddUnique(CreatedNode); for (URigVMPin* Pin : CreatedNode->GetPins()) { for (URigVMInjectionInfo* Injection : Pin->GetInjectedNodes()) { ProcessConstructedObject(Injection->Node); FName NewName = Injection->Node->GetFName(); UpdateObjectName(URigVMNode::StaticClass(), NewName); Controller->RenameObject(Injection->Node, *NewName.ToString(), nullptr); Injection->InputPin = Injection->InputPin ? Injection->Node->FindPin(Injection->InputPin->GetName()) : nullptr; Injection->OutputPin = Injection->OutputPin ? Injection->Node->FindPin(Injection->OutputPin->GetName()) : nullptr; } } } else if (URigVMLink* CreatedLink = Cast(CreatedObject)) { CreatedLinks.Add(CreatedLink); } } }; bool URigVMController::CanImportNodesFromText(const FString& InText) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } FRigVMControllerObjectFactory Factory(this); if (!Factory.CanCreateObjectsFromText(InText)) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); Factory.ProcessBuffer(Graph, RF_Transactional, InText); if (Factory.CreatedNodes.Num() == 0) { return false; } // If the graph is a function library, make sure we are pasting unconnected collapse nodes if (Graph->IsA()) { if (!Factory.CreatedLinks.IsEmpty()) { return false; } for (URigVMNode* Node : Factory.CreatedNodes) { if (!Node->IsA()) { return false; } } } return true; } TArray URigVMController::ImportNodesFromText(const FString& InText, bool bSetupUndoRedo, bool bPrintPythonCommands) { TArray NodeNames; if (!IsValidGraph()) { return NodeNames; } if (!bIsTransacting && !IsGraphEditable()) { return NodeNames; } URigVMGraph* Graph = GetGraph(); check(Graph); FRigVMControllerObjectFactory Factory(this); Factory.ProcessBuffer(Graph, RF_Transactional, InText); if (Factory.CreatedNodes.Num() == 0) { return NodeNames; } if (bSetupUndoRedo) { OpenUndoBracket(TEXT("Importing Nodes from Text")); } FRigVMControllerCompileBracketScope CompileScope(this); FRigVMInverseAction AddNodesAction; if (bSetupUndoRedo) { ActionStack->BeginAction(AddNodesAction); } TArray> EditGuards; TArray CreatedNodes = Factory.CreatedNodes; for (int32 i=0; i(CreatedNode)) { if (URigVMGraph* ContainedGraph = CollapseNode->GetContainedGraph()) { EditGuards.Emplace(ContainedGraph->bEditable, true); CreatedNodes.Append(ContainedGraph->GetNodes()); } } } FRigVMUnitNodeCreatedContext::FScope UnitNodeCreatedScope(UnitNodeCreatedContext, ERigVMNodeCreatedReason::Paste); TMap TypesWhenPasted; for (URigVMNode* CreatedNode : Factory.CreatedNodes) { if(!CanAddNode(CreatedNode, true)) { continue; } Graph->Nodes.Add(CreatedNode); if (bSetupUndoRedo) { if (!CreatedNode->IsInjected() || !CreatedNode->IsA()) { ActionStack->AddAction(FRigVMRemoveNodeAction(CreatedNode, this)); } } // find all nodes affected by this TArray SubNodes; SubNodes.Add(CreatedNode); // Refresh the unit nodes to account for changes in node color, pin additions, pin order, etc for(int32 SubNodeIndex=0; SubNodeIndex < SubNodes.Num(); SubNodeIndex++) { if (URigVMUnitNode* UnitNode = Cast(SubNodes[SubNodeIndex])) { TGuardValue SuspendNotifications(bSuspendNotifications, true); RepopulatePinsOnNode(UnitNode); } } for(int32 SubNodeIndex=0; SubNodeIndex < SubNodes.Num(); SubNodeIndex++) { if(URigVMCollapseNode* CollapseNode = Cast(SubNodes[SubNodeIndex])) { { FRigVMControllerGraphGuard GraphGuard(this, CollapseNode->GetContainedGraph(), false); TGuardValue GuardEditGraph(CollapseNode->ContainedGraph->bEditable, true); TGuardValue SuspendNotifications(bSuspendNotifications, true); ReattachLinksToPinObjects(); } SubNodes.Append(CollapseNode->GetContainedNodes()); } } if (URigVMLibraryNode* LibraryNode = Cast(CreatedNode)) { TFunction Recompute = [&](URigVMLibraryNode* OuterNode) { for (URigVMNode* Node : OuterNode->GetContainedNodes()) { if (URigVMLibraryNode* ContainedNode = Cast(Node)) { Recompute(ContainedNode); } } // If the OuterNode is referencing a non-existing function, the contained graph might be empty if (OuterNode->GetContainedGraph() == nullptr) { return; } }; Recompute(LibraryNode); } for(URigVMNode* SubNode : SubNodes) { FRigVMControllerGraphGuard GraphGuard(this, SubNode->GetGraph(), false); if (URigVMUnitNode* UnitNode = Cast(SubNode)) { if (UnitNodeCreatedContext.IsValid()) { if (TSharedPtr StructScope = UnitNode->ConstructStructInstance()) { TGuardValue NodeNameScope(UnitNodeCreatedContext.NodeName, UnitNode->GetFName()); FRigVMStruct* StructInstance = (FRigVMStruct*)StructScope->GetStructMemory(); StructInstance->OnUnitNodeCreated(UnitNodeCreatedContext); } } } if (URigVMFunctionReferenceNode* FunctionRefNode = Cast(SubNode)) { if(URigVMBuildData* BuildData = URigVMBuildData::Get()) { BuildData->RegisterFunctionReference(FunctionRefNode->GetReferencedFunctionHeader().LibraryPointer, FunctionRefNode); } } for(URigVMPin* Pin : SubNode->Pins) { EnsurePinValidity(Pin, true); } } Notify(ERigVMGraphNotifType::NodeAdded, CreatedNode); NodeNames.Add(CreatedNode->GetFName()); } if (bSetupUndoRedo) { ActionStack->EndAction(AddNodesAction); } if (Factory.CreatedLinks.Num() > 0) { FRigVMBaseAction AddLinksAction; if (bSetupUndoRedo) { ActionStack->BeginAction(AddLinksAction); } for (URigVMLink* CreatedLink : Factory.CreatedLinks) { FString SourceLeft, SourceRight, TargetLeft, TargetRight; if (URigVMPin::SplitPinPathAtStart(CreatedLink->SourcePinPath, SourceLeft, SourceRight) && URigVMPin::SplitPinPathAtStart(CreatedLink->TargetPinPath, TargetLeft, TargetRight)) { const FName* NewSourceNodeName = Factory.NodeNameMap.Find(*SourceLeft); const FName* NewTargetNodeName = Factory.NodeNameMap.Find(*TargetLeft); if (NewSourceNodeName && NewTargetNodeName) { CreatedLink->SourcePinPath = URigVMPin::JoinPinPath(NewSourceNodeName->ToString(), SourceRight); CreatedLink->TargetPinPath = URigVMPin::JoinPinPath(NewTargetNodeName->ToString(), TargetRight); URigVMPin* SourcePin = CreatedLink->GetSourcePin(); URigVMPin* TargetPin = CreatedLink->GetTargetPin(); if (SourcePin == nullptr) { URigVMNode* OriginalNode = Graph->FindNode(SourceLeft); if (OriginalNode && OriginalNode->IsA()) { CreatedLink->SourcePinPath = URigVMPin::JoinPinPath(SourceLeft, SourceRight); SourcePin = CreatedLink->GetSourcePin(); } } if (TargetPin == nullptr) { URigVMNode* OriginalNode = Graph->FindNode(TargetLeft); if (OriginalNode && OriginalNode->IsA()) { CreatedLink->TargetPinPath = URigVMPin::JoinPinPath(TargetLeft, TargetRight); TargetPin = CreatedLink->GetTargetPin(); } } if (SourcePin == nullptr) { URigVMNode* OriginalNode = Graph->FindNode(SourceLeft); if (OriginalNode) { FString OldSourcePinPath = URigVMPin::JoinPinPath(SourceLeft, SourceRight); if (URigVMPin* OldPin = Graph->FindPin(OldSourcePinPath)) { if (OldPin->IsStructMember()) { URigVMPin* OldRootPin = OldPin->GetRootPin(); FString NewSourceRootPinPath = URigVMPin::JoinPinPath(NewSourceNodeName->ToString(), OldRootPin->GetName()); if (URigVMPin* NewRootPin = Graph->FindPin(NewSourceRootPinPath)) { SourcePin = CreatedLink->GetSourcePin(); } } } } } if (TargetPin == nullptr) { URigVMNode* OriginalNode = Graph->FindNode(TargetLeft); if (OriginalNode) { FString OldTargetPinPath = URigVMPin::JoinPinPath(TargetLeft, TargetRight); if (URigVMPin* OldPin = Graph->FindPin(OldTargetPinPath)) { if (OldPin->IsStructMember()) { URigVMPin* OldRootPin = OldPin->GetRootPin(); FString NewTargetRootPinPath = URigVMPin::JoinPinPath(NewTargetNodeName->ToString(), OldRootPin->GetName()); if (URigVMPin* NewRootPin = Graph->FindPin(NewTargetRootPinPath)) { TargetPin = CreatedLink->GetTargetPin(); } } } } } if (SourcePin && TargetPin) { // BreakAllLinks will unbind and destroy the injected variable node // We need to rebind to recreate the variable node with the same name bool bWasBinded = TargetPin->IsBoundToVariable(); FString VariableNodeName, BindingPath; if (bWasBinded) { VariableNodeName = TargetPin->GetBoundVariableNode()->GetName(); BindingPath = TargetPin->GetBoundVariablePath(); // The current situation is that the outer pin has an injection info, and the injected node exists // but the injected node is not linked to the outer pin. BreakAllLinks will try to unbind the outer pin, // for that to be successful, the binding needs to be complete // Connect it so that the unbound is successful if (!SourcePin->IsLinkedTo(TargetPin)) { Graph->Links.Add(CreatedLink); SourcePin->Links.Add(CreatedLink); TargetPin->Links.Add(CreatedLink); } if (TargetPin->IsBoundToVariable()) { UnbindPinFromVariable(TargetPin, bSetupUndoRedo); } } BreakAllLinksRecursive(TargetPin, true, true, bSetupUndoRedo); BreakAllLinks(TargetPin, true, bSetupUndoRedo); BreakAllLinksRecursive(TargetPin, true, false, bSetupUndoRedo); // recreate binding if needed if (bWasBinded) { BindPinToVariable(TargetPin, BindingPath, bSetupUndoRedo, VariableNodeName); } else { PrepareToLink(TargetPin, SourcePin, bSetupUndoRedo); Graph->Links.Add(CreatedLink); SourcePin->Links.Add(CreatedLink); TargetPin->Links.Add(CreatedLink); if (bSetupUndoRedo) { ActionStack->AddAction(FRigVMAddLinkAction(SourcePin, TargetPin)); if (SourcePin->GetNode()->IsInjected()) { ActionStack->AddAction(FRigVMInjectNodeIntoPinAction(SourcePin->GetTypedOuter())); } if (TargetPin->GetNode()->IsInjected()) { ActionStack->AddAction(FRigVMInjectNodeIntoPinAction(TargetPin->GetTypedOuter())); } } Notify(ERigVMGraphNotifType::LinkAdded, CreatedLink); } continue; } } } ReportErrorf(TEXT("Cannot import link '%s'."), *URigVMLink::GetPinPathRepresentation(CreatedLink->SourcePinPath, CreatedLink->TargetPinPath)); DestroyObject(CreatedLink); } if (bSetupUndoRedo) { ActionStack->EndAction(AddLinksAction); } } if (bSetupUndoRedo) { CloseUndoBracket(); } #if WITH_EDITOR if (bPrintPythonCommands && !NodeNames.IsEmpty()) { FString PythonContent = InText.Replace(TEXT("\\\""), TEXT("\\\\\"")); PythonContent = InText.Replace(TEXT("'"), TEXT("\\'")); PythonContent = PythonContent.Replace(TEXT("\r\n"), TEXT("\\r\\n'\r\n'")); const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').import_nodes_from_text('%s')"), *GraphName, *PythonContent)); } #endif return NodeNames; } URigVMLibraryNode* URigVMController::LocalizeFunctionFromPath(const FString& InHostPath, const FName& InFunctionName, bool bLocalizeDependentPrivateFunctions, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); if (Graph->IsA()) { ReportError(TEXT("Cannot add function reference nodes to function library graphs.")); return nullptr; } UObject* HostObject = StaticLoadObject(UObject::StaticClass(), NULL, *InHostPath, NULL, LOAD_None, NULL); if (!HostObject) { ReportErrorf(TEXT("Failed to load the Host object %s."), *InHostPath); return nullptr; } IRigVMGraphFunctionHost* FunctionHost = Cast(HostObject); if (!FunctionHost) { ReportError(TEXT("Host object is not a IRigVMGraphFunctionHost.")); return nullptr; } FRigVMGraphFunctionData* Data = FunctionHost->GetRigVMGraphFunctionStore()->FindFunctionByName(InFunctionName); if (!Data) { ReportErrorf(TEXT("Function %s not found in host %s."), *InFunctionName.ToString(), *InHostPath); return nullptr; } return LocalizeFunction(Data->Header.LibraryPointer, bLocalizeDependentPrivateFunctions, bSetupUndoRedo, bPrintPythonCommand); } URigVMLibraryNode* URigVMController::LocalizeFunction( const FRigVMGraphFunctionIdentifier& InFunctionDefinition, bool bLocalizeDependentPrivateFunctions, bool bSetupUndoRedo, bool bPrintPythonCommand) { if(!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } TArray FunctionsToLocalize; FunctionsToLocalize.Add(InFunctionDefinition); TMap Results = LocalizeFunctions(FunctionsToLocalize, bLocalizeDependentPrivateFunctions, bSetupUndoRedo, bPrintPythonCommand); URigVMLibraryNode** LocalizedFunctionPtr = Results.Find(FunctionsToLocalize[0]); if(LocalizedFunctionPtr) { return *LocalizedFunctionPtr; } return nullptr; } TMap URigVMController::LocalizeFunctions( TArray InFunctionDefinitions, bool bLocalizeDependentPrivateFunctions, bool bSetupUndoRedo, bool bPrintPythonCommand) { TMap LocalizedFunctions; if(!IsValidGraph()) { return LocalizedFunctions; } if (!bIsTransacting && !IsGraphEditable()) { return LocalizedFunctions; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMFunctionLibrary* ThisLibrary = Graph->GetDefaultFunctionLibrary(); if(ThisLibrary == nullptr) { return LocalizedFunctions; } TArray FunctionsToLocalize; TArray NodesToVisit; for(const FRigVMGraphFunctionIdentifier& FunctionDefinition : InFunctionDefinitions) { NodesToVisit.AddUnique(FunctionDefinition); FunctionsToLocalize.AddUnique(FRigVMGraphFunctionData::FindFunctionData(FunctionDefinition)); } const FSoftObjectPath ThisFunctionHost = ThisLibrary->GetFunctionHostObjectPath(); // find all functions to localize for(int32 NodeToVisitIndex=0; NodeToVisitIndex& Pair : FunctionData->Header.Dependencies) { NodesToVisit.AddUnique(Pair.Key); } } // sort the functions to localize based on their nesting Algo::Sort(FunctionsToLocalize, [](FRigVMGraphFunctionData* A, FRigVMGraphFunctionData* B) -> bool { check(A); check(B); return B->Header.Dependencies.Contains(A->Header.LibraryPointer); }); FRigVMControllerCompileBracketScope CompileScope(this); if (bSetupUndoRedo) { OpenUndoBracket(TEXT("Localize functions")); } // import the functions to our local function library { FRigVMControllerGraphGuard GraphGuard(this, ThisLibrary, bSetupUndoRedo); for(FRigVMGraphFunctionData* FunctionToLocalize : FunctionsToLocalize) { if (URigVMLibraryNode* ReferencedFunction = Cast(FunctionToLocalize->Header.LibraryPointer.LibraryNode.TryLoad())) { if (IRigVMClientHost* ClientHost = ReferencedFunction->GetImplementingOuter()) { ClientHost->GetRigVMClient()->UpdateGraphFunctionSerializedGraph(ReferencedFunction); } } TArray NodeNames = ImportNodesFromText(FunctionToLocalize->SerializedCollapsedNode, false); if (NodeNames.Num() > 0) { URigVMLibraryNode* LocalizedFunction = ThisLibrary->FindFunction(NodeNames[0]); LocalizedFunctions.Add(FunctionToLocalize->Header.LibraryPointer, LocalizedFunction); ThisLibrary->LocalizedFunctions.FindOrAdd(FunctionToLocalize->Header.LibraryPointer.LibraryNode.ToString(), LocalizedFunction); } } } // once we have all local functions available, clean up the references TArray GraphsToUpdate; GraphsToUpdate.AddUnique(Graph); if(URigVMFunctionLibrary* DefaultFunctionLibrary = Graph->GetDefaultFunctionLibrary()) { GraphsToUpdate.AddUnique(DefaultFunctionLibrary); } for(int32 GraphToUpdateIndex=0; GraphToUpdateIndex NodesToUpdate = GraphToUpdate->GetNodes(); for(URigVMNode* NodeToUpdate : NodesToUpdate) { if(URigVMCollapseNode* CollapseNode = Cast(NodeToUpdate)) { GraphsToUpdate.AddUnique(CollapseNode->GetContainedGraph()); } else if(URigVMFunctionReferenceNode* FunctionReferenceNode = Cast(NodeToUpdate)) { URigVMLibraryNode** RemappedNodePtr = LocalizedFunctions.Find(FunctionReferenceNode->GetReferencedFunctionHeader().LibraryPointer); if(RemappedNodePtr) { URigVMLibraryNode* RemappedNode = *RemappedNodePtr; SetReferencedFunction(FunctionReferenceNode, RemappedNode, bSetupUndoRedo); } } } } if(bSetupUndoRedo) { CloseUndoBracket(); } if (bPrintPythonCommand) { for (const FRigVMGraphFunctionIdentifier& Identifier : InFunctionDefinitions) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); FRigVMGraphFunctionData* FunctionData = FRigVMGraphFunctionData::FindFunctionData(Identifier); const FString FunctionDefinitionName = GetSanitizedNodeName(FunctionData->Header.Name.ToString()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').localize_function_from_path('%s', '%s', %s)"), *GraphName, *Identifier.HostObject.ToString(), *FunctionDefinitionName, bLocalizeDependentPrivateFunctions ? TEXT("True") : TEXT("False"))); } } return LocalizedFunctions; } FName URigVMController::GetUniqueName(const FName& InName, TFunction IsNameAvailableFunction, bool bAllowPeriod, bool bAllowSpace) { FString SanitizedPrefix = InName.ToString(); SanitizeName(SanitizedPrefix, bAllowPeriod, bAllowSpace); int32 NameSuffix = 0; FString Name = SanitizedPrefix; while (!IsNameAvailableFunction(*Name)) { NameSuffix++; Name = FString::Printf(TEXT("%s_%d"), *SanitizedPrefix, NameSuffix); } return *Name; } URigVMCollapseNode* URigVMController::CollapseNodes(const TArray& InNodeNames, const FString& InCollapseNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand, bool bIsAggregate) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); TArray Nodes; for (const FName& NodeName : InNodeNames) { URigVMNode* Node = Graph->FindNodeByName(NodeName); if (Node == nullptr) { ReportErrorf(TEXT("Cannot find node '%s'."), *NodeName.ToString()); return nullptr; } Nodes.AddUnique(Node); } URigVMCollapseNode* Node = CollapseNodes(Nodes, InCollapseNodeName, bSetupUndoRedo, bIsAggregate); if (Node && bPrintPythonCommand) { FString ArrayStr = TEXT("["); for (auto It = InNodeNames.CreateConstIterator(); It; ++It) { ArrayStr += TEXT("'") + It->ToString() + TEXT("'"); if (It.GetIndex() < InNodeNames.Num() - 1) { ArrayStr += TEXT(", "); } } ArrayStr += TEXT("]"); const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').collapse_nodes(%s, '%s')"), *GraphName, *ArrayStr, *InCollapseNodeName)); } return Node; } TArray URigVMController::ExpandLibraryNode(const FName& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return TArray(); } if (!bIsTransacting && !IsGraphEditable()) { return TArray(); } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMNode* Node = Graph->FindNodeByName(InNodeName); if (Node == nullptr) { ReportErrorf(TEXT("Cannot find collapse node '%s'."), *InNodeName.ToString()); return TArray(); } URigVMLibraryNode* LibNode = Cast(Node); if (LibNode == nullptr) { ReportErrorf(TEXT("Node '%s' is not a library node (not collapse nor function)."), *InNodeName.ToString()); return TArray(); } TArray Nodes = ExpandLibraryNode(LibNode, bSetupUndoRedo); if (!Nodes.IsEmpty() && bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); const FString NodeName = GetSanitizedNodeName(Node->GetName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').expand_library_node('%s')"), *GraphName, *NodeName)); } return Nodes; } #endif URigVMCollapseNode* URigVMController::CollapseNodes(const TArray& InNodes, const FString& InCollapseNodeName, bool bSetupUndoRedo, bool bIsAggregate) { URigVMGraph* Graph = GetGraph(); check(Graph); if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } if (Graph->IsA()) { ReportError(TEXT("Cannot collapse nodes in function library graphs.")); return nullptr; } if (InNodes.IsEmpty()) { ReportError(TEXT("No nodes specified to collapse.")); return nullptr; } #if UE_RIGVM_AGGREGATE_NODES_ENABLED if (bIsAggregate) { if(InNodes.Num() != 1) { return nullptr; } if(!InNodes[0]->IsAggregate()) { ReportError(TEXT("Cannot aggregate the given node.")); return nullptr; } } #endif TArray Nodes; for (URigVMNode* Node : InNodes) { if (!IsValidNodeForGraph(Node)) { return nullptr; } // filter out certain nodes if (Node->IsEvent()) { continue; } if (Node->IsA() || Node->IsA()) { continue; } if (URigVMVariableNode* VariableNode = Cast(Node)) { if (VariableNode->IsInputArgument()) { continue; } } Nodes.Add(Node); } if (Nodes.Num() == 0) { return nullptr; } FBox2D Bounds = FBox2D(EForceInit::ForceInit); TArray NodeNames; for (URigVMNode* Node : Nodes) { NodeNames.Add(Node->GetFName()); Bounds += Node->GetPosition(); } FVector2D Diagonal = Bounds.Max - Bounds.Min; FVector2D Center = (Bounds.Min + Bounds.Max) * 0.5f; bool bContainsOutputs = false; TArray PinsToCollapse; TMap CollapsedPins; TArray LinksToRewire; TArray AllLinks = Graph->GetLinks(); auto NodeToBeCollapsed = [&Nodes](URigVMNode* InNode) -> bool { check(InNode); if(Nodes.Contains(InNode)) { return true; } if(InNode->IsInjected()) { InNode = InNode->GetTypedOuter(); if(Nodes.Contains(InNode)) { return true; } } return false; }; // find all pins to collapse. we need this to find out if // we might have a parent pin of a given linked pin already // collapsed. for (URigVMLink* Link : AllLinks) { URigVMPin* SourcePin = Link->GetSourcePin(); URigVMPin* TargetPin = Link->GetTargetPin(); bool bSourceToBeCollapsed = NodeToBeCollapsed(SourcePin->GetNode()); bool bTargetToBeCollapsed = NodeToBeCollapsed(TargetPin->GetNode()); if (bSourceToBeCollapsed == bTargetToBeCollapsed) { continue; } URigVMPin* PinToCollapse = SourcePin; PinsToCollapse.AddUnique(PinToCollapse); LinksToRewire.Add(Link); } // sort the links so that the links on the same node are in the right order Algo::Sort(LinksToRewire, [&AllLinks](URigVMLink* A, URigVMLink* B) -> bool { if(A->GetSourcePin()->GetNode() == B->GetSourcePin()->GetNode()) { return A->GetSourcePin()->GetAbsolutePinIndex() < B->GetSourcePin()->GetAbsolutePinIndex(); } if(A->GetTargetPin()->GetNode() == B->GetTargetPin()->GetNode()) { return A->GetTargetPin()->GetAbsolutePinIndex() < B->GetTargetPin()->GetAbsolutePinIndex(); } return AllLinks.Find(A) < AllLinks.Find(B); }); FRigVMControllerCompileBracketScope CompileScope(this); FRigVMCollapseNodesAction CollapseAction; FString CollapseNodeName = GetValidNodeName(InCollapseNodeName.IsEmpty() ? FString(TEXT("CollapseNode")) : InCollapseNodeName); if (bSetupUndoRedo) { CollapseAction = FRigVMCollapseNodesAction(this, Nodes, CollapseNodeName, bIsAggregate); CollapseAction.Title = TEXT("Collapse Nodes"); ActionStack->BeginAction(CollapseAction); } #if UE_RIGVM_AGGREGATE_NODES_ENABLED URigVMCollapseNode* CollapseNode = nullptr; if (bIsAggregate) { CollapseNode = NewObject(Graph, *CollapseNodeName); } else { CollapseNode = NewObject(Graph, *CollapseNodeName); } #else URigVMCollapseNode* CollapseNode = NewObject(Graph, *CollapseNodeName); #endif CollapseNode->ContainedGraph = NewObject(CollapseNode, TEXT("ContainedGraph")); #if UE_RIGVM_AGGREGATE_NODES_ENABLED if (bIsAggregate) { CollapseNode->ContainedGraph->bEditable = false; } TGuardValue GuardEditGraph(CollapseNode->ContainedGraph->bEditable, true); #endif CollapseNode->Position = Center; Graph->Nodes.Add(CollapseNode); // now looper over the links to be rewired for (URigVMLink* Link : LinksToRewire) { bool bSourceToBeCollapsed = NodeToBeCollapsed(Link->GetSourcePin()->GetNode()); bContainsOutputs = bContainsOutputs || bSourceToBeCollapsed; URigVMPin* PinToCollapse = bSourceToBeCollapsed ? Link->GetSourcePin() : Link->GetTargetPin(); if (CollapsedPins.Contains(PinToCollapse)) { continue; } if(PinToCollapse->IsExecuteContext() && PinToCollapse->GetDirection() == ERigVMPinDirection::IO) { for(const TPair& Pair : CollapsedPins) { if(Pair.Key->IsExecuteContext() && Pair.Key->GetDirection() == ERigVMPinDirection::IO) { CollapsedPins.Add(PinToCollapse, Pair.Value); break; } } if (CollapsedPins.Contains(PinToCollapse)) { continue; } } // for links that connect to the right side of the collapse // node, we need to skip sub pins of already exposed pins if (bSourceToBeCollapsed) { bool bParentPinCollapsed = false; URigVMPin* ParentPin = PinToCollapse->GetParentPin(); while (ParentPin != nullptr) { if (PinsToCollapse.Contains(ParentPin)) { bParentPinCollapsed = true; break; } ParentPin = ParentPin->GetParentPin(); } if (bParentPinCollapsed) { continue; } } FName PinName = GetUniqueName(PinToCollapse->GetFName(), [CollapseNode](const FName& InName) { return CollapseNode->FindPin(InName.ToString()) == nullptr; }, false, true); URigVMPin* CollapsedPin = NewObject(CollapseNode, PinName); ConfigurePinFromPin(CollapsedPin, PinToCollapse, true); if (CollapsedPin->GetDirection() == ERigVMPinDirection::IO) { if(CollapsedPin->IsExecuteContext()) { bContainsOutputs = true; } else { CollapsedPin->Direction = bSourceToBeCollapsed ? ERigVMPinDirection::Output : ERigVMPinDirection::Input; } } if (CollapsedPin->IsStruct()) { TGuardValue SuspendNotifications(bSuspendNotifications, true); AddPinsForStruct(CollapsedPin->GetScriptStruct(), CollapseNode, CollapsedPin, CollapsedPin->GetDirection(), FString(), false); } AddNodePin(CollapseNode, CollapsedPin); FPinState PinState = GetPinState(PinToCollapse); ApplyPinState(CollapsedPin, PinState); CollapsedPins.Add(PinToCollapse, CollapsedPin); } Notify(ERigVMGraphNotifType::NodeAdded, CollapseNode); URigVMFunctionEntryNode* EntryNode = nullptr; URigVMFunctionReturnNode* ReturnNode = nullptr; { FRigVMControllerGraphGuard GraphGuard(this, CollapseNode->GetContainedGraph(), false); EntryNode = NewObject(CollapseNode->ContainedGraph, TEXT("Entry")); CollapseNode->ContainedGraph->Nodes.Add(EntryNode); EntryNode->Position = -Diagonal * 0.5f - FVector2D(250.f, 0.f); { TGuardValue SuspendNotifications(bSuspendNotifications, true); RefreshFunctionPins(EntryNode); } Notify(ERigVMGraphNotifType::NodeAdded, EntryNode); if (bContainsOutputs) { ReturnNode = NewObject(CollapseNode->ContainedGraph, TEXT("Return")); CollapseNode->ContainedGraph->Nodes.Add(ReturnNode); ReturnNode->Position = FVector2D(Diagonal.X, -Diagonal.Y) * 0.5f + FVector2D(300.f, 0.f); { TGuardValue SuspendNotifications(bSuspendNotifications, true); RefreshFunctionPins(ReturnNode); } Notify(ERigVMGraphNotifType::NodeAdded, ReturnNode); } } // create the new nodes within the collapse node TArray ContainedNodeNames; { FString TextContent = ExportNodesToText(NodeNames); FRigVMControllerGraphGuard GraphGuard(this, CollapseNode->GetContainedGraph(), false); ContainedNodeNames = ImportNodesFromText(TextContent, false); // move the nodes to the right place for (const FName& ContainedNodeName : ContainedNodeNames) { if (URigVMNode* ContainedNode = CollapseNode->GetContainedGraph()->FindNodeByName(ContainedNodeName)) { if(!ContainedNode->IsInjected()) { SetNodePosition(ContainedNode, ContainedNode->Position - Center, false, false); } } } for (URigVMLink* LinkToRewire : LinksToRewire) { URigVMPin* SourcePin = LinkToRewire->GetSourcePin(); URigVMPin* TargetPin = LinkToRewire->GetTargetPin(); if (NodeToBeCollapsed(SourcePin->GetNode())) { // if the parent pin of this was collapsed // it's possible that the child pin wasn't. if (!CollapsedPins.Contains(SourcePin)) { continue; } URigVMPin* CollapsedPin = CollapsedPins.FindChecked(SourcePin); SourcePin = CollapseNode->ContainedGraph->FindPin(SourcePin->GetPinPath()); TargetPin = ReturnNode->FindPin(CollapsedPin->GetName()); } else { URigVMPin* CollapsedPin = CollapsedPins.FindChecked(TargetPin); SourcePin = EntryNode->FindPin(CollapsedPin->GetName()); TargetPin = CollapseNode->ContainedGraph->FindPin(TargetPin->GetPinPath()); } if (SourcePin && TargetPin) { if (!SourcePin->IsLinkedTo(TargetPin)) { AddLink(SourcePin, TargetPin, false); } } } } TArray RewiredLinks; for (URigVMLink* LinkToRewire : LinksToRewire) { if (RewiredLinks.Contains(LinkToRewire)) { continue; } URigVMPin* SourcePin = LinkToRewire->GetSourcePin(); URigVMPin* TargetPin = LinkToRewire->GetTargetPin(); if (NodeToBeCollapsed(SourcePin->GetNode())) { FString SegmentPath; URigVMPin* PinToCheck = SourcePin; URigVMPin** CollapsedPinPtr = CollapsedPins.Find(PinToCheck); while (CollapsedPinPtr == nullptr) { if (SegmentPath.IsEmpty()) { SegmentPath = PinToCheck->GetName(); } else { SegmentPath = URigVMPin::JoinPinPath(PinToCheck->GetName(), SegmentPath); } PinToCheck = PinToCheck->GetParentPin(); check(PinToCheck); CollapsedPinPtr = CollapsedPins.Find(PinToCheck); } URigVMPin* CollapsedPin = *CollapsedPinPtr; check(CollapsedPin); if (!SegmentPath.IsEmpty()) { CollapsedPin = CollapsedPin->FindSubPin(SegmentPath); check(CollapsedPin); } TArray TargetLinks = SourcePin->GetTargetLinks(false); for (URigVMLink* TargetLink : TargetLinks) { TargetPin = TargetLink->GetTargetPin(); if (!CollapsedPin->IsLinkedTo(TargetPin)) { AddLink(CollapsedPin, TargetPin, false); } } RewiredLinks.Append(TargetLinks); } else { URigVMPin* CollapsedPin = CollapsedPins.FindChecked(TargetPin); if (!SourcePin->IsLinkedTo(CollapsedPin)) { AddLink(SourcePin, CollapsedPin, false); } } RewiredLinks.Add(LinkToRewire); } if (ReturnNode) { struct Local { static bool IsLinkedToEntryNode(URigVMNode* InNode, TMap& CachedMap) { if (InNode->IsA()) { return true; } if (!CachedMap.Contains(InNode)) { CachedMap.Add(InNode, false); if (URigVMPin* ExecuteContextPin = InNode->FindPin(FRigVMStruct::ExecuteContextName.ToString())) { TArray SourcePins = ExecuteContextPin->GetLinkedSourcePins(); for (URigVMPin* SourcePin : SourcePins) { if (IsLinkedToEntryNode(SourcePin->GetNode(), CachedMap)) { CachedMap.FindOrAdd(InNode) = true; break; } } } } return CachedMap.FindChecked(InNode); } }; // check if there is a last node on the top level block what we need to hook up TMap IsContainedNodeLinkedToEntryNode; TArray NodesForExecutePin; NodesForExecutePin.Add(EntryNode); for (int32 NodeForExecutePinIndex = 0; NodeForExecutePinIndex < NodesForExecutePin.Num(); NodeForExecutePinIndex++) { URigVMNode* NodeForExecutePin = NodesForExecutePin[NodeForExecutePinIndex]; if (!NodeForExecutePin->IsMutable()) { continue; } TArray TargetNodes = NodeForExecutePin->GetLinkedTargetNodes(); for(URigVMNode* TargetNode : TargetNodes) { NodesForExecutePin.AddUnique(TargetNode); } // make sure the node doesn't have any mutable nodes connected to its executecontext URigVMPin* ExecuteContextPin = nullptr; if(URigVMUnitNode* UnitNode = Cast(NodeForExecutePin)) { TSharedPtr UnitScope = UnitNode->ConstructStructInstance(); if(UnitScope.IsValid()) { FRigVMStruct* Unit = (FRigVMStruct*)UnitScope->GetStructMemory(); if(Unit->IsForLoop()) { ExecuteContextPin = NodeForExecutePin->FindPin(FRigVMStruct::ForLoopCompletedPinName.ToString()); } } } if(ExecuteContextPin == nullptr) { ExecuteContextPin = NodeForExecutePin->FindPin(FRigVMStruct::ExecuteContextName.ToString()); } if(ExecuteContextPin) { if(!ExecuteContextPin->IsExecuteContext()) { continue; } if (ExecuteContextPin->GetDirection() != ERigVMPinDirection::IO && ExecuteContextPin->GetDirection() != ERigVMPinDirection::Output) { continue; } if (ExecuteContextPin->GetTargetLinks().Num() > 0) { continue; } if (!Local::IsLinkedToEntryNode(NodeForExecutePin, IsContainedNodeLinkedToEntryNode)) { continue; } FRigVMControllerGraphGuard GraphGuard(this, CollapseNode->GetContainedGraph(), false); AddLink(ExecuteContextPin, ReturnNode->FindPin(FRigVMStruct::ExecuteContextName.ToString()), false); break; } } } for (const FName& NodeToRemove : NodeNames) { RemoveNodeByName(NodeToRemove, false, true); } if (!InCollapseNodeName.IsEmpty() && CollapseNodeName != InCollapseNodeName) { FString ValidName = GetValidNodeName(InCollapseNodeName); if (ValidName == InCollapseNodeName) { RenameNode(CollapseNode, *ValidName, bSetupUndoRedo); } } if (bSetupUndoRedo) { ActionStack->EndAction(CollapseAction); } return CollapseNode; } TArray URigVMController::ExpandLibraryNode(URigVMLibraryNode* InNode, bool bSetupUndoRedo) { if (!bIsTransacting && !IsGraphEditable()) { return TArray(); } if (!IsValidNodeForGraph(InNode)) { return TArray(); } URigVMGraph* Graph = GetGraph(); check(Graph); if (Graph->IsA()) { ReportError(TEXT("Cannot expand nodes in function library graphs.")); return TArray(); } URigVMGraph* InnerGraph = InNode->GetContainedGraph(); if (URigVMFunctionReferenceNode* RefNode = Cast(InNode)) { if (URigVMLibraryNode* LibraryNode = RefNode->LoadReferencedNode()) { InnerGraph = LibraryNode->GetContainedGraph(); } else { ReportError(TEXT("Cannot expand nodes from function reference because the source graph is not found.")); return TArray(); } } TArray ContainedNodes = InnerGraph->GetNodes(); TArray ContainedLinks = InnerGraph->GetLinks(); if (ContainedNodes.Num() == 0) { return TArray(); } FRigVMControllerCompileBracketScope CompileScope(this); FRigVMExpandNodeAction ExpandAction; if (bSetupUndoRedo) { ExpandAction = FRigVMExpandNodeAction(this, InNode); ExpandAction.Title = FString::Printf(TEXT("Expand '%s' Node"), *InNode->GetName()); ActionStack->BeginAction(ExpandAction); } TArray NodeNames; FBox2D Bounds = FBox2D(EForceInit::ForceInit); { TArray FilteredNodes; for (URigVMNode* Node : ContainedNodes) { if (Cast(Node) != nullptr || Cast(Node) != nullptr) { continue; } if (URigVMVariableNode* VariableNode = Cast(Node)) { if (VariableNode->IsInputArgument()) { continue; } } if(Node->IsInjected()) { continue; } NodeNames.Add(Node->GetFName()); FilteredNodes.Add(Node); Bounds += Node->GetPosition(); } ContainedNodes = FilteredNodes; } if (ContainedNodes.Num() == 0) { if (bSetupUndoRedo) { ActionStack->CancelAction(ExpandAction, this); } return TArray(); } // Find local variables that need to be added as member variables. If member variables of same name and type already // exist, they will be reused. If a local variable is not used, it will not be created. if (URigVMFunctionReferenceNode* FunctionReferenceNode = Cast(InNode)) { TArray LocalVariables = InnerGraph->LocalVariables; TArray CurrentVariables = GetAllVariables(); TArray VariablesToAdd; for (const URigVMNode* Node : InnerGraph->GetNodes()) { if (const URigVMVariableNode* VariableNode = Cast(Node)) { if (VariableNode->IsInputArgument()) { continue; } for (FRigVMGraphVariableDescription& LocalVariable : LocalVariables) { if (LocalVariable.Name == VariableNode->GetVariableName()) { bool bVariableExists = false; bool bVariableIncompatible = false; FRigVMExternalVariable LocalVariableExternalType = LocalVariable.ToExternalVariable(); for (FRigVMExternalVariable& CurrentVariable : CurrentVariables) { if (CurrentVariable.Name == LocalVariable.Name) { if (CurrentVariable.TypeName != LocalVariableExternalType.TypeName || CurrentVariable.TypeObject != LocalVariableExternalType.TypeObject || CurrentVariable.bIsArray != LocalVariableExternalType.bIsArray) { bVariableIncompatible = true; } bVariableExists = true; break; } } if (!bVariableExists) { VariablesToAdd.Add(LocalVariable); } else if(bVariableIncompatible) { ReportErrorf(TEXT("Found variable %s of incompatible type with a local variable inside function %s"), *LocalVariable.Name.ToString(), *FunctionReferenceNode->GetReferencedFunctionHeader().Name.ToString()); if (bSetupUndoRedo) { ActionStack->CancelAction(ExpandAction, this); } return TArray(); } break; } } } } if (RequestNewExternalVariableDelegate.IsBound()) { for (const FRigVMGraphVariableDescription& OldVariable : VariablesToAdd) { RequestNewExternalVariableDelegate.Execute(OldVariable, false, false); } } } FVector2D Diagonal = Bounds.Max - Bounds.Min; FVector2D Center = (Bounds.Min + Bounds.Max) * 0.5f; FString TextContent; { FRigVMControllerGraphGuard GraphGuard(this, InnerGraph, false); TextContent = ExportNodesToText(NodeNames); } TArray ExpandedNodeNames = ImportNodesFromText(TextContent, false); TArray ExpandedNodes; for (const FName& ExpandedNodeName : ExpandedNodeNames) { URigVMNode* ExpandedNode = Graph->FindNodeByName(ExpandedNodeName); check(ExpandedNode); ExpandedNodes.Add(ExpandedNode); } check(ExpandedNodeNames.Num() >= NodeNames.Num()); TMap NodeNameMap; for (int32 NodeNameIndex = 0, ExpandedNodeNameIndex = 0; NodeNameIndex < NodeNames.Num(); ExpandedNodeNameIndex++) { if (ExpandedNodes[ExpandedNodeNameIndex]->IsInjected()) { continue; } NodeNameMap.Add(NodeNames[NodeNameIndex], ExpandedNodeNames[ExpandedNodeNameIndex]); SetNodePosition(ExpandedNodes[ExpandedNodeNameIndex], InNode->Position + ContainedNodes[NodeNameIndex]->Position - Center, false, false); NodeNameIndex++; } // a) store all of the pin defaults off the library node TMap PinStates = GetPinStates(InNode); // b) create a map of new links to create by following the links to / from the library node TMap> ToLibraryNode; TMap> FromLibraryNode; TArray LibraryPinsToReroute; TArray LibraryLinks = InNode->GetLinks(); for (URigVMLink* Link : LibraryLinks) { if (Link->GetTargetPin()->GetNode() == InNode) { if (!Link->GetTargetPin()->IsRootPin()) { LibraryPinsToReroute.AddUnique(Link->GetTargetPin()->GetRootPin()); } FString NodeName, PinPath; URigVMPin::SplitPinPathAtStart(Link->GetTargetPin()->GetPinPath(), NodeName, PinPath); ToLibraryNode.FindOrAdd(PinPath).Add(Link->GetSourcePin()->GetPinPath()); } else { if (!Link->GetSourcePin()->IsRootPin()) { LibraryPinsToReroute.AddUnique(Link->GetSourcePin()->GetRootPin()); } FString NodeName, PinPath; URigVMPin::SplitPinPathAtStart(Link->GetSourcePin()->GetPinPath(), NodeName, PinPath); FromLibraryNode.FindOrAdd(PinPath).Add(Link->GetTargetPin()->GetPinPath()); } } // c) create a map from the entry node to the contained graph TMap> FromEntryNode; if (URigVMFunctionEntryNode* EntryNode = InnerGraph->GetEntryNode()) { TArray EntryLinks = EntryNode->GetLinks(); for (URigVMNode* Node : InnerGraph->GetNodes()) { if (URigVMVariableNode* VariableNode = Cast(Node)) { if (VariableNode->IsInputArgument()) { EntryLinks.Append(VariableNode->GetLinks()); } } } for (URigVMLink* Link : EntryLinks) { if (Link->GetSourcePin()->GetNode() != EntryNode && !Link->GetSourcePin()->GetNode()->IsA()) { continue; } if (!Link->GetSourcePin()->IsRootPin()) { LibraryPinsToReroute.AddUnique(InNode->FindPin(Link->GetSourcePin()->GetRootPin()->GetName())); } FString NodeName, PinPath; URigVMPin::SplitPinPathAtStart(Link->GetSourcePin()->GetPinPath(), NodeName, PinPath); if (URigVMVariableNode* VariableNode = Cast(Link->GetSourcePin()->GetNode())) { PinPath = VariableNode->GetVariableName().ToString(); } TArray& LinkedPins = FromEntryNode.FindOrAdd(PinPath); URigVMPin::SplitPinPathAtStart(Link->GetTargetPin()->GetPinPath(), NodeName, PinPath); if (NodeNameMap.Contains(*NodeName)) { NodeName = NodeNameMap.FindChecked(*NodeName).ToString(); LinkedPins.Add(URigVMPin::JoinPinPath(NodeName, PinPath)); } else if (NodeName == TEXT("Return")) { LinkedPins.Add(URigVMPin::JoinPinPath(NodeName, PinPath)); } } } // d) create a map from the contained graph from to the return node TMap> ToReturnNode; if (URigVMFunctionReturnNode* ReturnNode = InnerGraph->GetReturnNode()) { TArray ReturnLinks = ReturnNode->GetLinks(); for (URigVMLink* Link : ReturnLinks) { if (Link->GetTargetPin()->GetNode() != ReturnNode) { continue; } if (!Link->GetTargetPin()->IsRootPin()) { LibraryPinsToReroute.AddUnique(InNode->FindPin(Link->GetTargetPin()->GetRootPin()->GetName())); } FString NodeName, PinPath; URigVMPin::SplitPinPathAtStart(Link->GetTargetPin()->GetPinPath(), NodeName, PinPath); TArray& LinkedPins = ToReturnNode.FindOrAdd(PinPath); URigVMPin::SplitPinPathAtStart(Link->GetSourcePin()->GetPinPath(), NodeName, PinPath); if (NodeNameMap.Contains(*NodeName)) { NodeName = NodeNameMap.FindChecked(*NodeName).ToString(); LinkedPins.Add(URigVMPin::JoinPinPath(NodeName, PinPath)); } else if (NodeName == TEXT("Entry")) { LinkedPins.Add(URigVMPin::JoinPinPath(NodeName, PinPath)); } } } // e) restore all pin states on pins linked to the entry node for (const TPair>& FromEntryPair : FromEntryNode) { FString EntryPinPath = FromEntryPair.Key; const FPinState* CollapsedPinState = PinStates.Find(EntryPinPath); if (CollapsedPinState == nullptr) { continue; } for (const FString& EntryTargetLinkPinPath : FromEntryPair.Value) { if (URigVMPin* TargetPin = GetGraph()->FindPin(EntryTargetLinkPinPath)) { ApplyPinState(TargetPin, *CollapsedPinState); } } } // f) create reroutes for all pins which had wires on sub pins TMap ReroutedInputPins; TMap ReroutedOutputPins; FVector2D RerouteInputPosition = InNode->Position + FVector2D(-Diagonal.X, -Diagonal.Y) * 0.5 + FVector2D(-200.f, 0.f); FVector2D RerouteOutputPosition = InNode->Position + FVector2D(Diagonal.X, -Diagonal.Y) * 0.5 + FVector2D(250.f, 0.f); for (URigVMPin* LibraryPinToReroute : LibraryPinsToReroute) { if (LibraryPinToReroute->GetDirection() == ERigVMPinDirection::Input || LibraryPinToReroute->GetDirection() == ERigVMPinDirection::IO) { URigVMRerouteNode* RerouteNode = AddFreeRerouteNode( true, LibraryPinToReroute->GetCPPType(), *LibraryPinToReroute->GetCPPTypeObject()->GetPathName(), false, NAME_None, LibraryPinToReroute->GetDefaultValue(), RerouteInputPosition, FString::Printf(TEXT("Reroute_%s"), *LibraryPinToReroute->GetName()), false); RerouteInputPosition += FVector2D(0.f, 150.f); URigVMPin* ReroutePin = RerouteNode->FindPin(URigVMRerouteNode::ValueName); ApplyPinState(ReroutePin, GetPinState(LibraryPinToReroute)); ReroutedInputPins.Add(LibraryPinToReroute->GetName(), ReroutePin); ExpandedNodes.Add(RerouteNode); } if (LibraryPinToReroute->GetDirection() == ERigVMPinDirection::Output || LibraryPinToReroute->GetDirection() == ERigVMPinDirection::IO) { URigVMRerouteNode* RerouteNode = AddFreeRerouteNode( true, LibraryPinToReroute->GetCPPType(), *LibraryPinToReroute->GetCPPTypeObject()->GetPathName(), false, NAME_None, LibraryPinToReroute->GetDefaultValue(), RerouteOutputPosition, FString::Printf(TEXT("Reroute_%s"), *LibraryPinToReroute->GetName()), false); RerouteOutputPosition += FVector2D(0.f, 150.f); URigVMPin* ReroutePin = RerouteNode->FindPin(URigVMRerouteNode::ValueName); ApplyPinState(ReroutePin, GetPinState(LibraryPinToReroute)); ReroutedOutputPins.Add(LibraryPinToReroute->GetName(), ReroutePin); ExpandedNodes.Add(RerouteNode); } } // g) remap all output / source pins and create a final list of links to create TMap RemappedSourcePinsForInputs; TMap RemappedSourcePinsForOutputs; TArray LibraryPins = InNode->GetAllPinsRecursively(); for (URigVMPin* LibraryPin : LibraryPins) { FString LibraryPinPath = LibraryPin->GetPinPath(); FString LibraryNodeName; URigVMPin::SplitPinPathAtStart(LibraryPinPath, LibraryNodeName, LibraryPinPath); struct Local { static void UpdateRemappedSourcePins(FString SourcePinPath, FString TargetPinPath, TMap& RemappedSourcePins) { while (!SourcePinPath.IsEmpty() && !TargetPinPath.IsEmpty()) { RemappedSourcePins.FindOrAdd(SourcePinPath) = TargetPinPath; FString SourceLastSegment, TargetLastSegment; if (!URigVMPin::SplitPinPathAtEnd(SourcePinPath, SourcePinPath, SourceLastSegment)) { break; } if (!URigVMPin::SplitPinPathAtEnd(TargetPinPath, TargetPinPath, TargetLastSegment)) { break; } } } }; if (LibraryPin->GetDirection() == ERigVMPinDirection::Input || LibraryPin->GetDirection() == ERigVMPinDirection::IO) { if (const TArray* LibraryPinLinksPtr = ToLibraryNode.Find(LibraryPinPath)) { const TArray& LibraryPinLinks = *LibraryPinLinksPtr; ensure(LibraryPinLinks.Num() == 1); const FString SourcePinPath = LibraryPinPath; FString TargetPinPath = LibraryPinLinks[0]; // if the pin on the library node is represented by a reroute // we need to remap to that instead. if(URigVMPin** ReroutedPinPtr = ReroutedInputPins.Find(SourcePinPath)) { if(URigVMPin* ReroutedPin = *ReroutedPinPtr) { TargetPinPath = ReroutedPin->GetPinPath(); } } Local::UpdateRemappedSourcePins(SourcePinPath, TargetPinPath, RemappedSourcePinsForInputs); } } if (LibraryPin->GetDirection() == ERigVMPinDirection::Output || LibraryPin->GetDirection() == ERigVMPinDirection::IO) { if (const TArray* LibraryPinLinksPtr = ToReturnNode.Find(LibraryPinPath)) { const TArray& LibraryPinLinks = *LibraryPinLinksPtr; ensure(LibraryPinLinks.Num() == 1); const FString SourcePinPath = LibraryPinPath; FString TargetPinPath = LibraryPinLinks[0]; // if the pin on the library node is represented by a reroute // we need to remap to that instead. if(URigVMPin** ReroutedPinPtr = ReroutedOutputPins.Find(SourcePinPath)) { if(URigVMPin* ReroutedPin = *ReroutedPinPtr) { TargetPinPath = ReroutedPin->GetPinPath(); } } Local::UpdateRemappedSourcePins(SourcePinPath, TargetPinPath, RemappedSourcePinsForOutputs); } } } // h) re-establish all of the links going to the left of the library node // in this pass we only care about pins which have reroutes for (const TPair>& ToLibraryNodePair : ToLibraryNode) { FString LibraryNodePinName, LibraryNodePinPathSuffix; if (!URigVMPin::SplitPinPathAtStart(ToLibraryNodePair.Key, LibraryNodePinName, LibraryNodePinPathSuffix)) { LibraryNodePinName = ToLibraryNodePair.Key; } if (!ReroutedInputPins.Contains(LibraryNodePinName)) { continue; } URigVMPin* ReroutedPin = ReroutedInputPins.FindChecked(LibraryNodePinName); URigVMPin* TargetPin = LibraryNodePinPathSuffix.IsEmpty() ? ReroutedPin : ReroutedPin->FindSubPin(LibraryNodePinPathSuffix); check(TargetPin); for (const FString& SourcePinPath : ToLibraryNodePair.Value) { URigVMPin* SourcePin = GetGraph()->FindPin(*SourcePinPath); if (SourcePin && TargetPin) { if (!SourcePin->IsLinkedTo(TargetPin)) { AddLink(SourcePin, TargetPin, false); } } } } // i) re-establish all of the links going to the left of the library node (based on the entry node) for (const TPair>& FromEntryNodePair : FromEntryNode) { FString EntryPinPath = FromEntryNodePair.Key; FString EntryPinPathSuffix; const FString* RemappedSourcePin = RemappedSourcePinsForInputs.Find(EntryPinPath); while (RemappedSourcePin == nullptr) { FString LastSegment; if (!URigVMPin::SplitPinPathAtEnd(EntryPinPath, EntryPinPath, LastSegment)) { break; } if (EntryPinPathSuffix.IsEmpty()) { EntryPinPathSuffix = LastSegment; } else { EntryPinPathSuffix = URigVMPin::JoinPinPath(LastSegment, EntryPinPathSuffix); } RemappedSourcePin = RemappedSourcePinsForInputs.Find(EntryPinPath); } if (RemappedSourcePin == nullptr) { continue; } FString RemappedSourcePinPath = *RemappedSourcePin; if (!EntryPinPathSuffix.IsEmpty()) { RemappedSourcePinPath = URigVMPin::JoinPinPath(RemappedSourcePinPath, EntryPinPathSuffix); } // remap the top level pin in case we need to insert a reroute FString EntryPinName; if (!URigVMPin::SplitPinPathAtStart(FromEntryNodePair.Key, EntryPinPath, EntryPinPathSuffix)) { EntryPinName = FromEntryNodePair.Key; EntryPinPathSuffix.Reset(); } if (ReroutedInputPins.Contains(EntryPinName)) { URigVMPin* ReroutedPin = ReroutedInputPins.FindChecked(EntryPinName); URigVMPin* TargetPin = EntryPinPathSuffix.IsEmpty() ? ReroutedPin : ReroutedPin->FindSubPin(EntryPinPathSuffix); check(TargetPin); RemappedSourcePinPath = TargetPin->GetPinPath(); } for (const FString& FromEntryNodeTargetPinPath : FromEntryNodePair.Value) { TArray TargetPins; URigVMPin* SourcePin = GetGraph()->FindPin(RemappedSourcePinPath); URigVMPin* TargetPin = GetGraph()->FindPin(FromEntryNodeTargetPinPath); // potentially the target pin was on the entry node, // so there's no node been added for it. we'll have to look into the remapped // pins for the "FromLibraryNode" map. if(TargetPin == nullptr) { FString RemappedTargetPinPath = FromEntryNodeTargetPinPath; FString ReturnNodeName, ReturnPinPath; if (URigVMPin::SplitPinPathAtStart(RemappedTargetPinPath, ReturnNodeName, ReturnPinPath)) { if(Cast(InnerGraph->FindNode(ReturnNodeName))) { if(FromLibraryNode.Contains(ReturnPinPath)) { const TArray& FromLibraryNodeTargetPins = FromLibraryNode.FindChecked(ReturnPinPath); for(const FString& FromLibraryNodeTargetPin : FromLibraryNodeTargetPins) { if(URigVMPin* MappedTargetPin = GetGraph()->FindPin(FromLibraryNodeTargetPin)) { TargetPins.Add(MappedTargetPin); } } } } } } else { TargetPins.Add(TargetPin); } if (SourcePin) { for(URigVMPin* EachTargetPin : TargetPins) { if (!SourcePin->IsLinkedTo(EachTargetPin)) { AddLink(SourcePin, EachTargetPin, false); } } } } } // j) re-establish all of the links going from the right of the library node // in this pass we only check pins which have a reroute for (const TPair>& ToReturnNodePair : ToReturnNode) { FString LibraryNodePinName, LibraryNodePinPathSuffix; if (!URigVMPin::SplitPinPathAtStart(ToReturnNodePair.Key, LibraryNodePinName, LibraryNodePinPathSuffix)) { LibraryNodePinName = ToReturnNodePair.Key; } if (!ReroutedOutputPins.Contains(LibraryNodePinName)) { continue; } URigVMPin* ReroutedPin = ReroutedOutputPins.FindChecked(LibraryNodePinName); URigVMPin* TargetPin = LibraryNodePinPathSuffix.IsEmpty() ? ReroutedPin : ReroutedPin->FindSubPin(LibraryNodePinPathSuffix); check(TargetPin); for (const FString& SourcePinpath : ToReturnNodePair.Value) { URigVMPin* SourcePin = GetGraph()->FindPin(*SourcePinpath); if (SourcePin && TargetPin) { if (!SourcePin->IsLinkedTo(TargetPin)) { AddLink(SourcePin, TargetPin, false); } } } } // k) re-establish all of the links going from the right of the library node for (const TPair>& FromLibraryNodePair : FromLibraryNode) { FString FromLibraryNodePinPath = FromLibraryNodePair.Key; FString FromLibraryNodePinPathSuffix; const FString* RemappedSourcePin = RemappedSourcePinsForOutputs.Find(FromLibraryNodePinPath); while (RemappedSourcePin == nullptr) { FString LastSegment; if (!URigVMPin::SplitPinPathAtEnd(FromLibraryNodePinPath, FromLibraryNodePinPath, LastSegment)) { break; } if (FromLibraryNodePinPathSuffix.IsEmpty()) { FromLibraryNodePinPathSuffix = LastSegment; } else { FromLibraryNodePinPathSuffix = URigVMPin::JoinPinPath(LastSegment, FromLibraryNodePinPathSuffix); } RemappedSourcePin = RemappedSourcePinsForOutputs.Find(FromLibraryNodePinPath); } if (RemappedSourcePin == nullptr) { continue; } FString RemappedSourcePinPath = *RemappedSourcePin; if (!FromLibraryNodePinPathSuffix.IsEmpty()) { RemappedSourcePinPath = URigVMPin::JoinPinPath(RemappedSourcePinPath, FromLibraryNodePinPathSuffix); } // remap the top level pin in case we need to insert a reroute FString ReturnPinName, ReturnPinPathSuffix; if (!URigVMPin::SplitPinPathAtStart(FromLibraryNodePair.Key, ReturnPinName, ReturnPinPathSuffix)) { ReturnPinName = FromLibraryNodePair.Key; ReturnPinPathSuffix.Reset(); } if (ReroutedOutputPins.Contains(ReturnPinName)) { URigVMPin* ReroutedPin = ReroutedOutputPins.FindChecked(ReturnPinName); URigVMPin* SourcePin = ReturnPinPathSuffix.IsEmpty() ? ReroutedPin : ReroutedPin->FindSubPin(ReturnPinPathSuffix); check(SourcePin); RemappedSourcePinPath = SourcePin->GetPinPath(); } for (const FString& FromLibraryNodeTargetPinPath : FromLibraryNodePair.Value) { URigVMPin* SourcePin = GetGraph()->FindPin(*RemappedSourcePinPath); URigVMPin* TargetPin = GetGraph()->FindPin(FromLibraryNodeTargetPinPath); if (SourcePin && TargetPin) { if (!SourcePin->IsLinkedTo(TargetPin)) { AddLink(SourcePin, TargetPin, false); } } } } // l) remove the library node from the graph RemoveNode(InNode, false, true); if (bSetupUndoRedo) { for (URigVMNode* ExpandedNode : ExpandedNodes) { ExpandAction.ExpandedNodePaths.Add(ExpandedNode->GetName()); } ActionStack->EndAction(ExpandAction); } return ExpandedNodes; } FName URigVMController::PromoteCollapseNodeToFunctionReferenceNode(const FName& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand, const FString& InExistingFunctionDefinitionPath) { if (!IsValidGraph()) { return NAME_None; } if (!bIsTransacting && !IsGraphEditable()) { return NAME_None; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMNode* Result = PromoteCollapseNodeToFunctionReferenceNode(Cast(Graph->FindNodeByName(InNodeName)), bSetupUndoRedo, InExistingFunctionDefinitionPath); if (Result) { if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').promote_collapse_node_to_function_reference_node('%s')"), *GraphName, *GetSanitizedNodeName(InNodeName.ToString()))); } return Result->GetFName(); } return NAME_None; } FName URigVMController::PromoteFunctionReferenceNodeToCollapseNode(const FName& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand, bool bRemoveFunctionDefinition) { if (!IsValidGraph()) { return NAME_None; } if (!bIsTransacting && !IsGraphEditable()) { return NAME_None; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMNode* Result = PromoteFunctionReferenceNodeToCollapseNode(Cast(Graph->FindNodeByName(InNodeName)), bSetupUndoRedo, bRemoveFunctionDefinition); if (Result) { return Result->GetFName(); } return NAME_None; } URigVMFunctionReferenceNode* URigVMController::PromoteCollapseNodeToFunctionReferenceNode(URigVMCollapseNode* InCollapseNode, bool bSetupUndoRedo, const FString& InExistingFunctionDefinitionPath) { if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } if (!IsValidNodeForGraph(InCollapseNode)) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMFunctionLibrary* FunctionLibrary = Graph->GetDefaultFunctionLibrary(); if (FunctionLibrary == nullptr) { return nullptr; } for (URigVMPin* Pin : InCollapseNode->GetPins()) { if (Pin->IsWildCard()) { ReportAndNotifyErrorf(TEXT("Cannot create function %s because it contains a wildcard pin %s"), *InCollapseNode->GetName(), *Pin->GetName()); return nullptr; } } FRigVMControllerCompileBracketScope CompileScope(this); URigVMFunctionReferenceNode* FunctionRefNode = nullptr; // Create Function URigVMLibraryNode* FunctionDefinition = nullptr; if (!InExistingFunctionDefinitionPath.IsEmpty() && ensureAlwaysMsgf(!FPackageName::IsShortPackageName(InExistingFunctionDefinitionPath), TEXT("Expected full path name for function definition path: \"%s\"), *InExistingFunctionDefinitionPath"))) { FunctionDefinition = FindObject(nullptr, *InExistingFunctionDefinitionPath); } if (FunctionDefinition == nullptr) { { FRigVMControllerGraphGuard GraphGuard(this, FunctionLibrary, false); const FString FunctionName = GetValidNodeName(InCollapseNode->GetName()); FunctionDefinition = AddFunctionToLibrary( *FunctionName, InCollapseNode->GetPins().ContainsByPredicate([](const URigVMPin* Pin) -> bool { return Pin->IsExecuteContext() && (Pin->GetDirection() == ERigVMPinDirection::IO); }), FVector2D::ZeroVector, false); } // Add interface pins in function if (FunctionDefinition) { FRigVMControllerGraphGuard GraphGuard(this, FunctionDefinition->GetContainedGraph(), false); for(const URigVMPin* Pin : InCollapseNode->GetPins()) { AddExposedPin(Pin->GetFName(), Pin->GetDirection(), Pin->GetCPPType(), (Pin->GetCPPTypeObject() ? *Pin->GetCPPTypeObject()->GetPathName() : TEXT("")), Pin->GetDefaultValue(), false); } } } // Copy inner graph from collapsed node to function if (FunctionDefinition) { FString TextContent; { FRigVMControllerGraphGuard GraphGuard(this, InCollapseNode->GetContainedGraph(), false); TArray NodeNames; for (const URigVMNode* Node : InCollapseNode->GetContainedNodes()) { if (Node->IsInjected()) { continue; } NodeNames.Add(Node->GetFName()); } TextContent = ExportNodesToText(NodeNames); } { FRigVMControllerGraphGuard GraphGuard(this, FunctionDefinition->GetContainedGraph(), false); ImportNodesFromText(TextContent, false); if (FunctionDefinition->GetContainedGraph()->GetEntryNode() && InCollapseNode->GetContainedGraph()->GetEntryNode()) { SetNodePosition(FunctionDefinition->GetContainedGraph()->GetEntryNode(), InCollapseNode->GetContainedGraph()->GetEntryNode()->GetPosition(), false); } if (FunctionDefinition->GetContainedGraph()->GetReturnNode() && InCollapseNode->GetContainedGraph()->GetReturnNode()) { SetNodePosition(FunctionDefinition->GetContainedGraph()->GetReturnNode(), InCollapseNode->GetContainedGraph()->GetReturnNode()->GetPosition(), false); } for (const URigVMLink* InnerLink : InCollapseNode->GetContainedGraph()->GetLinks()) { URigVMPin* SourcePin = InCollapseNode->GetGraph()->FindPin(InnerLink->SourcePinPath); URigVMPin* TargetPin = InCollapseNode->GetGraph()->FindPin(InnerLink->TargetPinPath); if (SourcePin && TargetPin) { if (!SourcePin->IsLinkedTo(TargetPin)) { AddLink(InnerLink->SourcePinPath, InnerLink->TargetPinPath, false); } } } } } // Remove collapse node, add function reference, and add external links if (FunctionDefinition) { FString NodeName = InCollapseNode->GetName(); FVector2D NodePosition = InCollapseNode->GetPosition(); TMap PinStates = GetPinStates(InCollapseNode); TArray Links = InCollapseNode->GetLinks(); TArray< TPair< FString, FString > > LinkPaths; for (URigVMLink* Link : Links) { LinkPaths.Add(TPair< FString, FString >(Link->GetSourcePin()->GetPinPath(), Link->GetTargetPin()->GetPinPath())); } RemoveNode(InCollapseNode, false, true); FunctionRefNode = AddFunctionReferenceNode(FunctionDefinition, NodePosition, NodeName, false); if (FunctionRefNode) { ApplyPinStates(FunctionRefNode, PinStates); for (const TPair& LinkPath : LinkPaths) { AddLink(LinkPath.Key, LinkPath.Value, false); } } if (bSetupUndoRedo) { ActionStack->AddAction(FRigVMPromoteNodeAction(InCollapseNode, NodeName, FString())); } } return FunctionRefNode; } URigVMCollapseNode* URigVMController::PromoteFunctionReferenceNodeToCollapseNode(URigVMFunctionReferenceNode* InFunctionRefNode, bool bSetupUndoRedo, bool bRemoveFunctionDefinition) { if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } if (!IsValidNodeForGraph(InFunctionRefNode)) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); const URigVMCollapseNode* FunctionDefinition = Cast(InFunctionRefNode->LoadReferencedNode()); if (FunctionDefinition == nullptr) { return nullptr; } // Find local variables that need to be added as member variables. If member variables of same name and type already // exist, they will be reused. If a local variable is not used, it will not be created. TArray LocalVariables = FunctionDefinition->GetContainedGraph()->LocalVariables; TArray CurrentVariables = GetAllVariables(); TArray VariablesToAdd; for (const URigVMNode* Node : FunctionDefinition->GetContainedGraph()->GetNodes()) { if (const URigVMVariableNode* VariableNode = Cast(Node)) { for (FRigVMGraphVariableDescription& LocalVariable : LocalVariables) { if (LocalVariable.Name == VariableNode->GetVariableName()) { bool bVariableExists = false; bool bVariableIncompatible = false; FRigVMExternalVariable LocalVariableExternalType = LocalVariable.ToExternalVariable(); for (FRigVMExternalVariable& CurrentVariable : CurrentVariables) { if (CurrentVariable.Name == LocalVariable.Name) { if (CurrentVariable.TypeName != LocalVariableExternalType.TypeName || CurrentVariable.TypeObject != LocalVariableExternalType.TypeObject || CurrentVariable.bIsArray != LocalVariableExternalType.bIsArray) { bVariableIncompatible = true; } bVariableExists = true; break; } } if (!bVariableExists) { VariablesToAdd.Add(LocalVariable); } else if(bVariableIncompatible) { ReportErrorf(TEXT("Found variable %s of incompatible type with a local variable inside function %s"), *LocalVariable.Name.ToString(), *FunctionDefinition->GetName()); return nullptr; } break; } } } } FRigVMControllerCompileBracketScope CompileScope(this); FString NodeName = InFunctionRefNode->GetName(); FVector2D NodePosition = InFunctionRefNode->GetPosition(); TMap PinStates = GetPinStates(InFunctionRefNode); TArray Links = InFunctionRefNode->GetLinks(); TArray< TPair< FString, FString > > LinkPaths; for (URigVMLink* Link : Links) { LinkPaths.Add(TPair< FString, FString >(Link->GetSourcePin()->GetPinPath(), Link->GetTargetPin()->GetPinPath())); } if (bSetupUndoRedo) { ActionStack->AddAction(FRigVMPromoteNodeAction(InFunctionRefNode, NodeName, FunctionDefinition->GetPathName())); } RemoveNode(InFunctionRefNode, false, true); if (RequestNewExternalVariableDelegate.IsBound()) { for (const FRigVMGraphVariableDescription& OldVariable : VariablesToAdd) { RequestNewExternalVariableDelegate.Execute(OldVariable, false, false); } } URigVMCollapseNode* CollapseNode = DuplicateObject(FunctionDefinition, Graph, *NodeName); if(CollapseNode) { { FRigVMControllerGraphGuard Guard(this, CollapseNode->GetContainedGraph(), false); TGuardValue SuspendNotifications(bSuspendNotifications, true); ReattachLinksToPinObjects(); for (URigVMNode* Node : CollapseNode->GetContainedGraph()->GetNodes()) { if (URigVMVariableNode* VariableNode = Cast(Node)) { RepopulatePinsOnNode(VariableNode, true, false, true); } } CollapseNode->GetContainedGraph()->LocalVariables.Empty(); } CollapseNode->NodeColor = FLinearColor::White; CollapseNode->Position = NodePosition; Graph->Nodes.Add(CollapseNode); Notify(ERigVMGraphNotifType::NodeAdded, CollapseNode); ApplyPinStates(CollapseNode, PinStates); for (const TPair& LinkPath : LinkPaths) { AddLink(LinkPath.Key, LinkPath.Value, false); } } if(bRemoveFunctionDefinition) { FRigVMControllerGraphGuard Guard(this, FunctionDefinition->GetRootGraph(), false); RemoveFunctionFromLibrary(FunctionDefinition->GetFName(), false); } return CollapseNode; } void URigVMController::SetReferencedFunction(URigVMFunctionReferenceNode* InFunctionRefNode, URigVMLibraryNode* InNewReferencedNode, bool bSetupUndoRedo) { if(!IsValidGraph()) { return; } if (!bIsTransacting && !IsGraphEditable()) { return; } FRigVMGraphFunctionHeader OldReferencedNode = InFunctionRefNode->GetReferencedFunctionHeader(); InFunctionRefNode->ReferencedFunctionHeader = InNewReferencedNode->GetFunctionHeader(); if(!(OldReferencedNode == InFunctionRefNode->ReferencedFunctionHeader)) { if(URigVMBuildData* BuildData = URigVMBuildData::Get()) { BuildData->UnregisterFunctionReference(OldReferencedNode.LibraryPointer, InFunctionRefNode); BuildData->RegisterFunctionReference(InFunctionRefNode->ReferencedFunctionHeader.LibraryPointer, InFunctionRefNode); } } FRigVMControllerGraphGuard GraphGuard(this, InFunctionRefNode->GetGraph(), false); GetGraph()->Notify(ERigVMGraphNotifType::NodeReferenceChanged, InFunctionRefNode); } void URigVMController::RefreshFunctionPins(URigVMNode* InNode) { if (InNode == nullptr) { return; } URigVMFunctionEntryNode* EntryNode = Cast(InNode); URigVMFunctionReturnNode* ReturnNode = Cast(InNode); if (EntryNode || ReturnNode) { RepopulatePinsOnNode(InNode, false, false, true); } } void URigVMController::ReportRemovedLink(const FString& InSourcePinPath, const FString& InTargetPinPath) { if(bSuspendNotifications) { return; } if(!IsValidGraph()) { return; } const URigVMPin* TargetPin = GetGraph()->FindPin(InTargetPinPath); FString TargetNodeName, TargetSegmentPath; if(!URigVMPin::SplitPinPathAtStart(InTargetPinPath, TargetNodeName, TargetSegmentPath)) { TargetSegmentPath = InTargetPinPath; } ReportWarningf(TEXT("Link '%s' -> '%s' was removed."), *InSourcePinPath, *InTargetPinPath); SendUserFacingNotification( FString::Printf(TEXT("Link to target pin '%s' was removed."), *TargetSegmentPath), 0.f, TargetPin, TEXT("MessageLog.Note") ); } bool URigVMController::RemoveNode(URigVMNode* InNode, bool bSetupUndoRedo, bool bRecursive, bool bPrintPythonCommand, bool bRelinkPins) { if (!bIsTransacting && !IsGraphEditable()) { return false; } if (!IsValidNodeForGraph(InNode)) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); if (InNode->IsInjected()) { URigVMInjectionInfo* InjectionInfo = InNode->GetInjectionInfo(); if (InjectionInfo->GetPin()->GetInjectedNodes().Last() != InjectionInfo) { ReportErrorf(TEXT("Cannot remove injected node %s as it is not the last injection on the pin"), *InNode->GetNodePath()); return false; } } if (bSetupUndoRedo) { // don't allow deletion of function entry / return nodes if ((Cast(InNode) != nullptr && InNode->GetName() == TEXT("Entry")) || (Cast(InNode) != nullptr && InNode->GetName() == TEXT("Return"))) { // due to earlier bugs in the copy & paste code entry and return nodes could end up in // root graphs - in those cases we allow deletion if(!Graph->IsRootGraph()) { return false; } } // check if the operation will cause to dirty assets if(URigVMVariableNode* VariableNode = Cast(InNode)) { if(URigVMFunctionLibrary* OuterLibrary = Graph->GetTypedOuter()) { if(URigVMLibraryNode* OuterFunction = OuterLibrary->FindFunctionForNode(Graph->GetTypedOuter())) { const FName VariableToRemove = VariableNode->GetVariableName(); bool bIsLocalVariable = false; for (FRigVMGraphVariableDescription VariableDescription : OuterFunction->GetContainedGraph()->LocalVariables) { if (VariableDescription.Name == VariableToRemove) { bIsLocalVariable = true; break; } } if (!bIsLocalVariable) { TArray ExternalVariablesWithoutVariableNode; { URigVMGraph* EditedGraph = InNode->GetGraph(); TGuardValue> TemporaryRemoveNodes(EditedGraph->Nodes, TArray()); ExternalVariablesWithoutVariableNode = EditedGraph->GetExternalVariables(); } bool bFoundExternalVariable = false; for(const FRigVMExternalVariable& ExternalVariable : ExternalVariablesWithoutVariableNode) { if(ExternalVariable.Name == VariableToRemove) { bFoundExternalVariable = true; break; } } if(!bFoundExternalVariable) { FRigVMControllerGraphGuard Guard(this, OuterFunction->GetContainedGraph(), false); if(RequestBulkEditDialogDelegate.IsBound()) { FRigVMController_BulkEditResult Result = RequestBulkEditDialogDelegate.Execute(OuterFunction, ERigVMControllerBulkEditType::RemoveVariable); if(Result.bCanceled) { return false; } bSetupUndoRedo = Result.bSetupUndoRedo; } } } } } } } TGuardValue GuardCompactness(bIgnoreRerouteCompactnessChanges, true); FRigVMControllerCompileBracketScope CompileScope(this); FRigVMBaseAction Action; if (bSetupUndoRedo) { Action = FRigVMBaseAction(); Action.Title = FString::Printf(TEXT("Remove %s Node"), *InNode->GetNodeTitle()); ActionStack->BeginAction(Action); } if (URigVMInjectionInfo* InjectionInfo = InNode->GetInjectionInfo()) { URigVMPin* Pin = InjectionInfo->GetPin(); check(Pin); if (!EjectNodeFromPin(Pin->GetPinPath(), bSetupUndoRedo)) { ActionStack->CancelAction(Action, this); return false; } if (InjectionInfo->bInjectedAsInput) { if (InjectionInfo->InputPin) { URigVMPin* LastInputPin = Pin->GetPinForLink(); RewireLinks(InjectionInfo->InputPin, LastInputPin, true, bSetupUndoRedo); } } else { if (InjectionInfo->OutputPin) { URigVMPin* LastOutputPin = Pin->GetPinForLink(); RewireLinks(InjectionInfo->OutputPin, LastOutputPin, false, bSetupUndoRedo); } } } if (URigVMLibraryNode* LibraryNode = Cast(InNode)) { // If we are removing a reference, remove the function references to this node in the function library if(URigVMFunctionReferenceNode* FunctionReferenceNode = Cast(LibraryNode)) { if(URigVMBuildData* BuildData = URigVMBuildData::Get()) { BuildData->UnregisterFunctionReference(FunctionReferenceNode->GetReferencedFunctionHeader().LibraryPointer, FunctionReferenceNode); } } // If we are removing a function, remove all the references first else if (URigVMFunctionLibrary* FunctionLibrary = Cast(LibraryNode->GetGraph())) { if(URigVMBuildData* BuildData = URigVMBuildData::Get()) { FRigVMGraphFunctionIdentifier Identifier = LibraryNode->GetFunctionIdentifier(); if (const FRigVMFunctionReferenceArray* ReferencesEntry = BuildData->FindFunctionReferences(Identifier)) { // make a copy since we'll be modifying the array TArray< TSoftObjectPtr > FunctionReferences = ReferencesEntry->FunctionReferences; for (const TSoftObjectPtr& FunctionReferencePtr : FunctionReferences) { if (!ReferencesEntry->FunctionReferences.Contains(FunctionReferencePtr)) { continue; } if (FunctionReferencePtr.IsValid()) { FRigVMControllerGraphGuard GraphGuard(this, FunctionReferencePtr->GetGraph(), bSetupUndoRedo); RemoveNode(FunctionReferencePtr.Get()); } } } BuildData->GraphFunctionReferences.Remove(Identifier); } for(const auto& Pair : FunctionLibrary->LocalizedFunctions) { if(Pair.Value == LibraryNode) { FunctionLibrary->LocalizedFunctions.Remove(Pair.Key); break; } } if (FunctionLibrary->PublicFunctionNames.Contains(LibraryNode->GetFName())) { FunctionLibrary->PublicFunctionNames.Remove(LibraryNode->GetFName()); if (bSetupUndoRedo) { ActionStack->AddAction(FRigVMMarkFunctionPublicAction(LibraryNode->GetFName(), true)); } } } } // try to reconnect source and target nodes based on the current links if (bRelinkPins) { RelinkSourceAndTargetPins(InNode, bSetupUndoRedo); } if (bSetupUndoRedo || bRecursive) { SelectNode(InNode, false, bSetupUndoRedo); for (URigVMPin* Pin : InNode->GetPins()) { // Remove injected nodes while (Pin->HasInjectedNodes()) { RemoveInjectedNode(Pin->GetPinPath(), Pin->GetDirection() != ERigVMPinDirection::Output, bSetupUndoRedo); } // breaking links also removes injected nodes BreakAllLinks(Pin, true, bSetupUndoRedo); BreakAllLinks(Pin, false, bSetupUndoRedo); BreakAllLinksRecursive(Pin, true, false, bSetupUndoRedo); BreakAllLinksRecursive(Pin, false, false, bSetupUndoRedo); } if (URigVMCollapseNode* CollapseNode = Cast(InNode)) { URigVMGraph* SubGraph = CollapseNode->GetContainedGraph(); FRigVMControllerGraphGuard GraphGuard(this, SubGraph, bSetupUndoRedo); TArray ContainedNodes = SubGraph->GetNodes(); for (URigVMNode* ContainedNode : ContainedNodes) { if(Cast(ContainedNode) != nullptr || Cast(ContainedNode) != nullptr) { continue; } RemoveNode(ContainedNode, bSetupUndoRedo, bRecursive); } } if (bSetupUndoRedo) { ActionStack->AddAction(FRigVMRemoveNodeAction(InNode, this)); } } Graph->Nodes.Remove(InNode); if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } Notify(ERigVMGraphNotifType::NodeRemoved, InNode); if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); if (Graph->IsA()) { const FString NodeName = GetSanitizedNodeName(InNode->GetName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("library_controller.remove_function_from_library('%s')"), *NodeName)); } else { const FString NodePath = GetSanitizedPinPath(InNode->GetNodePath()); FString PythonCmd = FString::Printf(TEXT("blueprint.get_controller_by_name('%s')."), *GraphName ); PythonCmd += bRelinkPins ? FString::Printf(TEXT("remove_node_by_name('%s', relink_pins=True)"), *NodePath) : FString::Printf(TEXT("remove_node_by_name('%s')"), *NodePath); RigVMPythonUtils::Print(GetGraphOuterName(), PythonCmd ); } } if (URigVMVariableNode* VariableNode = Cast(InNode)) { Notify(ERigVMGraphNotifType::VariableRemoved, VariableNode); } if (URigVMInjectionInfo* InjectionInfo = InNode->GetInjectionInfo()) { DestroyObject(InjectionInfo); } if (URigVMCollapseNode* CollapseNode = Cast(InNode)) { DestroyObject(CollapseNode->GetContainedGraph()); } DestroyObject(InNode); if (bSetupUndoRedo) { ActionStack->EndAction(Action); } return true; } bool URigVMController::RemoveNodeByName(const FName& InNodeName, bool bSetupUndoRedo, bool bRecursive, bool bPrintPythonCommand, bool bRelinkPins) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); return RemoveNode(Graph->FindNodeByName(InNodeName), bSetupUndoRedo, bRecursive, bPrintPythonCommand, bRelinkPins); } bool URigVMController::RenameNode(URigVMNode* InNode, const FName& InNewName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!bIsTransacting && !IsGraphEditable()) { return false; } if (!IsValidNodeForGraph(InNode)) { return false; } FName ValidNewName = *GetValidNodeName(InNewName.ToString()); if (InNode->GetFName() == ValidNewName) { return false; } const FString OldName = InNode->GetName(); FRigVMRenameNodeAction Action; if (bSetupUndoRedo) { Action = FRigVMRenameNodeAction(InNode->GetFName(), ValidNewName); ActionStack->BeginAction(Action); } // loop over all links and remove them TArray Links = InNode->GetLinks(); for (URigVMLink* Link : Links) { Link->PrepareForCopy(); Notify(ERigVMGraphNotifType::LinkRemoved, Link); } const FSoftObjectPath PreviousObjectPath(InNode); InNode->PreviousName = InNode->GetFName(); if (!RenameObject(InNode, *ValidNewName.ToString())) { ActionStack->CancelAction(Action, this); return false; } Notify(ERigVMGraphNotifType::NodeRenamed, InNode); // update the links once more for (URigVMLink* Link : Links) { Link->PrepareForCopy(); Notify(ERigVMGraphNotifType::LinkAdded, Link); } if(URigVMLibraryNode* LibraryNode = Cast(InNode)) { if (URigVMFunctionLibrary* FunctionLibrary = Cast(LibraryNode->GetGraph())) { // rename each function reference if(URigVMBuildData* BuildData = URigVMBuildData::Get()) { BuildData->ForEachFunctionReference(LibraryNode->GetFunctionIdentifier(), [this, InNewName](URigVMFunctionReferenceNode* ReferenceNode) { FRigVMControllerGraphGuard GraphGuard(this, ReferenceNode->GetGraph(), false); RenameNode(ReferenceNode, InNewName, false); }); } if (FunctionLibrary->PublicFunctionNames.Contains(InNode->PreviousName)) { FunctionLibrary->PublicFunctionNames.Remove(InNode->PreviousName); FunctionLibrary->PublicFunctionNames.Add(ValidNewName); } } } if (bSetupUndoRedo) { ActionStack->EndAction(Action); } if (GetGraph()->IsA()) { RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("library_controller.rename_function('%s', '%s')"), *OldName, *InNewName.ToString())); } return true; } bool URigVMController::SelectNode(URigVMNode* InNode, bool bSelect, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!bIsTransacting && !IsGraphEditable()) { return false; } if (!IsValidNodeForGraph(InNode)) { return false; } if (InNode->IsSelected() == bSelect) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); TArray NewSelection = Graph->GetSelectNodes(); if (bSelect) { NewSelection.AddUnique(InNode->GetFName()); } else { NewSelection.Remove(InNode->GetFName()); } return SetNodeSelection(NewSelection, bSetupUndoRedo, bPrintPythonCommand); } bool URigVMController::SelectNodeByName(const FName& InNodeName, bool bSelect, bool bSetupUndoRedo) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); return SelectNode(Graph->FindNodeByName(InNodeName), bSelect, bSetupUndoRedo); } bool URigVMController::ClearNodeSelection(bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } return SetNodeSelection(TArray(), bSetupUndoRedo, bPrintPythonCommand); } bool URigVMController::SetNodeSelection(const TArray& InNodeNames, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); FRigVMSetNodeSelectionAction Action; if (bSetupUndoRedo) { Action = FRigVMSetNodeSelectionAction(Graph, InNodeNames); ActionStack->BeginAction(Action); } bool bSelectionChanged = false; TArray PreviousSelection = Graph->GetSelectNodes(); for (const FName& PreviouslySelectedNode : PreviousSelection) { if (!InNodeNames.Contains(PreviouslySelectedNode)) { if(Graph->SelectedNodes.Remove(PreviouslySelectedNode) > 0) { bSelectionChanged = true; } } } for (const FName& InNodeName : InNodeNames) { if (URigVMNode* NodeToSelect = Graph->FindNodeByName(InNodeName)) { int32 PreviousNum = Graph->SelectedNodes.Num(); Graph->SelectedNodes.AddUnique(InNodeName); if (PreviousNum != Graph->SelectedNodes.Num()) { bSelectionChanged = true; } } } if (bSetupUndoRedo) { if (bSelectionChanged) { const TArray& SelectedNodes = Graph->GetSelectNodes(); if (SelectedNodes.Num() == 0) { Action.Title = TEXT("Deselect all nodes."); } else { if (SelectedNodes.Num() == 1) { Action.Title = FString::Printf(TEXT("Selected node '%s'."), *SelectedNodes[0].ToString()); } else { Action.Title = TEXT("Selected multiple nodes."); } } ActionStack->EndAction(Action); } else { ActionStack->CancelAction(Action, this); } } if (bSelectionChanged) { Notify(ERigVMGraphNotifType::NodeSelectionChanged, nullptr); } if (bPrintPythonCommand) { FString ArrayStr = TEXT("["); for (auto It = InNodeNames.CreateConstIterator(); It; ++It) { ArrayStr += TEXT("'") + GetSanitizedNodeName(It->ToString()) + TEXT("'"); if (It.GetIndex() < InNodeNames.Num() - 1) { ArrayStr += TEXT(", "); } } ArrayStr += TEXT("]"); const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').set_node_selection(%s)"), *GraphName, *ArrayStr)); } return bSelectionChanged; } bool URigVMController::SetNodePosition(URigVMNode* InNode, const FVector2D& InPosition, bool bSetupUndoRedo, bool bMergeUndoAction, bool bPrintPythonCommand) { if (!bIsTransacting && !IsGraphEditable()) { return false; } if (!IsValidNodeForGraph(InNode)) { return false; } if ((InNode->Position - InPosition).IsNearlyZero()) { return false; } FRigVMSetNodePositionAction Action; if (bSetupUndoRedo) { Action = FRigVMSetNodePositionAction(InNode, InPosition); Action.Title = FString::Printf(TEXT("Set Node Position")); ActionStack->BeginAction(Action); } InNode->Position = InPosition; Notify(ERigVMGraphNotifType::NodePositionChanged, InNode); if (bSetupUndoRedo) { ActionStack->EndAction(Action, bMergeUndoAction); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); const FString NodePath = GetSanitizedPinPath(InNode->GetNodePath()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').set_node_position_by_name('%s', %s)"), *GraphName, *NodePath, *RigVMPythonUtils::Vector2DToPythonString(InPosition))); } return true; } bool URigVMController::SetNodePositionByName(const FName& InNodeName, const FVector2D& InPosition, bool bSetupUndoRedo, bool bMergeUndoAction, bool bPrintPythonCommand) { if(!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMNode* Node = Graph->FindNodeByName(InNodeName); return SetNodePosition(Node, InPosition, bSetupUndoRedo, bMergeUndoAction, bPrintPythonCommand); } bool URigVMController::SetNodeSize(URigVMNode* InNode, const FVector2D& InSize, bool bSetupUndoRedo, bool bMergeUndoAction, bool bPrintPythonCommand) { if (!bIsTransacting && !IsGraphEditable()) { return false; } if (!IsValidNodeForGraph(InNode)) { return false; } if ((InNode->Size - InSize).IsNearlyZero()) { return false; } FRigVMSetNodeSizeAction Action; if (bSetupUndoRedo) { Action = FRigVMSetNodeSizeAction(InNode, InSize); Action.Title = FString::Printf(TEXT("Set Node Size")); ActionStack->BeginAction(Action); } InNode->Size = InSize; Notify(ERigVMGraphNotifType::NodeSizeChanged, InNode); if (bSetupUndoRedo) { ActionStack->EndAction(Action, bMergeUndoAction); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); const FString NodePath = GetSanitizedPinPath(InNode->GetNodePath()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').set_node_size_by_name('%s', %s)"), *GraphName, *NodePath, *RigVMPythonUtils::Vector2DToPythonString(InSize))); } return true; } bool URigVMController::SetNodeSizeByName(const FName& InNodeName, const FVector2D& InSize, bool bSetupUndoRedo, bool bMergeUndoAction, bool bPrintPythonCommand) { if(!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMNode* Node = Graph->FindNodeByName(InNodeName); return SetNodeSize(Node, InSize, bSetupUndoRedo, bMergeUndoAction, bPrintPythonCommand); } bool URigVMController::SetNodeColor(URigVMNode* InNode, const FLinearColor& InColor, bool bSetupUndoRedo, bool bMergeUndoAction, bool bPrintPythonCommand) { if (!bIsTransacting && !IsGraphEditable()) { return false; } if (!IsValidNodeForGraph(InNode)) { return false; } if ((FVector4(InNode->NodeColor) - FVector4(InColor)).IsNearlyZero3()) { return false; } FRigVMSetNodeColorAction Action; if (bSetupUndoRedo) { Action = FRigVMSetNodeColorAction(InNode, InColor); Action.Title = FString::Printf(TEXT("Set Node Color")); ActionStack->BeginAction(Action); } InNode->NodeColor = InColor; Notify(ERigVMGraphNotifType::NodeColorChanged, InNode); if (URigVMLibraryNode* LibraryNode = Cast(InNode)) { if (URigVMFunctionLibrary* FunctionLibrary = Cast(LibraryNode->GetGraph())) { FunctionLibrary->ForEachReference(LibraryNode->GetFName(), [this](URigVMFunctionReferenceNode* ReferenceNode) { FRigVMControllerGraphGuard GraphGuard(this, ReferenceNode->GetGraph(), false); Notify(ERigVMGraphNotifType::NodeColorChanged, ReferenceNode); }); } } if (bSetupUndoRedo) { ActionStack->EndAction(Action, bMergeUndoAction); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); const FString NodePath = GetSanitizedPinPath(InNode->GetNodePath()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').set_node_color_by_name('%s', %s)"), *GraphName, *NodePath, *RigVMPythonUtils::LinearColorToPythonString(InColor))); } return true; } bool URigVMController::SetNodeColorByName(const FName& InNodeName, const FLinearColor& InColor, bool bSetupUndoRedo, bool bMergeUndoAction) { if(!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMNode* Node = Graph->FindNodeByName(InNodeName); return SetNodeColor(Node, InColor, bSetupUndoRedo, bMergeUndoAction); } bool URigVMController::SetNodeCategory(URigVMCollapseNode* InNode, const FString& InCategory, bool bSetupUndoRedo, bool bMergeUndoAction, bool bPrintPythonCommand) { if (!bIsTransacting && !IsGraphEditable()) { return false; } if (!IsValidNodeForGraph(InNode)) { return false; } if (InNode->GetNodeCategory() == InCategory) { return false; } FRigVMSetNodeCategoryAction Action; if (bSetupUndoRedo) { Action = FRigVMSetNodeCategoryAction(InNode, InCategory); Action.Title = FString::Printf(TEXT("Set Node Category")); ActionStack->BeginAction(Action); } InNode->NodeCategory = InCategory; Notify(ERigVMGraphNotifType::NodeCategoryChanged, InNode); if (bSetupUndoRedo) { ActionStack->EndAction(Action, bMergeUndoAction); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); const FString NodePath = GetSanitizedPinPath(InNode->GetNodePath()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').set_node_category_by_name('%s', '%s')"), *GraphName, *NodePath, *InCategory)); } return true; } bool URigVMController::SetNodeCategoryByName(const FName& InNodeName, const FString& InCategory, bool bSetupUndoRedo, bool bMergeUndoAction) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMCollapseNode* Node = Cast(Graph->FindNodeByName(InNodeName)); return SetNodeCategory(Node, InCategory, bSetupUndoRedo, bMergeUndoAction); } bool URigVMController::SetNodeKeywords(URigVMCollapseNode* InNode, const FString& InKeywords, bool bSetupUndoRedo, bool bMergeUndoAction, bool bPrintPythonCommand) { if (!bIsTransacting && !IsGraphEditable()) { return false; } if (!IsValidNodeForGraph(InNode)) { return false; } if (InNode->GetNodeKeywords() == InKeywords) { return false; } FRigVMSetNodeKeywordsAction Action; if (bSetupUndoRedo) { Action = FRigVMSetNodeKeywordsAction(InNode, InKeywords); Action.Title = FString::Printf(TEXT("Set Node Keywords")); ActionStack->BeginAction(Action); } InNode->NodeKeywords = InKeywords; Notify(ERigVMGraphNotifType::NodeKeywordsChanged, InNode); if (bSetupUndoRedo) { ActionStack->EndAction(Action, bMergeUndoAction); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); const FString NodePath = GetSanitizedPinPath(InNode->GetNodePath()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').set_node_keywords_by_name('%s', '%s')"), *GraphName, *NodePath, *InKeywords)); } return true; } bool URigVMController::SetNodeKeywordsByName(const FName& InNodeName, const FString& InKeywords, bool bSetupUndoRedo, bool bMergeUndoAction) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMCollapseNode* Node = Cast(Graph->FindNodeByName(InNodeName)); return SetNodeKeywords(Node, InKeywords, bSetupUndoRedo, bMergeUndoAction); } bool URigVMController::SetNodeDescription(URigVMCollapseNode* InNode, const FString& InDescription, bool bSetupUndoRedo, bool bMergeUndoAction, bool bPrintPythonCommand) { if (!bIsTransacting && !IsGraphEditable()) { return false; } if (!IsValidNodeForGraph(InNode)) { return false; } if (InNode->GetNodeDescription() == InDescription) { return false; } FRigVMSetNodeDescriptionAction Action; if (bSetupUndoRedo) { Action = FRigVMSetNodeDescriptionAction(InNode, InDescription); Action.Title = FString::Printf(TEXT("Set Node Description")); ActionStack->BeginAction(Action); } InNode->NodeDescription = InDescription; Notify(ERigVMGraphNotifType::NodeDescriptionChanged, InNode); if (bSetupUndoRedo) { ActionStack->EndAction(Action, bMergeUndoAction); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); const FString NodePath = GetSanitizedPinPath(InNode->GetNodePath()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').set_node_description_by_name('%s', '%s')"), *GraphName, *NodePath, *InDescription)); } return true; } bool URigVMController::SetNodeDescriptionByName(const FName& InNodeName, const FString& InDescription, bool bSetupUndoRedo, bool bMergeUndoAction) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMCollapseNode* Node = Cast(Graph->FindNodeByName(InNodeName)); return SetNodeDescription(Node, InDescription, bSetupUndoRedo, bMergeUndoAction); } bool URigVMController::SetCommentText(URigVMNode* InNode, const FString& InCommentText, const int32& InCommentFontSize, const bool& bInCommentBubbleVisible, const bool& bInCommentColorBubble, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!bIsTransacting && !IsGraphEditable()) { return false; } if (!IsValidNodeForGraph(InNode)) { return false; } if (URigVMCommentNode* CommentNode = Cast(InNode)) { if(CommentNode->CommentText == InCommentText && CommentNode->FontSize == InCommentFontSize && CommentNode->bBubbleVisible == bInCommentBubbleVisible && CommentNode->bColorBubble == bInCommentColorBubble) { return false; } FRigVMSetCommentTextAction Action; if (bSetupUndoRedo) { Action = FRigVMSetCommentTextAction(CommentNode, InCommentText, InCommentFontSize, bInCommentBubbleVisible, bInCommentColorBubble); Action.Title = FString::Printf(TEXT("Set Comment Text")); ActionStack->BeginAction(Action); } CommentNode->CommentText = InCommentText; CommentNode->FontSize = InCommentFontSize; CommentNode->bBubbleVisible = bInCommentBubbleVisible; CommentNode->bColorBubble = bInCommentColorBubble; Notify(ERigVMGraphNotifType::CommentTextChanged, InNode); if (bSetupUndoRedo) { ActionStack->EndAction(Action); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); const FString NodePath = GetSanitizedPinPath(CommentNode->GetNodePath()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').set_comment_text_by_name('%s', '%s')"), *GraphName, *NodePath, *InCommentText)); } return true; } return false; } bool URigVMController::SetCommentTextByName(const FName& InNodeName, const FString& InCommentText, const int32& InCommentFontSize, const bool& bInCommentBubbleVisible, const bool& bInCommentColorBubble, bool bSetupUndoRedo, bool bPrintPythonCommand) { if(!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMNode* Node = Graph->FindNodeByName(InNodeName); return SetCommentText(Node, InCommentText, InCommentFontSize, bInCommentBubbleVisible, bInCommentColorBubble, bSetupUndoRedo, bPrintPythonCommand); } bool URigVMController::SetRerouteCompactness(URigVMNode* InNode, bool bShowAsFullNode, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!bIsTransacting && !IsGraphEditable()) { return false; } if (!IsValidNodeForGraph(InNode)) { return false; } if (URigVMRerouteNode* RerouteNode = Cast(InNode)) { if (RerouteNode->bShowAsFullNode == bShowAsFullNode) { return false; } FRigVMSetRerouteCompactnessAction Action; if (bSetupUndoRedo) { Action = FRigVMSetRerouteCompactnessAction(RerouteNode, bShowAsFullNode); Action.Title = FString::Printf(TEXT("Set Reroute Size")); ActionStack->BeginAction(Action); } RerouteNode->bShowAsFullNode = bShowAsFullNode; Notify(ERigVMGraphNotifType::RerouteCompactnessChanged, InNode); if (bSetupUndoRedo) { ActionStack->EndAction(Action); } return true; } return false; } bool URigVMController::SetRerouteCompactnessByName(const FName& InNodeName, bool bShowAsFullNode, bool bSetupUndoRedo) { if(!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMNode* Node = Graph->FindNodeByName(InNodeName); return SetRerouteCompactness(Node, bShowAsFullNode, bSetupUndoRedo); } bool URigVMController::RenameVariable(const FName& InOldName, const FName& InNewName, bool bSetupUndoRedo) { if(!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } if (InOldName == InNewName) { ReportWarning(TEXT("RenameVariable: InOldName and InNewName are equal.")); return false; } URigVMGraph* Graph = GetGraph(); check(Graph); TArray ExistingVariables = Graph->GetVariableDescriptions(); for (const FRigVMGraphVariableDescription& ExistingVariable : ExistingVariables) { if (ExistingVariable.Name == InNewName) { ReportErrorf(TEXT("Cannot rename variable to '%s' - variable already exists."), *InNewName.ToString()); return false; } } // If there is a local variable with the old name, a rename of the blueprint member variable does not affect this graph for (FRigVMGraphVariableDescription& LocalVariable : Graph->GetLocalVariables(true)) { if (LocalVariable.Name == InOldName) { return false; } } FRigVMControllerCompileBracketScope CompileScope(this); FRigVMRenameVariableAction Action; if (bSetupUndoRedo) { Action = FRigVMRenameVariableAction(InOldName, InNewName); Action.Title = FString::Printf(TEXT("Rename Variable")); ActionStack->BeginAction(Action); } TArray RenamedNodes; for (URigVMNode* Node : Graph->Nodes) { if(URigVMVariableNode* VariableNode = Cast(Node)) { if (VariableNode->GetVariableName() == InOldName) { VariableNode->FindPin(URigVMVariableNode::VariableName)->DefaultValue = InNewName.ToString(); RenamedNodes.Add(Node); } } } for (URigVMNode* RenamedNode : RenamedNodes) { Notify(ERigVMGraphNotifType::VariableRenamed, RenamedNode); if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } } if (bSetupUndoRedo) { if (RenamedNodes.Num() > 0) { ActionStack->EndAction(Action); } else { ActionStack->CancelAction(Action, this); } } return RenamedNodes.Num() > 0; } bool URigVMController::RenameParameter(const FName& InOldName, const FName& InNewName, bool bSetupUndoRedo) { ReportWarning(TEXT("RenameParameter has been deprecated. Please use RenameVariable instead.")); return false; } void URigVMController::UpdateRerouteNodeAfterChangingLinks(URigVMPin* PinChanged, bool bSetupUndoRedo) { if (bIgnoreRerouteCompactnessChanges) { return; } if (!IsValidGraph()) { return; } URigVMRerouteNode* Node = Cast(PinChanged->GetNode()); if (Node == nullptr) { return; } int32 NbTotalSources = Node->Pins[0]->GetSourceLinks(true /* recursive */).Num(); int32 NbTotalTargets = Node->Pins[0]->GetTargetLinks(true /* recursive */).Num(); int32 NbToplevelSources = Node->Pins[0]->GetSourceLinks(false /* recursive */).Num(); int32 NbToplevelTargets = Node->Pins[0]->GetTargetLinks(false /* recursive */).Num(); bool bJustTopLevelConnections = (NbTotalSources == NbToplevelSources) && (NbTotalTargets == NbToplevelTargets); bool bOnlyConnectionsOnOneSide = (NbTotalSources == 0) || (NbTotalTargets == 0); bool bShowAsFullNode = (!bJustTopLevelConnections) || bOnlyConnectionsOnOneSide; SetRerouteCompactness(Node, bShowAsFullNode, bSetupUndoRedo); } bool URigVMController::SetPinExpansion(const FString& InPinPath, bool bIsExpanded, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMPin* Pin = Graph->FindPin(InPinPath); if (Pin == nullptr) { ReportErrorf(TEXT("Cannot find pin '%s'."), *InPinPath); return false; } const bool bSuccess = SetPinExpansion(Pin, bIsExpanded, bSetupUndoRedo); if (bSuccess && bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').set_pin_expansion('%s', %s)"), *GraphName, *GetSanitizedPinPath(InPinPath), (bIsExpanded) ? TEXT("True") : TEXT("False"))); } return bSuccess; } bool URigVMController::SetPinExpansion(URigVMPin* InPin, bool bIsExpanded, bool bSetupUndoRedo) { if (!bIsTransacting && !IsGraphEditable()) { return false; } // If there is nothing to do, just return success if (InPin->GetSubPins().Num() == 0 || InPin->IsExpanded() == bIsExpanded) { return true; } URigVMGraph* Graph = GetGraph(); check(Graph); FRigVMSetPinExpansionAction Action; if (bSetupUndoRedo) { Action = FRigVMSetPinExpansionAction(InPin, bIsExpanded); Action.Title = bIsExpanded ? TEXT("Expand Pin") : TEXT("Collapse Pin"); ActionStack->BeginAction(Action); } InPin->bIsExpanded = bIsExpanded; Notify(ERigVMGraphNotifType::PinExpansionChanged, InPin); if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } if (bSetupUndoRedo) { ActionStack->EndAction(Action); } return true; } bool URigVMController::SetPinIsWatched(const FString& InPinPath, bool bIsWatched, bool bSetupUndoRedo) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMPin* Pin = Graph->FindPin(InPinPath); if (Pin == nullptr) { ReportErrorf(TEXT("Cannot find pin '%s'."), *InPinPath); return false; } return SetPinIsWatched(Pin, bIsWatched, bSetupUndoRedo); } bool URigVMController::SetPinIsWatched(URigVMPin* InPin, bool bIsWatched, bool bSetupUndoRedo) { if (!bIsTransacting && !IsGraphEditable()) { return false; } if (!IsValidPinForGraph(InPin)) { return false; } if (InPin->GetParentPin() != nullptr) { return false; } if (InPin->RequiresWatch() == bIsWatched) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); FRigVMControllerCompileBracketScope CompileScope(this); FRigVMSetPinWatchAction Action; if (bSetupUndoRedo) { Action = FRigVMSetPinWatchAction(InPin, bIsWatched); Action.Title = bIsWatched ? TEXT("Watch Pin") : TEXT("Unwatch Pin"); ActionStack->BeginAction(Action); } InPin->bRequiresWatch = bIsWatched; Notify(ERigVMGraphNotifType::PinWatchedChanged, InPin); if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } if (bSetupUndoRedo) { ActionStack->EndAction(Action); } return true; } FString URigVMController::GetPinDefaultValue(const FString& InPinPath) { if (!IsValidGraph()) { return FString(); } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMPin* Pin = Graph->FindPin(InPinPath); if (Pin == nullptr) { ReportErrorf(TEXT("Cannot find pin '%s'."), *InPinPath); return FString(); } Pin = Pin->GetPinForLink(); return Pin->GetDefaultValue(); } bool URigVMController::SetPinDefaultValue(const FString& InPinPath, const FString& InDefaultValue, bool bResizeArrays, bool bSetupUndoRedo, bool bMergeUndoAction, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMPin* Pin = Graph->FindPin(InPinPath); if (Pin == nullptr) { ReportErrorf(TEXT("Cannot find pin '%s'."), *InPinPath); return false; } if (URigVMVariableNode* VariableNode = Cast(Pin->GetNode())) { if (Pin->GetName() == URigVMVariableNode::VariableName) { return SetVariableName(VariableNode, *InDefaultValue, bSetupUndoRedo); } } if (!SetPinDefaultValue(Pin, InDefaultValue, bResizeArrays, bSetupUndoRedo, bMergeUndoAction)) { return false; } URigVMPin* PinForLink = Pin->GetPinForLink(); if (PinForLink != Pin) { if (!SetPinDefaultValue(PinForLink, InDefaultValue, bResizeArrays, false, bMergeUndoAction)) { return false; } } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').set_pin_default_value('%s', '%s', %s)"), *GraphName, *GetSanitizedPinPath(InPinPath), *InDefaultValue, (bResizeArrays) ? TEXT("True") : TEXT("False"))); } return true; } bool URigVMController::SetPinDefaultValue(URigVMPin* InPin, const FString& InDefaultValue, bool bResizeArrays, bool bSetupUndoRedo, bool bMergeUndoAction) { if (!bIsTransacting && !IsGraphEditable()) { return false; } check(InPin); if(!InPin->IsUObject() && InPin->GetCPPType() != RigVMTypeUtils::FStringType && InPin->GetCPPType() != RigVMTypeUtils::FNameType && bValidatePinDefaults) { ensure(!InDefaultValue.IsEmpty()); } URigVMGraph* Graph = GetGraph(); check(Graph); if (bValidatePinDefaults) { if (!InPin->IsValidDefaultValue(InDefaultValue)) { return false; } } FRigVMControllerCompileBracketScope CompileScope(this); FRigVMSetPinDefaultValueAction Action; if (bSetupUndoRedo) { Action = FRigVMSetPinDefaultValueAction(InPin, InDefaultValue); Action.Title = FString::Printf(TEXT("Set Pin Default Value")); ActionStack->BeginAction(Action); } const FString ClampedDefaultValue = InPin->IsRootPin() ? InPin->ClampDefaultValueFromMetaData(InDefaultValue) : InDefaultValue; bool bSetPinDefaultValueSucceeded = false; if (InPin->IsArray()) { if (ShouldPinBeUnfolded(InPin)) { TArray Elements = URigVMPin::SplitDefaultValue(ClampedDefaultValue); if (bResizeArrays) { while (Elements.Num() > InPin->SubPins.Num()) { InsertArrayPin(InPin, INDEX_NONE, FString(), bSetupUndoRedo); } while (Elements.Num() < InPin->SubPins.Num()) { RemoveArrayPin(InPin->SubPins.Last()->GetPinPath(), bSetupUndoRedo); } } else { ensure(Elements.Num() == InPin->SubPins.Num()); } for (int32 ElementIndex = 0; ElementIndex < Elements.Num(); ElementIndex++) { URigVMPin* SubPin = InPin->SubPins[ElementIndex]; PostProcessDefaultValue(SubPin, Elements[ElementIndex]); if (!Elements[ElementIndex].IsEmpty()) { SetPinDefaultValue(SubPin, Elements[ElementIndex], bResizeArrays, false, false); bSetPinDefaultValueSucceeded = true; } } } } else if (InPin->IsStruct()) { TArray MemberValuePairs = URigVMPin::SplitDefaultValue(ClampedDefaultValue); for (const FString& MemberValuePair : MemberValuePairs) { FString MemberName, MemberValue; if (MemberValuePair.Split(TEXT("="), &MemberName, &MemberValue)) { URigVMPin* SubPin = InPin->FindSubPin(MemberName); if (SubPin && !MemberValue.IsEmpty()) { PostProcessDefaultValue(SubPin, MemberValue); if (!MemberValue.IsEmpty()) { SetPinDefaultValue(SubPin, MemberValue, bResizeArrays, false, false); bSetPinDefaultValueSucceeded = true; } } } } } if(!bSetPinDefaultValueSucceeded) { // no need to send notifications if not changing the value if (InPin->GetSubPins().IsEmpty() && (InPin->DefaultValue != ClampedDefaultValue)) { InPin->DefaultValue = ClampedDefaultValue; Notify(ERigVMGraphNotifType::PinDefaultValueChanged, InPin); if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } } } if (bSetupUndoRedo) { ActionStack->EndAction(Action, bMergeUndoAction); } return true; } bool URigVMController::ResetPinDefaultValue(const FString& InPinPath, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMPin* Pin = Graph->FindPin(InPinPath); if (Pin == nullptr) { ReportErrorf(TEXT("Cannot find pin '%s'."), *InPinPath); return false; } URigVMNode* Node = Pin->GetNode(); if (!Node->IsA() && !Node->IsA()) { ReportErrorf(TEXT("Pin '%s' is neither part of a unit nor a function reference node."), *InPinPath); return false; } const bool bSuccess = ResetPinDefaultValue(Pin, bSetupUndoRedo); if (bSuccess && bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').reset_pin_default_value('%s')"), *GraphName, *GetSanitizedPinPath(InPinPath))); } return bSuccess; } bool URigVMController::ResetPinDefaultValue(URigVMPin* InPin, bool bSetupUndoRedo) { if (!bIsTransacting && !IsGraphEditable()) { return false; } check(InPin); URigVMNode* RigVMNode = InPin->GetNode(); // unit nodes if (URigVMUnitNode* UnitNode = Cast(RigVMNode)) { // cut off the first one since it's the node static const uint32 Offset = 1; const FString DefaultValue = GetPinInitialDefaultValueFromStruct(UnitNode->GetScriptStruct(), InPin, Offset); if (!DefaultValue.IsEmpty()) { SetPinDefaultValue(InPin, DefaultValue, true, bSetupUndoRedo, false); return true; } } // function reference nodes URigVMFunctionReferenceNode* RefNode = Cast(RigVMNode); if (RefNode != nullptr) { const FString DefaultValue = GetPinInitialDefaultValue(InPin); if (!DefaultValue.IsEmpty()) { SetPinDefaultValue(InPin, DefaultValue, true, bSetupUndoRedo, false); return true; } } return false; } FString URigVMController::GetPinInitialDefaultValue(const URigVMPin* InPin) { static const FString EmptyValue; static const FString TArrayInitValue( TEXT("()") ); static const FString TObjectInitValue( TEXT("()") ); static const TMap InitValues = { { RigVMTypeUtils::BoolType, TEXT("False") }, { RigVMTypeUtils::Int32Type, TEXT("0") }, { RigVMTypeUtils::FloatType, TEXT("0.000000") }, { RigVMTypeUtils::DoubleType, TEXT("0.000000") }, { RigVMTypeUtils::FNameType, FName(NAME_None).ToString() }, { RigVMTypeUtils::FStringType, TEXT("") } }; if (InPin->IsStruct()) { // offset is useless here as we are going to get the full struct default value static const uint32 Offset = 0; return GetPinInitialDefaultValueFromStruct(InPin->GetScriptStruct(), InPin, Offset); } if (InPin->IsStructMember()) { if (URigVMPin* ParentPin = InPin->GetParentPin()) { // cut off node's and parent struct's paths if func reference node, only node instead static const uint32 Offset = InPin->GetNode()->IsA() ? 2 : 1; return GetPinInitialDefaultValueFromStruct(ParentPin->GetScriptStruct(), InPin, Offset); } } if (InPin->IsArray()) { return TArrayInitValue; } if (InPin->IsUObject()) { return TObjectInitValue; } if (UEnum* Enum = InPin->GetEnum()) { return Enum->GetNameStringByIndex(0); } if (const FString* BasicDefault = InitValues.Find(InPin->GetCPPType())) { return *BasicDefault; } return EmptyValue; } FString URigVMController::GetPinInitialDefaultValueFromStruct(UScriptStruct* ScriptStruct, const URigVMPin* InPin, uint32 InOffset) { FString DefaultValue; if (InPin && ScriptStruct) { TSharedPtr StructOnScope = MakeShareable(new FStructOnScope(ScriptStruct)); uint8* Memory = (uint8*)StructOnScope->GetStructMemory(); if (InPin->GetScriptStruct() == ScriptStruct) { ScriptStruct->ExportText(DefaultValue, Memory, nullptr, nullptr, PPF_None, nullptr, true); return DefaultValue; } const FString PinPath = InPin->GetPinPath(); TArray Parts; if (!URigVMPin::SplitPinPath(PinPath, Parts)) { return DefaultValue; } const uint32 NumParts = Parts.Num(); if (InOffset >= NumParts) { return DefaultValue; } uint32 PartIndex = InOffset; UStruct* Struct = ScriptStruct; FProperty* Property = Struct->FindPropertyByName(*Parts[PartIndex++]); check(Property); Memory = Property->ContainerPtrToValuePtr(Memory); while (PartIndex < NumParts && Property != nullptr) { if (FArrayProperty* ArrayProperty = CastField(Property)) { Property = ArrayProperty->Inner; check(Property); PartIndex++; if (FStructProperty* StructProperty = CastField(Property)) { UScriptStruct* InnerStruct = StructProperty->Struct; StructOnScope = MakeShareable(new FStructOnScope(InnerStruct)); Memory = (uint8 *)StructOnScope->GetStructMemory(); } continue; } if (FStructProperty* StructProperty = CastField(Property)) { Struct = StructProperty->Struct; Property = Struct->FindPropertyByName(*Parts[PartIndex++]); check(Property); Memory = Property->ContainerPtrToValuePtr(Memory); continue; } break; } if (Memory) { check(Property); Property->ExportTextItem_Direct(DefaultValue, Memory, nullptr, nullptr, PPF_None); } } return DefaultValue; } FString URigVMController::AddAggregatePin(const FString& InNodeName, const FString& InPinName, const FString& InDefaultValue, bool bSetupUndoRedo, bool bPrintPythonCommand) { #if UE_RIGVM_AGGREGATE_NODES_ENABLED if (!IsValidGraph()) { return FString(); } if (!bIsTransacting && !IsGraphEditable()) { return FString(); } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMNode* Node = Graph->FindNodeByName(*InNodeName); if (!Node) { return FString(); } return AddAggregatePin(Node, InPinName, InDefaultValue, bSetupUndoRedo, bPrintPythonCommand); } FString URigVMController::AddAggregatePin(URigVMNode* InNode, const FString& InPinName, const FString& InDefaultValue, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!bIsTransacting && !IsGraphEditable()) { return FString(); } if (!InNode) { return FString(); } if (!IsValidNodeForGraph(InNode)) { return FString(); } URigVMAggregateNode* AggregateNode = Cast(InNode); if (AggregateNode == nullptr) { if(!InNode->IsAggregate()) { return FString(); } } FRigVMControllerCompileBracketScope CompileScope(this); FRigVMBaseAction Action; if (bSetupUndoRedo) { Action.Title = FString::Printf(TEXT("Add Aggregate Pin")); ActionStack->BeginAction(Action); } if (AggregateNode == nullptr) { bool bAggregateInputs = false; URigVMPin* Arg1 = nullptr; URigVMPin* Arg2 = nullptr; URigVMPin* ArgOpposite = nullptr; const TArray AggregateInputs = InNode->GetAggregateInputs(); const TArray AggregateOutputs = InNode->GetAggregateOutputs(); if (AggregateInputs.Num() == 2 && AggregateOutputs.Num() == 1) { bAggregateInputs = true; Arg1 = AggregateInputs[0]; Arg2 = AggregateInputs[1]; ArgOpposite = AggregateOutputs[0]; } else if (AggregateInputs.Num() == 1 && AggregateOutputs.Num() == 2) { bAggregateInputs = false; Arg1 = AggregateOutputs[0]; Arg2 = AggregateOutputs[1]; ArgOpposite = AggregateInputs[0]; } else { if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return FString(); } if (!Arg1 || !Arg2 || !ArgOpposite) { if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return FString(); } if (Arg1->GetCPPType() != Arg2->GetCPPType() || Arg1->GetCPPTypeObject() != Arg2->GetCPPTypeObject() || Arg1->GetCPPType() != ArgOpposite->GetCPPType() || Arg1->GetCPPTypeObject() != ArgOpposite->GetCPPTypeObject()) { if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return FString(); } const FString AggregateArg1 = Arg1->GetName(); const FString AggregateArg2 = Arg2->GetName(); const FString AggregateOppositeArg = ArgOpposite->GetName(); TArray> LinkedPaths = GetLinkedPinPaths(InNode); if(!BreakLinkedPaths(LinkedPaths, bSetupUndoRedo)) { if(bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return FString(); } // We must resolve the type before we continue if (Arg1->IsWildCard()) { TRigVMTypeIndex AnswerType = INDEX_NONE; if (RequestPinTypeSelectionDelegate.IsBound()) { if (URigVMTemplateNode* TemplateNode = Cast(InNode)) { if (const FRigVMTemplate* Template = TemplateNode->GetTemplate()) { if (const FRigVMTemplateArgument* Argument = Template->FindArgument(Arg1->GetFName())) { const TArray& Types = Argument->GetTypeIndices(); AnswerType = RequestPinTypeSelectionDelegate.Execute(Types); } } } } if (AnswerType != INDEX_NONE) { ResolveWildCardPin(Arg1, AnswerType, bSetupUndoRedo); } } if (Arg1->IsWildCard() || Arg2->IsWildCard() || ArgOpposite->IsWildCard()) { if(bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return FString(); } const FName PreviousNodeName = InNode->GetFName(); URigVMCollapseNode* CollapseNode = CollapseNodes({InNode}, InNode->GetName(), bSetupUndoRedo, true); if (!CollapseNode) { if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return FString(); } InNode = CollapseNode->GetContainedGraph()->FindNodeByName(PreviousNodeName); AggregateNode = Cast(CollapseNode); if (AggregateNode) { FRigVMControllerGraphGuard GraphGuard(this, AggregateNode->GetContainedGraph(), bSetupUndoRedo); TGuardValue GuardEditGraph(GetGraph()->bEditable, true); for(int32 Index = 0; Index < InNode->GetPins().Num(); Index++) { URigVMPin* Pin = InNode->GetPins()[Index]; const FName PinName = Pin->GetFName(); if (URigVMPin* AggregatePin = AggregateNode->FindPin(PinName.ToString())) { SetExposedPinIndex(PinName, Index, bSetupUndoRedo); continue; } const FName ExposedPinName = AddExposedPin(PinName, Pin->GetDirection(), Pin->GetCPPType(), *Pin->GetCPPTypeObject()->GetPathName(), Pin->GetDefaultValue()); const FString PinNameStr = PinName.ToString(); const FString ExposedPinNameStr = ExposedPinName.ToString(); if(URigVMPin* ExposedPin = AggregateNode->FindPin(ExposedPinNameStr)) { ExposedPin->SetDisplayName(Pin->GetDisplayName()); } if(URigVMPin* ExposedPin = AggregateNode->GetEntryNode()->FindPin(ExposedPinNameStr)) { ExposedPin->SetDisplayName(Pin->GetDisplayName()); } if(URigVMPin* ExposedPin = AggregateNode->GetReturnNode()->FindPin(ExposedPinNameStr)) { ExposedPin->SetDisplayName(Pin->GetDisplayName()); } if (Pin->GetDirection() == ERigVMPinDirection::Input) { AddLink(FString::Printf(TEXT("Entry.%s"), *ExposedPinNameStr), FString::Printf(TEXT("%s.%s"), *InNode->GetName(), *PinNameStr), bSetupUndoRedo); } else { AddLink(FString::Printf(TEXT("%s.%s"), *InNode->GetName(), *PinNameStr), FString::Printf(TEXT("Return.%s"), *ExposedPinNameStr), bSetupUndoRedo); } } } else { if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return FString(); } RestoreLinkedPaths(LinkedPaths, {{PreviousNodeName.ToString(), AggregateNode->GetName()}}, {}, bSetupUndoRedo); } if (!AggregateNode) { if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return FString(); } URigVMPin* NewPin = nullptr; { FRigVMControllerGraphGuard GraphGuard(this, AggregateNode->GetContainedGraph(), bSetupUndoRedo); TGuardValue GuardEditGraph(GetGraph()->bEditable, true); URigVMNode* InnerNode = (AggregateNode == nullptr) ? InNode : AggregateNode->GetFirstInnerNode(); const FString InnerNodeContent = ExportNodesToText({InnerNode->GetFName()}); const TArray NewNodeNames = ImportNodesFromText(InnerNodeContent); if(NewNodeNames.IsEmpty()) { if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return FString(); } URigVMNode* NewNode = AggregateNode->GetContainedGraph()->FindNodeByName(NewNodeNames[0]); FName NewPinName = *InPinName; if (NewPinName.IsNone()) { URigVMNode* LastInnerNode = AggregateNode->GetLastInnerNode(); URigVMPin* SecondAggregateInnerPin = LastInnerNode->GetSecondAggregatePin(); FString LastAggregateName; if (AggregateNode->IsInputAggregate()) { TArray SourcePins = SecondAggregateInnerPin->GetLinkedSourcePins(); if (SourcePins.Num() > 0) { LastAggregateName = SourcePins[0]->GetName(); } } else { TArray TargetPins = SecondAggregateInnerPin->GetLinkedTargetPins(); if (TargetPins.Num() > 0) { LastAggregateName = TargetPins[0]->GetName(); } } NewPinName = InnerNode->GetNextAggregateName(*LastAggregateName); } if (NewPinName.IsNone()) { NewPinName = InnerNode->GetSecondAggregatePin()->GetFName(); } const URigVMPin* Arg1 = AggregateNode->GetFirstAggregatePin(); FName NewExposedPinName = AddExposedPin(NewPinName, Arg1->GetDirection(), Arg1->GetCPPType(), *Arg1->GetCPPTypeObject()->GetPathName(), InDefaultValue, bSetupUndoRedo); NewPin = AggregateNode->FindPin(NewExposedPinName.ToString()); URigVMPin* NewUnitPinArg1 = NewNode->GetFirstAggregatePin(); URigVMPin* NewUnitPinArg2 = NewNode->GetSecondAggregatePin(); URigVMPin* NewUnitPinOppositeArg = NewNode->GetOppositeAggregatePin(); URigVMNode* PreviousNode = nullptr; if(AggregateNode->IsInputAggregate()) { URigVMFunctionEntryNode* EntryNode = AggregateNode->GetEntryNode(); URigVMPin* EntryPin = EntryNode->FindPin(NewExposedPinName.ToString()); URigVMPin* ReturnPin = AggregateNode->GetReturnNode()->FindPin(NewUnitPinOppositeArg->GetName()); URigVMPin* PreviousReturnPin = ReturnPin->GetLinkedSourcePins()[0]; PreviousNode = PreviousReturnPin->GetNode(); BreakAllLinks(ReturnPin, true, bSetupUndoRedo); AddLink(PreviousReturnPin, NewUnitPinArg1, bSetupUndoRedo); AddLink(EntryPin, NewUnitPinArg2, bSetupUndoRedo); AddLink(NewUnitPinOppositeArg, ReturnPin, bSetupUndoRedo); } else { URigVMFunctionReturnNode* ReturnNode = AggregateNode->GetReturnNode(); URigVMPin* NewReturnPin = ReturnNode->FindPin(NewExposedPinName.ToString()); URigVMPin* OldReturnPin = ReturnNode->GetPins()[ReturnNode->GetPins().Num()-2]; URigVMPin* PreviousReturnPin = OldReturnPin->GetLinkedSourcePins()[0]; PreviousNode = PreviousReturnPin->GetNode(); BreakAllLinks(OldReturnPin, true, bSetupUndoRedo); AddLink(PreviousReturnPin, NewUnitPinOppositeArg, bSetupUndoRedo); AddLink(NewUnitPinArg1, OldReturnPin, bSetupUndoRedo); AddLink(NewUnitPinArg2, NewReturnPin, bSetupUndoRedo); } // Rearrange the graph nodes URigVMFunctionReturnNode* ReturnNode = AggregateNode->GetReturnNode(); FVector2D NodeDimensions(200, 150); SetNodePosition(NewNode, PreviousNode->GetPosition() + NodeDimensions, bSetupUndoRedo); SetNodePosition(ReturnNode, NewNode->GetPosition() + NodeDimensions, bSetupUndoRedo); // Connect other input pins for (URigVMPin* OtherInputPin : AggregateNode->GetFirstInnerNode()->GetPins()) { if (OtherInputPin->GetName() != NewUnitPinArg1->GetName() && OtherInputPin->GetName() != NewUnitPinArg2->GetName() && OtherInputPin->GetName() != NewUnitPinOppositeArg->GetName()) { URigVMPin* OtherEntryPin = AggregateNode->GetEntryNode()->FindPin(OtherInputPin->GetName()); AddLink(OtherEntryPin, NewNode->FindPin(OtherEntryPin->GetName()), bSetupUndoRedo); } } AggregateNode->LastInnerNodeCache = NewNode; } if (!NewPin) { if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return FString(); } if (bSetupUndoRedo) { ActionStack->EndAction(Action); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); const FString NodePath = GetSanitizedPinPath(InNode->GetNodePath()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_aggregate_pin('%s', '%s', '%s')"), *GraphName, *NodePath, *InPinName, *InDefaultValue)); } return NewPin->GetPinPath(); #else return FString(); #endif } bool URigVMController::RemoveAggregatePin(const FString& InPinPath, bool bSetupUndoRedo, bool bPrintPythonCommand) { #if UE_RIGVM_AGGREGATE_NODES_ENABLED if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMPin* Pin = Graph->FindPin(*InPinPath); if (!Pin) { return false; } return RemoveAggregatePin(Pin, bSetupUndoRedo, bPrintPythonCommand); } bool URigVMController::RemoveAggregatePin(URigVMPin* InPin, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!bIsTransacting && !IsGraphEditable()) { return false; } if (!InPin) { return false; } if (InPin->GetParentPin()) { return false; } FRigVMControllerCompileBracketScope CompileScope(this); FRigVMBaseAction Action; if (bSetupUndoRedo) { Action.Title = FString::Printf(TEXT("Remove Aggregate Pin")); ActionStack->BeginAction(Action); } bool bSuccess = false; if (URigVMAggregateNode* AggregateNode = Cast(InPin->GetNode())) { URigVMGraph* Graph = AggregateNode->GetContainedGraph(); if (AggregateNode->IsInputAggregate()) { if (URigVMFunctionEntryNode* EntryNode = Graph->GetEntryNode()) { if (URigVMPin* EntryPin = EntryNode->FindPin(InPin->GetName())) { if (EntryPin->GetLinkedTargetPins().Num() > 0) { FRigVMControllerGraphGuard GraphGuard(this, AggregateNode->GetContainedGraph(), bSetupUndoRedo); TGuardValue GuardEditGraph(GetGraph()->bEditable, true); URigVMPin* TargetPin = EntryPin->GetLinkedTargetPins()[0]; URigVMNode* NodeToRemove = TargetPin->GetNode(); URigVMPin* ResultPin = NodeToRemove->GetOppositeAggregatePin(); URigVMPin* NextNodePin = ResultPin->GetLinkedTargetPins()[0]; if (NodeToRemove == AggregateNode->FirstInnerNodeCache || NodeToRemove == AggregateNode->LastInnerNodeCache) { AggregateNode->InvalidateCache(); } const FString FirstAggregatePin = AggregateNode->GetFirstAggregatePin()->GetName(); const FString SecondAggregatePin = AggregateNode->GetSecondAggregatePin()->GetName(); FString OtherArg = TargetPin->GetName() == FirstAggregatePin ? SecondAggregatePin : FirstAggregatePin; BreakAllLinks(NextNodePin, true, bSetupUndoRedo); RewireLinks(NodeToRemove->FindPin(OtherArg), NextNodePin, true, bSetupUndoRedo); RemoveNode(NodeToRemove, bSetupUndoRedo); RemoveExposedPin(*InPin->GetName(), bSetupUndoRedo); bSuccess = true; } } } } else { if (URigVMFunctionReturnNode* ReturnNode = Graph->GetReturnNode()) { if (URigVMPin* ReturnPin = ReturnNode->FindPin(InPin->GetName())) { if (ReturnPin->GetLinkedSourcePins().Num() > 0) { FRigVMControllerGraphGuard GraphGuard(this, AggregateNode->GetContainedGraph(), bSetupUndoRedo); TGuardValue GuardEditGraph(GetGraph()->bEditable, true); URigVMPin* SourcePin = ReturnPin->GetLinkedSourcePins()[0]; URigVMNode* NodeToRemove = SourcePin->GetNode(); URigVMPin* OppositePin = NodeToRemove->GetOppositeAggregatePin(); URigVMPin* NextNodePin = OppositePin->GetLinkedSourcePins()[0]; URigVMNode* NextNode = NextNodePin->GetNode(); if (NodeToRemove == AggregateNode->FirstInnerNodeCache || NodeToRemove == AggregateNode->LastInnerNodeCache) { AggregateNode->InvalidateCache(); } const FString FirstAggregatePin = AggregateNode->GetFirstAggregatePin()->GetName(); const FString SecondAggregatePin = AggregateNode->GetSecondAggregatePin()->GetName(); FString OtherArg = SourcePin->GetName() == FirstAggregatePin ? SecondAggregatePin : FirstAggregatePin; BreakAllLinks(NextNodePin, false, bSetupUndoRedo); RewireLinks(NodeToRemove->FindPin(OtherArg), NextNodePin, false, bSetupUndoRedo); RemoveNode(NodeToRemove, bSetupUndoRedo); RemoveExposedPin(*InPin->GetName(), bSetupUndoRedo); bSuccess = true; } } } } if (bSuccess && AggregateNode->GetContainedNodes().Num() == 3) { TArray> LinkedPaths = GetLinkedPinPaths(AggregateNode); if(!BreakLinkedPaths(LinkedPaths, bSetupUndoRedo)) { if(bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return false; } TMap PinNameMap; for(URigVMPin* Pin : AggregateNode->GetPins()) { if(URigVMPin* EntryPin = AggregateNode->GetEntryNode()->FindPin(Pin->GetName())) { TArray TargetPins = EntryPin->GetLinkedTargetPins(); if(TargetPins.Num() > 0) { PinNameMap.Add(EntryPin->GetName(), TargetPins[0]->GetName()); } } else if(URigVMPin* ReturnPin = AggregateNode->GetReturnNode()->FindPin(Pin->GetName())) { TArray SourcePins = ReturnPin->GetLinkedSourcePins(); if(SourcePins.Num() > 0) { PinNameMap.Add(ReturnPin->GetName(), SourcePins[0]->GetName()); } } } const FString PreviousNodeName = AggregateNode->GetName(); TArray NodesEjected = ExpandLibraryNode(AggregateNode, bSetupUndoRedo); bSuccess = NodesEjected.Num() == 1; if(bSuccess) { URigVMNode* EjectedNode = NodesEjected[0]; RestoreLinkedPaths(LinkedPaths, {}, {{ PreviousNodeName, FRigVMController_PinPathRemapDelegate::CreateLambda([ PreviousNodeName, EjectedNode, PinNameMap ](const FString& InPinPath, bool bIsInput) -> FString { static constexpr TCHAR PinPrefixFormat[] = TEXT("%s."); TArray Segments; URigVMPin::SplitPinPath(InPinPath, Segments); Segments[0] = EjectedNode->GetName(); if(const FString* RemappedPin = PinNameMap.Find(Segments[1])) { Segments[1] = *RemappedPin; } return URigVMPin::JoinPinPath(Segments); }) }}, bSetupUndoRedo); } } } if (bSetupUndoRedo) { if (bSuccess) { ActionStack->EndAction(Action); } else { ActionStack->CancelAction(Action, this); } } if (bSuccess && bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); const FString PinPath = GetSanitizedPinPath(InPin->GetPinPath()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').remove_aggregate_pin('%s')"), *GraphName, *PinPath)); } return bSuccess; #else return false; #endif } FString URigVMController::AddArrayPin(const FString& InArrayPinPath, const FString& InDefaultValue, bool bSetupUndoRedo, bool bPrintPythonCommand) { return InsertArrayPin(InArrayPinPath, INDEX_NONE, InDefaultValue, bSetupUndoRedo, bPrintPythonCommand); } FString URigVMController::DuplicateArrayPin(const FString& InArrayElementPinPath, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return FString(); } if (!bIsTransacting && !IsGraphEditable()) { return FString(); } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMPin* ElementPin = Graph->FindPin(InArrayElementPinPath); if (ElementPin == nullptr) { ReportErrorf(TEXT("Cannot find pin '%s'."), *InArrayElementPinPath); return FString(); } if (!ElementPin->IsArrayElement()) { ReportErrorf(TEXT("Pin '%s' is not an array element."), *InArrayElementPinPath); return FString(); } URigVMPin* ArrayPin = ElementPin->GetParentPin(); check(ArrayPin); ensure(ArrayPin->IsArray()); FString DefaultValue = ElementPin->GetDefaultValue(); return InsertArrayPin(ArrayPin->GetPinPath(), ElementPin->GetPinIndex() + 1, DefaultValue, bSetupUndoRedo, bPrintPythonCommand); } FString URigVMController::InsertArrayPin(const FString& InArrayPinPath, int32 InIndex, const FString& InDefaultValue, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return FString(); } if (!bIsTransacting && !IsGraphEditable()) { return FString(); } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMPin* ArrayPin = Graph->FindPin(InArrayPinPath); if (ArrayPin == nullptr) { ReportErrorf(TEXT("Cannot find pin '%s'."), *InArrayPinPath); return FString(); } URigVMPin* ElementPin = InsertArrayPin(ArrayPin, InIndex, InDefaultValue, bSetupUndoRedo); if (ElementPin) { if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').insert_array_pin('%s', %d, '%s')"), *GraphName, *GetSanitizedPinPath(InArrayPinPath), InIndex, *InDefaultValue)); } return ElementPin->GetPinPath(); } return FString(); } URigVMPin* URigVMController::InsertArrayPin(URigVMPin* ArrayPin, int32 InIndex, const FString& InDefaultValue, bool bSetupUndoRedo) { if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } if (!ArrayPin->IsArray()) { ReportErrorf(TEXT("Pin '%s' is not an array."), *ArrayPin->GetPinPath()); return nullptr; } if (!ShouldPinBeUnfolded(ArrayPin)) { ReportErrorf(TEXT("Cannot insert array pin under '%s'."), *ArrayPin->GetPinPath()); return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); if (InIndex == INDEX_NONE) { InIndex = ArrayPin->GetSubPins().Num(); } FRigVMControllerCompileBracketScope CompileScope(this); FRigVMInsertArrayPinAction Action; if (bSetupUndoRedo) { Action = FRigVMInsertArrayPinAction(ArrayPin, InIndex, InDefaultValue); Action.Title = FString::Printf(TEXT("Insert Array Pin")); ActionStack->BeginAction(Action); } for (int32 ExistingIndex = ArrayPin->GetSubPins().Num() - 1; ExistingIndex >= InIndex; ExistingIndex--) { URigVMPin* ExistingPin = ArrayPin->GetSubPins()[ExistingIndex]; RenameObject(ExistingPin, *FString::FormatAsNumber(ExistingIndex + 1)); } URigVMPin* Pin = NewObject(ArrayPin, *FString::FormatAsNumber(InIndex)); ConfigurePinFromPin(Pin, ArrayPin); Pin->CPPType = ArrayPin->GetArrayElementCppType(); ArrayPin->SubPins.Insert(Pin, InIndex); if (Pin->IsStruct()) { UScriptStruct* ScriptStruct = Pin->GetScriptStruct(); if (ScriptStruct) { FString DefaultValue = InDefaultValue; CreateDefaultValueForStructIfRequired(ScriptStruct, DefaultValue); { TGuardValue SuspendNotifications(bSuspendNotifications, true); AddPinsForStruct(ScriptStruct, Pin->GetNode(), Pin, Pin->Direction, DefaultValue, false); } } } else if (Pin->IsArray()) { FArrayProperty * ArrayProperty = CastField(FindPropertyForPin(Pin->GetPinPath())); if (ArrayProperty) { TArray ElementDefaultValues = URigVMPin::SplitDefaultValue(InDefaultValue); AddPinsForArray(ArrayProperty, Pin->GetNode(), Pin, Pin->Direction, ElementDefaultValues, false); } } else { FString DefaultValue = InDefaultValue; PostProcessDefaultValue(Pin, DefaultValue); Pin->DefaultValue = DefaultValue; } Notify(ERigVMGraphNotifType::PinAdded, Pin); Notify(ERigVMGraphNotifType::PinArraySizeChanged, ArrayPin); if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } if (bSetupUndoRedo) { ActionStack->EndAction(Action); } return Pin; } bool URigVMController::RemoveArrayPin(const FString& InArrayElementPinPath, bool bSetupUndoRedo, bool bPrintPythonCommand) { if(!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMPin* ArrayElementPin = Graph->FindPin(InArrayElementPinPath); if (ArrayElementPin == nullptr) { ReportErrorf(TEXT("Cannot find pin '%s'."), *InArrayElementPinPath); return false; } if (!ArrayElementPin->IsArrayElement()) { ReportErrorf(TEXT("Pin '%s' is not an array element."), *InArrayElementPinPath); return false; } URigVMPin* ArrayPin = ArrayElementPin->GetParentPin(); check(ArrayPin); ensure(ArrayPin->IsArray()); FRigVMControllerCompileBracketScope CompileScope(this); FRigVMRemoveArrayPinAction Action; if (bSetupUndoRedo) { Action = FRigVMRemoveArrayPinAction(ArrayElementPin); Action.Title = FString::Printf(TEXT("Remove Array Pin")); ActionStack->BeginAction(Action); } // we need to keep at least one element for fixed size arrays if(ArrayPin->IsExecuteContext() || ArrayPin->IsFixedSizeArray()) { if(ArrayPin->GetArraySize() == 1) { return false; } } int32 IndexToRemove = ArrayElementPin->GetPinIndex(); if (!RemovePin(ArrayElementPin, bSetupUndoRedo)) { return false; } for (int32 ExistingIndex = IndexToRemove; ExistingIndex < ArrayPin->GetArraySize(); ExistingIndex++) { URigVMPin* ExistingPin = ArrayPin->GetSubPins()[ExistingIndex]; ExistingPin->SetNameFromIndex(); Notify(ERigVMGraphNotifType::PinRenamed, ExistingPin); } if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } Notify(ERigVMGraphNotifType::PinArraySizeChanged, ArrayPin); if (bSetupUndoRedo) { ActionStack->EndAction(Action); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').remove_array_pin('%s')"), *GraphName, *GetSanitizedPinPath(InArrayElementPinPath))); } return true; } bool URigVMController::RemovePin(URigVMPin* InPinToRemove, bool bSetupUndoRedo) { if (!bIsTransacting && !IsGraphEditable()) { return false; } FRigVMControllerCompileBracketScope CompileScope(this); if (bSetupUndoRedo) { BreakAllLinks(InPinToRemove, true, bSetupUndoRedo); BreakAllLinks(InPinToRemove, false, bSetupUndoRedo); BreakAllLinksRecursive(InPinToRemove, true, false, bSetupUndoRedo); BreakAllLinksRecursive(InPinToRemove, false, false, bSetupUndoRedo); } TArray SubPins = InPinToRemove->GetSubPins(); for (URigVMPin* SubPin : SubPins) { if (!RemovePin(SubPin, bSetupUndoRedo)) { return false; } } if (URigVMPin* ParentPin = InPinToRemove->GetParentPin()) { ParentPin->SubPins.Remove(InPinToRemove); } else if(URigVMNode* Node = InPinToRemove->GetNode()) { Node->Pins.Remove(InPinToRemove); Node->OrphanedPins.Remove(InPinToRemove); } if (!bSuspendNotifications) { Notify(ERigVMGraphNotifType::PinRemoved, InPinToRemove); } DestroyObject(InPinToRemove); return true; } bool URigVMController::ClearArrayPin(const FString& InArrayPinPath, bool bSetupUndoRedo, bool bPrintPythonCommand) { return SetArrayPinSize(InArrayPinPath, 0, FString(), bSetupUndoRedo, bPrintPythonCommand); } bool URigVMController::SetArrayPinSize(const FString& InArrayPinPath, int32 InSize, const FString& InDefaultValue, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMPin* Pin = Graph->FindPin(InArrayPinPath); if (Pin == nullptr) { ReportErrorf(TEXT("Cannot find pin '%s'."), *InArrayPinPath); return false; } if (!Pin->IsArray()) { ReportErrorf(TEXT("Pin '%s' is not an array."), *InArrayPinPath); return false; } FRigVMControllerCompileBracketScope CompileScope(this); FRigVMBaseAction Action; if (bSetupUndoRedo) { Action.Title = FString::Printf(TEXT("Set Array Pin Size (%d)"), InSize); ActionStack->BeginAction(Action); } InSize = FMath::Max(InSize, 0); int32 AddedPins = 0; int32 RemovedPins = 0; FString DefaultValue = InDefaultValue; if (DefaultValue.IsEmpty()) { if (Pin->GetSubPins().Num() > 0) { DefaultValue = Pin->GetSubPins().Last()->GetDefaultValue(); } CreateDefaultValueForStructIfRequired(Pin->GetScriptStruct(), DefaultValue); } while (Pin->GetSubPins().Num() > InSize) { if (!RemoveArrayPin(Pin->GetSubPins()[Pin->GetSubPins().Num()-1]->GetPinPath(), bSetupUndoRedo)) { if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return false; } RemovedPins++; } while (Pin->GetSubPins().Num() < InSize) { if (AddArrayPin(Pin->GetPinPath(), DefaultValue, bSetupUndoRedo).IsEmpty()) { if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return false; } AddedPins++; } if (!InDefaultValue.IsEmpty() && InDefaultValue != TEXT("()")) { SetPinDefaultValue(Pin, InDefaultValue, false, bSetupUndoRedo, true); } if (bSetupUndoRedo) { if (RemovedPins > 0 || AddedPins > 0) { ActionStack->EndAction(Action); } else { ActionStack->CancelAction(Action, this); } } return RemovedPins > 0 || AddedPins > 0; } bool URigVMController::BindPinToVariable(const FString& InPinPath, const FString& InNewBoundVariablePath, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMPin* Pin = Graph->FindPin(InPinPath); if (Pin == nullptr) { ReportErrorf(TEXT("Cannot find pin '%s'."), *InPinPath); return false; } bool bSuccess = false; if (InNewBoundVariablePath.IsEmpty()) { bSuccess = UnbindPinFromVariable(Pin, bSetupUndoRedo); } else { bSuccess = BindPinToVariable(Pin, InNewBoundVariablePath, bSetupUndoRedo); } if (bSuccess && bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').bind_pin_to_variable('%s', '%s')"), *GraphName, *GetSanitizedPinPath(InPinPath), *InNewBoundVariablePath)); } return bSuccess; } bool URigVMController::BindPinToVariable(URigVMPin* InPin, const FString& InNewBoundVariablePath, bool bSetupUndoRedo, const FString& InVariableNodeName) { if (!bIsTransacting && !IsGraphEditable()) { return false; } if (!IsValidPinForGraph(InPin)) { return false; } if (GetGraph()->IsA()) { ReportError(TEXT("Cannot bind pins to variables in function library graphs.")); return false; } if (InPin->GetBoundVariablePath() == InNewBoundVariablePath) { return false; } if (InPin->GetDirection() != ERigVMPinDirection::Input) { return false; } FString VariableName = InNewBoundVariablePath, SegmentPath; InNewBoundVariablePath.Split(TEXT("."), &VariableName, &SegmentPath); FRigVMExternalVariable Variable; for (const FRigVMExternalVariable& VariableDescription : GetAllVariables(true)) { if (VariableDescription.Name.ToString() == VariableName) { Variable = VariableDescription; break; } } if (!Variable.Name.IsValid()) { ReportError(TEXT("Cannot find variable in this graph.")); return false; } if (!RigVMTypeUtils::AreCompatible(Variable, InPin->ToExternalVariable(), SegmentPath)) { ReportError(TEXT("Cannot find variable in this graph.")); return false; } FRigVMControllerCompileBracketScope CompileScope(this); FRigVMBaseAction Action; if (bSetupUndoRedo) { Action.Title = TEXT("Bind pin to variable"); ActionStack->BeginAction(Action); } // Unbind any other variables, remove any other injections, and break all links to the input pin { if (InPin->IsBoundToVariable()) { UnbindPinFromVariable(InPin, bSetupUndoRedo); } TArray Infos = InPin->GetInjectedNodes(); for (URigVMInjectionInfo* Info : Infos) { RemoveInjectedNode(Info->GetPin()->GetPinPath(), Info->bInjectedAsInput, bSetupUndoRedo); } BreakAllLinks(InPin, true, bSetupUndoRedo); } // Create variable node URigVMVariableNode* VariableNode = nullptr; { { TGuardValue GuardNotifications(bSuspendNotifications, true); FString CPPType; UObject* CPPTypeObject; RigVMTypeUtils::CPPTypeFromExternalVariable(Variable, CPPType, &CPPTypeObject); VariableNode = AddVariableNode(*VariableName, CPPType, CPPTypeObject, true, FString(), FVector2D::ZeroVector, InVariableNodeName, bSetupUndoRedo); } if (VariableNode == nullptr) { if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return false; } } URigVMPin* ValuePin = VariableNode->FindPin(URigVMVariableNode::ValueName); // Connect value pin to input pin { if (!SegmentPath.IsEmpty()) { ValuePin = ValuePin->FindSubPin(SegmentPath); } { GetGraph()->ClearAST(true, false); if (!AddLink(ValuePin, InPin, bSetupUndoRedo)) { if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return false; } } } // Inject into pin if (!InjectNodeIntoPin(InPin->GetPinPath(), true, FName(), ValuePin->GetFName(), bSetupUndoRedo)) { if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return false; } if (bSetupUndoRedo) { ActionStack->EndAction(Action); } return true; } bool URigVMController::UnbindPinFromVariable(const FString& InPinPath, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMPin* Pin = Graph->FindPin(InPinPath); if (Pin == nullptr) { ReportErrorf(TEXT("Cannot find pin '%s'."), *InPinPath); return false; } const bool bSuccess = UnbindPinFromVariable(Pin, bSetupUndoRedo); if (bSuccess && bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').unbind_pin_from_variable('%s')"), *GraphName, *GetSanitizedPinPath(InPinPath))); } return bSuccess; } bool URigVMController::UnbindPinFromVariable(URigVMPin* InPin, bool bSetupUndoRedo) { if (!bIsTransacting && !IsGraphEditable()) { return false; } if (!IsValidPinForGraph(InPin)) { return false; } if (GetGraph()->IsA()) { ReportError(TEXT("Cannot unbind pins from variables in function library graphs.")); return false; } if (!InPin->IsBoundToVariable()) { ReportError(TEXT("Pin is not bound to any variable.")); return false; } FRigVMControllerCompileBracketScope CompileScope(this); FRigVMBaseAction Action; if (bSetupUndoRedo) { Action.Title = TEXT("Unbind pin from variable"); ActionStack->BeginAction(Action); } RemoveInjectedNode(InPin->GetPinPath(), true, bSetupUndoRedo); if (bSetupUndoRedo) { ActionStack->EndAction(Action); } return true; } bool URigVMController::MakeBindingsFromVariableNode(const FName& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); if (URigVMVariableNode* VariableNode = Cast(Graph->FindNodeByName(InNodeName))) { return MakeBindingsFromVariableNode(VariableNode, bSetupUndoRedo); } return false; } bool URigVMController::MakeBindingsFromVariableNode(URigVMVariableNode* InNode, bool bSetupUndoRedo) { if (!bIsTransacting && !IsGraphEditable()) { return false; } check(InNode); TArray> Pairs; TArray NodesToRemove; NodesToRemove.Add(InNode); if (URigVMPin* ValuePin = InNode->FindPin(URigVMVariableNode::ValueName)) { TArray Links = ValuePin->GetTargetLinks(true); for (URigVMLink* Link : Links) { URigVMPin* SourcePin = Link->GetSourcePin(); TArray TargetPins; TargetPins.Add(Link->GetTargetPin()); for (int32 TargetPinIndex = 0; TargetPinIndex < TargetPins.Num(); TargetPinIndex++) { URigVMPin* TargetPin = TargetPins[TargetPinIndex]; if (Cast(TargetPin->GetNode())) { NodesToRemove.AddUnique(TargetPin->GetNode()); TargetPins.Append(TargetPin->GetLinkedTargetPins(false /* recursive */)); } else { Pairs.Add(TPair(SourcePin, TargetPin)); } } } } FName VariableName = InNode->GetVariableName(); FRigVMExternalVariable Variable = GetVariableByName(VariableName); if (!Variable.IsValid(true /* allow nullptr */)) { return false; } if (Pairs.Num() > 0) { FRigVMControllerCompileBracketScope CompileScope(this); if (bSetupUndoRedo) { OpenUndoBracket(TEXT("Turn Variable Node into Bindings")); } for (const TPair& Pair : Pairs) { URigVMPin* SourcePin = Pair.Key; URigVMPin* TargetPin = Pair.Value; FString SegmentPath = SourcePin->GetSegmentPath(); FString VariablePathToBind = VariableName.ToString(); if (!SegmentPath.IsEmpty()) { VariablePathToBind = FString::Printf(TEXT("%s.%s"), *VariablePathToBind, *SegmentPath); } if (!BindPinToVariable(TargetPin, VariablePathToBind, bSetupUndoRedo)) { CancelUndoBracket(); } } for (URigVMNode* NodeToRemove : NodesToRemove) { RemoveNode(NodeToRemove, bSetupUndoRedo, true); } if (bSetupUndoRedo) { CloseUndoBracket(); } return true; } return false; } bool URigVMController::MakeVariableNodeFromBinding(const FString& InPinPath, const FVector2D& InNodePosition, bool bSetupUndoRedo, bool bPrintPythonCommand) { return PromotePinToVariable(InPinPath, true, InNodePosition, bSetupUndoRedo, bPrintPythonCommand); } bool URigVMController::PromotePinToVariable(const FString& InPinPath, bool bCreateVariableNode, const FVector2D& InNodePosition, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMPin* Pin = Graph->FindPin(InPinPath); if (Pin == nullptr) { ReportErrorf(TEXT("Cannot find pin '%s'."), *InPinPath); return false; } const bool bSuccess = PromotePinToVariable(Pin, bCreateVariableNode, InNodePosition, bSetupUndoRedo); if (bSuccess && bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').promote_pin_to_variable('%s', %s, %s)"), *GraphName, *GetSanitizedPinPath(InPinPath), (bCreateVariableNode) ? TEXT("True") : TEXT("False"), *RigVMPythonUtils::Vector2DToPythonString(InNodePosition))); } return bSuccess; } bool URigVMController::PromotePinToVariable(URigVMPin* InPin, bool bCreateVariableNode, const FVector2D& InNodePosition, bool bSetupUndoRedo) { if (!bIsTransacting && !IsGraphEditable()) { return false; } check(InPin); if (GetGraph()->IsA()) { ReportError(TEXT("Cannot promote pins to variables in function library graphs.")); return false; } if (InPin->GetDirection() != ERigVMPinDirection::Input) { return false; } FRigVMExternalVariable VariableForPin; FString SegmentPath; if (InPin->IsBoundToVariable()) { VariableForPin = GetVariableByName(*InPin->GetBoundVariableName()); check(VariableForPin.IsValid(true /* allow nullptr */)); SegmentPath = InPin->GetBoundVariablePath(); if (SegmentPath.StartsWith(VariableForPin.Name.ToString() + TEXT("."))) { SegmentPath = SegmentPath.RightChop(VariableForPin.Name.ToString().Len()); } else { SegmentPath.Empty(); } } else { if (!UnitNodeCreatedContext.GetCreateExternalVariableDelegate().IsBound()) { return false; } VariableForPin = InPin->ToExternalVariable(); FName VariableName = UnitNodeCreatedContext.GetCreateExternalVariableDelegate().Execute(VariableForPin, InPin->GetDefaultValue()); if (VariableName.IsNone()) { return false; } VariableForPin = GetVariableByName(VariableName); if (!VariableForPin.IsValid(true /* allow nullptr*/)) { return false; } } if (bCreateVariableNode) { FRigVMControllerCompileBracketScope CompileScope(this); if (URigVMVariableNode* VariableNode = AddVariableNode( VariableForPin.Name, VariableForPin.TypeName.ToString(), VariableForPin.TypeObject, true, FString(), InNodePosition, FString(), bSetupUndoRedo)) { if (URigVMPin* ValuePin = VariableNode->FindPin(URigVMVariableNode::ValueName)) { return AddLink(ValuePin->GetPinPath() + SegmentPath, InPin->GetPinPath(), bSetupUndoRedo); } } } else { FRigVMControllerCompileBracketScope CompileScope(this); return BindPinToVariable(InPin, VariableForPin.Name.ToString(), bSetupUndoRedo); } return false; } bool URigVMController::AddLink(const FString& InOutputPinPath, const FString& InInputPinPath, bool bSetupUndoRedo, bool bPrintPythonCommand, ERigVMPinDirection InUserDirection, bool bCreateCastNode) { if(!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); FString OutputPinPath = InOutputPinPath; FString InputPinPath = InInputPinPath; if (FString* RedirectedOutputPinPath = OutputPinRedirectors.Find(OutputPinPath)) { OutputPinPath = *RedirectedOutputPinPath; } if (FString* RedirectedInputPinPath = InputPinRedirectors.Find(InputPinPath)) { InputPinPath = *RedirectedInputPinPath; } URigVMPin* OutputPin = Graph->FindPin(OutputPinPath); if (OutputPin == nullptr) { ReportErrorf(TEXT("Cannot find pin '%s'."), *OutputPinPath); return false; } OutputPin = OutputPin->GetPinForLink(); URigVMPin* InputPin = Graph->FindPin(InputPinPath); if (InputPin == nullptr) { ReportErrorf(TEXT("Cannot find pin '%s'."), *InputPinPath); return false; } InputPin = InputPin->GetPinForLink(); const bool bSuccess = AddLink(OutputPin, InputPin, bSetupUndoRedo, InUserDirection, bCreateCastNode); if (bSuccess && bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); const FString SanitizedInputPinPath = GetSanitizedPinPath(InputPin->GetPinPath()); const FString SanitizedOutputPinPath = GetSanitizedPinPath(OutputPin->GetPinPath()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_link('%s', '%s')"), *GraphName, *SanitizedOutputPinPath, *SanitizedInputPinPath)); } return bSuccess; } bool URigVMController::AddLink(URigVMPin* OutputPin, URigVMPin* InputPin, bool bSetupUndoRedo, ERigVMPinDirection InUserDirection, bool bCreateCastNode) { if (!bIsTransacting && !IsGraphEditable()) { return false; } if(OutputPin == nullptr) { ReportError(TEXT("OutputPin is nullptr.")); return false; } if(InputPin == nullptr) { ReportError(TEXT("InputPin is nullptr.")); return false; } if(!IsValidPinForGraph(OutputPin) || !IsValidPinForGraph(InputPin)) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); if (Graph->IsA()) { ReportError(TEXT("Cannot add links in function library graphs.")); return false; } TGuardValue UserLinkDirectionGuard(UserLinkDirection, InUserDirection == ERigVMPinDirection::Invalid ? UserLinkDirection : InUserDirection); if (!bIsTransacting) { FString FailureReason; if (!Graph->CanLink(OutputPin, InputPin, &FailureReason, GetCurrentByteCode(), UserLinkDirection, bCreateCastNode)) { if(OutputPin->IsExecuteContext() && InputPin->IsExecuteContext()) { if(OutputPin->GetNode()->IsA() && InputPin->GetNode()->IsA()) { return false; } } ReportErrorf(TEXT("Cannot link '%s' to '%s': %s."), *OutputPin->GetPinPath(), *InputPin->GetPinPath(), *FailureReason, GetCurrentByteCode()); return false; } } ensure(!OutputPin->IsLinkedTo(InputPin)); ensure(!InputPin->IsLinkedTo(OutputPin)); FRigVMControllerCompileBracketScope CompileScope(this); FRigVMBaseAction Action; if (bSetupUndoRedo) { Action.Title = TEXT("Add Link"); ActionStack->BeginAction(Action); } // check if we need to inject cast node if(bCreateCastNode && bEnableTypeCasting && OutputPin->GetTypeIndex() != InputPin->GetTypeIndex() && !OutputPin->IsWildCard() && !InputPin->IsWildCard()) { if(!FRigVMRegistry::Get().CanMatchTypes(OutputPin->GetTypeIndex(), InputPin->GetTypeIndex(), true)) { if(bCreateCastNode) { const FRigVMFunction* CastFunction = RigVMTypeUtils::GetCastForTypeIndices(OutputPin->GetTypeIndex(), InputPin->GetTypeIndex()); if(CastFunction == nullptr) { // this is potentially a template node which has more types available. // look through the filtered types and find a matching cast. } if(CastFunction == nullptr) { if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return false; } const FVector2D OutputPinPosition = OutputPin->GetNode()->GetPosition() + FVector2D(150, 40.f) + FVector2D(0, 16) * OutputPin->GetRootPin()->GetPinIndex(); const FVector2D InputPinPosition = InputPin->GetNode()->GetPosition() + FVector2D(-75, 40.f) + FVector2D(0, 16) * InputPin->GetRootPin()->GetPinIndex(); const FVector2D CastPosition = (OutputPinPosition + InputPinPosition) * 0.5; const URigVMNode* CastNode = nullptr; // try to find an existing cast node if(CastNode == nullptr) { for(URigVMLink* ExistingLink : OutputPin->GetLinks()) { if(ExistingLink->GetSourcePin() == OutputPin) { if(URigVMUnitNode* ExistingCastNode = Cast(ExistingLink->GetTargetPin()->GetNode())) { if(ExistingCastNode->GetScriptStruct() == CastFunction->Struct) { CastNode = ExistingCastNode; break; } } } } } if(CastNode == nullptr) { CastNode = AddUnitNode(CastFunction->Struct, CastFunction->GetMethodName(), CastPosition, FString(), bSetupUndoRedo, false); } if(CastNode == nullptr) { if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return false; } static const FString CastTemplateValueName = RigVMTypeUtils::GetCastTemplateValueName().ToString(); static const FString CastTemplateResultName = RigVMTypeUtils::GetCastTemplateResultName().ToString(); URigVMPin* ValuePin = CastNode->FindPin(CastTemplateValueName); URigVMPin* ResultPin = CastNode->FindPin(CastTemplateResultName); if(!OutputPin->IsLinkedTo(ValuePin)) { if(!AddLink(OutputPin, ValuePin, bSetupUndoRedo)) { if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return false; } } if(!ResultPin->IsLinkedTo(InputPin)) { if(!AddLink(ResultPin, InputPin, bSetupUndoRedo)) { if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return false; } } if(bSetupUndoRedo) { Action.Title = TEXT("Add Link with Cast"); ActionStack->EndAction(Action); } return true; } } } if (OutputPin->IsExecuteContext()) { BreakAllLinks(OutputPin, false, bSetupUndoRedo); } BreakAllLinks(InputPin, true, bSetupUndoRedo); if (bSetupUndoRedo) { BreakAllLinksRecursive(InputPin, true, true, bSetupUndoRedo); BreakAllLinksRecursive(InputPin, true, false, bSetupUndoRedo); } // resolve types on the pins if needed if((InputPin->GetCPPTypeObject() != OutputPin->GetCPPTypeObject() || OutputPin->GetCPPType() != InputPin->GetCPPType()) && !InputPin->IsExecuteContext() && !OutputPin->IsExecuteContext()) { bool bOutputPinCanChangeType = OutputPin->IsWildCard(); bool bInputPinCanChangeType = InputPin->IsWildCard(); if(!bOutputPinCanChangeType && !bInputPinCanChangeType) { bInputPinCanChangeType = UserLinkDirection == ERigVMPinDirection::Output && InputPin->GetNode()->IsA(); bOutputPinCanChangeType = UserLinkDirection == ERigVMPinDirection::Input && OutputPin->GetNode()->IsA(); } if(bOutputPinCanChangeType) { Notify(ERigVMGraphNotifType::InteractionBracketOpened, nullptr); if(OutputPin->GetNode()->IsA()) { SetPinDefaultValue(OutputPin, InputPin->GetDefaultValue(), true, bSetupUndoRedo, false); } if(InputPin->GetNode()->IsA()) { SetPinDefaultValue(OutputPin, OutputPin->GetDefaultValue(), true, bSetupUndoRedo, false); } Notify(ERigVMGraphNotifType::InteractionBracketClosed, nullptr); } } if (bSetupUndoRedo) { ExpandPinRecursively(OutputPin->GetParentPin(), bSetupUndoRedo); ExpandPinRecursively(InputPin->GetParentPin(), bSetupUndoRedo); } // Before adding the link, let's resolve input and ouput pin types // If templates, we will filter the permutations that support this link // If any links need to be broken before perfoming this connection, try to find them and break them if (!bIsTransacting && !InputPin->IsExecuteContext() && !OutputPin->IsExecuteContext()) { URigVMPin* FirstToResolve = (InUserDirection == ERigVMPinDirection::Input) ? OutputPin : InputPin; URigVMPin* SecondToResolve = (FirstToResolve == OutputPin) ? InputPin : OutputPin; if (!PrepareToLink(FirstToResolve, SecondToResolve, bSetupUndoRedo)) { if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return false; } } if (bSetupUndoRedo) { ActionStack->AddAction(FRigVMAddLinkAction(OutputPin, InputPin)); } URigVMLink* Link = NewObject(Graph); Link->SourcePin = OutputPin; Link->TargetPin = InputPin; Link->SourcePinPath = OutputPin->GetPinPath(); Link->TargetPinPath = InputPin->GetPinPath(); Graph->Links.Add(Link); OutputPin->Links.Add(Link); InputPin->Links.Add(Link); if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } Notify(ERigVMGraphNotifType::LinkAdded, Link); if (bSetupUndoRedo) { UpdateRerouteNodeAfterChangingLinks(OutputPin, bSetupUndoRedo); UpdateRerouteNodeAfterChangingLinks(InputPin, bSetupUndoRedo); } if (bSetupUndoRedo) { #if WITH_EDITOR if (!bSuspendTemplateComputation) { auto ResolveTemplateNodeToCommonTypes = [this](URigVMPin* Pin) { if(!Pin->IsExecuteContext()) { return; } URigVMTemplateNode* TemplateNode = Cast(Pin->GetNode()); if(TemplateNode == nullptr) { return; } const FRigVMTemplate* Template = TemplateNode->GetTemplate(); if(Template == nullptr) { return; } if(!TemplateNode->HasWildCardPin()) { return; } const FRigVMTemplate::FTypeMap PreferredTypes = GetCommonlyUsedTypesForTemplate(TemplateNode); if(PreferredTypes.IsEmpty()) { return; } const int32 PreferredPermutation = Template->FindPermutation(PreferredTypes); if(PreferredPermutation != INDEX_NONE) { const TGuardValue DisableRegisterUseOfTemplate(bRegisterTemplateNodeUsage, false); if(FullyResolveTemplateNode(TemplateNode, PreferredPermutation, true)) { static const FString Message = TEXT("Template node was automatically resolved to commonly used types."); SendUserFacingNotification(Message, 0.f, TemplateNode, TEXT("MessageLog.Note")); } } }; ResolveTemplateNodeToCommonTypes(OutputPin); ResolveTemplateNodeToCommonTypes(InputPin); } #endif ActionStack->EndAction(Action); } if (!bIsTransacting) { ensureMsgf(RigVMTypeUtils::AreCompatible(*OutputPin->GetCPPType(), OutputPin->GetCPPTypeObject(), *InputPin->GetCPPType(), InputPin->GetCPPTypeObject()), TEXT("Incompatible types after successful link %s (%s) -> %s (%s) created in %s") , *OutputPin->GetPinPath(true) , *OutputPin->GetCPPType() , *InputPin->GetPinPath(true) , *InputPin->GetCPPType() , *GetPackage()->GetPathName()); } return true; } void URigVMController::RelinkSourceAndTargetPins(URigVMNode* Node, bool bSetupUndoRedo) { TArray SourcePins; TArray TargetPins; TArray LinksToRemove; // store source and target links const TArray RigVMLinks = Node->GetLinks(); for (URigVMLink* Link: RigVMLinks) { URigVMPin* SrcPin = Link->GetSourcePin(); if (SrcPin && SrcPin->GetNode() != Node) { SourcePins.AddUnique(SrcPin); LinksToRemove.AddUnique(Link); } URigVMPin* DstPin = Link->GetTargetPin(); if (DstPin && DstPin->GetNode() != Node) { TargetPins.AddUnique(DstPin); LinksToRemove.AddUnique(Link); } } if( SourcePins.Num() > 0 && TargetPins.Num() > 0 ) { // remove previous links for (URigVMLink* Link: LinksToRemove) { BreakLink(Link->GetSourcePin(), Link->GetTargetPin(), bSetupUndoRedo); } // relink pins if feasible TArray TargetHandled; TargetHandled.AddZeroed(TargetPins.Num()); for (URigVMPin* Src: SourcePins) { for (int32 Index = 0; Index < TargetPins.Num(); Index++) { if (!TargetHandled[Index]) { if (URigVMPin::CanLink(Src, TargetPins[Index], nullptr, nullptr)) { // execute pins can be linked to one target only so link to the 1st compatible target const bool bNeedNewLink = Src->IsExecuteContext() ? (Src->GetTargetLinks().Num() == 0) : true; if (bNeedNewLink) { AddLink(Src, TargetPins[Index], bSetupUndoRedo); TargetHandled[Index] = true; } } } } } } } bool URigVMController::BreakLink(const FString& InOutputPinPath, const FString& InInputPinPath, bool bSetupUndoRedo, bool bPrintPythonCommand) { if(!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMPin* OutputPin = Graph->FindPin(InOutputPinPath); if (OutputPin == nullptr) { ReportErrorf(TEXT("Cannot find pin '%s'."), *InOutputPinPath); return false; } OutputPin = OutputPin->GetPinForLink(); URigVMPin* InputPin = Graph->FindPin(InInputPinPath); if (InputPin == nullptr) { ReportErrorf(TEXT("Cannot find pin '%s'."), *InInputPinPath); return false; } InputPin = InputPin->GetPinForLink(); const bool bSuccess = BreakLink(OutputPin, InputPin, bSetupUndoRedo); if (bSuccess && bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').break_link('%s', '%s')"), *GraphName, *GetSanitizedPinPath(OutputPin->GetPinPath()), *GetSanitizedPinPath(InputPin->GetPinPath()))); } return bSuccess; } bool URigVMController::BreakLink(URigVMPin* OutputPin, URigVMPin* InputPin, bool bSetupUndoRedo) { if (!bIsTransacting && !IsGraphEditable()) { return false; } if(!IsValidPinForGraph(OutputPin) || !IsValidPinForGraph(InputPin)) { return false; } if (!OutputPin->IsLinkedTo(InputPin)) { return false; } ensure(InputPin->IsLinkedTo(OutputPin)); URigVMGraph* Graph = GetGraph(); check(Graph); if (Graph->IsA()) { ReportError(TEXT("Cannot break links in function library graphs.")); return false; } for (URigVMLink* Link : InputPin->Links) { if (Link->SourcePin == OutputPin && Link->TargetPin == InputPin) { FRigVMControllerCompileBracketScope CompileScope(this); FRigVMBreakLinkAction Action; if (bSetupUndoRedo) { Action = FRigVMBreakLinkAction(OutputPin, InputPin); Action.Title = FString::Printf(TEXT("Break Link")); ActionStack->BeginAction(Action); } OutputPin->Links.Remove(Link); InputPin->Links.Remove(Link); Graph->Links.Remove(Link); // each time a link is removed, existing orphaned pins // may become unused and thus can be removed RemoveUnusedOrphanedPins(OutputPin->GetNode()); RemoveUnusedOrphanedPins(InputPin->GetNode()); if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } Notify(ERigVMGraphNotifType::LinkRemoved, Link); DestroyObject(Link); if (bSetupUndoRedo) { UpdateRerouteNodeAfterChangingLinks(OutputPin, bSetupUndoRedo); UpdateRerouteNodeAfterChangingLinks(InputPin, bSetupUndoRedo); } if (bSetupUndoRedo) { ActionStack->EndAction(Action); } return true; } } return false; } bool URigVMController::BreakAllLinks(const FString& InPinPath, bool bAsInput, bool bSetupUndoRedo, bool bPrintPythonCommand) { if(!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMPin* Pin = Graph->FindPin(InPinPath); if (Pin == nullptr) { ReportErrorf(TEXT("Cannot find pin '%s'."), *InPinPath); return false; } Pin = Pin->GetPinForLink(); if (!IsValidPinForGraph(Pin)) { return false; } const bool bSuccess = BreakAllLinks(Pin, bAsInput, bSetupUndoRedo); if (bSuccess && bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').break_all_links('%s', %s)"), *GraphName, *GetSanitizedPinPath(Pin->GetPinPath()), bAsInput ? TEXT("True") : TEXT("False"))); } return bSuccess; } bool URigVMController::BreakAllLinks(URigVMPin* Pin, bool bAsInput, bool bSetupUndoRedo) { if (!bIsTransacting && !IsGraphEditable()) { return false; } if(!Pin->IsLinked(false)) { return false; } FRigVMControllerCompileBracketScope CompileScope(this); FRigVMBaseAction Action; if (bSetupUndoRedo) { Action.Title = FString::Printf(TEXT("Break All Links")); ActionStack->BeginAction(Action); } int32 LinksBroken = 0; { if (Pin->IsBoundToVariable() && bAsInput && bSetupUndoRedo) { UnbindPinFromVariable(Pin, bSetupUndoRedo); LinksBroken++; } TArray Links = Pin->GetLinks(); for (int32 LinkIndex = Links.Num() - 1; LinkIndex >= 0; LinkIndex--) { URigVMLink* Link = Links[LinkIndex]; if (bAsInput && Link->GetTargetPin() == Pin) { LinksBroken += BreakLink(Link->GetSourcePin(), Pin, bSetupUndoRedo) ? 1 : 0; } else if (!bAsInput && Link->GetSourcePin() == Pin) { LinksBroken += BreakLink(Pin, Link->GetTargetPin(), bSetupUndoRedo) ? 1 : 0; } } } if (bSetupUndoRedo) { if (LinksBroken > 0) { ActionStack->EndAction(Action); } else { ActionStack->CancelAction(Action, this); } } return LinksBroken > 0; } bool URigVMController::BreakAllLinksRecursive(URigVMPin* Pin, bool bAsInput, bool bTowardsParent, bool bSetupUndoRedo) { bool bBrokenLinks = false; { if (bTowardsParent) { URigVMPin* ParentPin = Pin->GetParentPin(); if (ParentPin) { bBrokenLinks |= BreakAllLinks(ParentPin, bAsInput, bSetupUndoRedo); bBrokenLinks |= BreakAllLinksRecursive(ParentPin, bAsInput, bTowardsParent, bSetupUndoRedo); } } else { for (URigVMPin* SubPin : Pin->SubPins) { bBrokenLinks |= BreakAllLinks(SubPin, bAsInput, bSetupUndoRedo); bBrokenLinks |= BreakAllLinksRecursive(SubPin, bAsInput, bTowardsParent, bSetupUndoRedo); } } } return bBrokenLinks; } FName URigVMController::AddExposedPin(const FName& InPinName, ERigVMPinDirection InDirection, const FString& InCPPType, const FName& InCPPTypeObjectPath, const FString& InDefaultValue, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return NAME_None; } if (!bIsTransacting && !IsGraphEditable()) { return NAME_None; } URigVMGraph* Graph = GetGraph(); check(Graph); if (Graph->IsTopLevelGraph()) { ReportError(TEXT("Exposed pins can only be edited on nested graphs.")); return NAME_None; } if (Graph->IsA()) { ReportError(TEXT("Cannot expose pins in function library graphs.")); return NAME_None; } URigVMLibraryNode* LibraryNode = Cast(Graph->GetOuter()); check(LibraryNode); UObject* CPPTypeObject = nullptr; if (!InCPPTypeObjectPath.IsNone()) { if (CPPTypeObject == nullptr) { CPPTypeObject = URigVMCompiler::GetScriptStructForCPPType(InCPPTypeObjectPath.ToString()); } if (CPPTypeObject == nullptr) { CPPTypeObject = RigVMTypeUtils::FindObjectFromCPPTypeObjectPath(InCPPTypeObjectPath.ToString()); } } // For now we do not allow wildcards for library nodes if (CPPTypeObject) { if(CPPTypeObject == RigVMTypeUtils::GetWildCardCPPTypeObject()) { ReportError(TEXT("Cannot expose pins of wildcard type in functions.")); return NAME_None; } } // only allow one IO / input exposed pin of type execute context per direction // except for aggregate nodes that can have multiple exec outputs bool bCheckForExecUniqueness = true; if (LibraryNode->IsA()) { bCheckForExecUniqueness = InDirection != ERigVMPinDirection::Output; } if (bCheckForExecUniqueness) { if(const UScriptStruct* CPPTypeStruct = Cast(CPPTypeObject)) { if(CPPTypeStruct->IsChildOf(FRigVMExecuteContext::StaticStruct())) { for(const URigVMPin* ExistingPin : LibraryNode->Pins) { if(ExistingPin->IsExecuteContext()) { return NAME_None; } } } } } FName PinName = GetUniqueName(InPinName, [LibraryNode](const FName& InName) { if(LibraryNode->FindPin(InName.ToString()) != nullptr) { return false; } const TArray& LocalVariables = LibraryNode->GetContainedGraph()->GetLocalVariables(true); for(const FRigVMGraphVariableDescription& VariableDescription : LocalVariables) { if (VariableDescription.Name == InName) { return false; } } return true; }, false, true); URigVMPin* Pin = NewObject(LibraryNode, PinName); Pin->CPPType = RigVMTypeUtils::PostProcessCPPType(InCPPType, CPPTypeObject); Pin->CPPTypeObjectPath = InCPPTypeObjectPath; Pin->bIsConstant = false; Pin->Direction = InDirection; AddNodePin(LibraryNode, Pin); if (Pin->IsStruct()) { FRigVMControllerGraphGuard GraphGuard(this, LibraryNode->GetGraph(), bSetupUndoRedo); FString DefaultValue = InDefaultValue; CreateDefaultValueForStructIfRequired(Pin->GetScriptStruct(), DefaultValue); { TGuardValue SuspendNotifications(bSuspendNotifications, true); AddPinsForStruct(Pin->GetScriptStruct(), LibraryNode, Pin, Pin->Direction, DefaultValue, false); } } FRigVMControllerCompileBracketScope CompileScope(this); FRigVMAddExposedPinAction Action(Pin); if (bSetupUndoRedo) { ActionStack->BeginAction(Action); } { FRigVMControllerGraphGuard GraphGuard(this, LibraryNode->GetGraph(), bSetupUndoRedo); Notify(ERigVMGraphNotifType::PinAdded, Pin); } if (!InDefaultValue.IsEmpty()) { FRigVMControllerGraphGuard GraphGuard(this, Pin->GetGraph(), bSetupUndoRedo); SetPinDefaultValue(Pin, InDefaultValue, true, bSetupUndoRedo, false); } URigVMFunctionEntryNode* EntryNode = Graph->GetEntryNode(); if (!EntryNode) { EntryNode = NewObject(Graph, TEXT("Entry")); Graph->Nodes.Add(EntryNode); { TGuardValue SuspendNotifications(bSuspendNotifications, true); RefreshFunctionPins(EntryNode); } Notify(ERigVMGraphNotifType::NodeAdded, EntryNode); } URigVMFunctionReturnNode* ReturnNode = Graph->GetReturnNode(); if (!ReturnNode) { ReturnNode = NewObject(Graph, TEXT("Return")); Graph->Nodes.Add(ReturnNode); { TGuardValue SuspendNotifications(bSuspendNotifications, true); RefreshFunctionPins(ReturnNode); } Notify(ERigVMGraphNotifType::NodeAdded, ReturnNode); } RefreshFunctionPins(EntryNode); RefreshFunctionPins(ReturnNode); RefreshFunctionReferences(LibraryNode, bSetupUndoRedo); if (bSetupUndoRedo) { ActionStack->EndAction(Action); } if (bPrintPythonCommand) { //AddExposedPin(const FName& InPinName, ERigVMPinDirection InDirection, const FString& InCPPType, const FName& InCPPTypeObjectPath, const FString& InDefaultValue, bool bSetupUndoRedo, bool bPrintPythonCommand) const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); static constexpr TCHAR AddExposedPinFormat[] = TEXT("blueprint.get_controller_by_name('%s').add_exposed_pin('%s', %s, '%s', '%s', '%s')"); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(AddExposedPinFormat, *GraphName, *GetSanitizedPinName(InPinName.ToString()), *RigVMPythonUtils::EnumValueToPythonString((int64)InDirection), *InCPPType, *InCPPTypeObjectPath.ToString(), *InDefaultValue)); } return PinName; } bool URigVMController::RemoveExposedPin(const FName& InPinName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); if (Graph->IsTopLevelGraph()) { ReportError(TEXT("Exposed pins can only be edited on nested graphs.")); return false; } if (Graph->IsA()) { ReportError(TEXT("Cannot remove exposed pins in function library graphs.")); return false; } URigVMLibraryNode* LibraryNode = Cast(Graph->GetOuter()); check(LibraryNode); URigVMPin* Pin = LibraryNode->FindPin(InPinName.ToString()); if (Pin == nullptr) { return false; } if(bSetupUndoRedo) { if(RequestBulkEditDialogDelegate.IsBound()) { FRigVMController_BulkEditResult Result = RequestBulkEditDialogDelegate.Execute(LibraryNode, ERigVMControllerBulkEditType::RemoveExposedPin); if(Result.bCanceled) { return false; } bSetupUndoRedo = Result.bSetupUndoRedo; } } FRigVMControllerCompileBracketScope CompileScope(this); FRigVMRemoveExposedPinAction Action(Pin); if (bSetupUndoRedo) { ActionStack->BeginAction(Action); } bool bSuccessfullyRemovedPin = false; { { FRigVMControllerGraphGuard GraphGuard(this, LibraryNode->GetGraph(), bSetupUndoRedo); bSuccessfullyRemovedPin = RemovePin(Pin, bSetupUndoRedo); } TArray NodesToRemove; for (URigVMNode* Node : Graph->GetNodes()) { if (URigVMVariableNode* VariableNode = Cast(Node)) { if (VariableNode->GetVariableName() == InPinName) { NodesToRemove.Add(VariableNode); } } } for (int32 i=NodesToRemove.Num()-1; i >= 0; --i) { RemoveNode(NodesToRemove[i], bSetupUndoRedo); } RefreshFunctionPins(Graph->GetEntryNode()); RefreshFunctionPins(Graph->GetReturnNode()); RefreshFunctionReferences(LibraryNode, false); } if (bSetupUndoRedo) { if (bSuccessfullyRemovedPin) { ActionStack->EndAction(Action); } else { ActionStack->CancelAction(Action, this); } } if (bSuccessfullyRemovedPin && bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').remove_exposed_pin('%s')"), *GraphName, *GetSanitizedPinName(InPinName.ToString()))); } return bSuccessfullyRemovedPin; } bool URigVMController::RenameExposedPin(const FName& InOldPinName, const FName& InNewPinName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); if (Graph->IsTopLevelGraph()) { ReportError(TEXT("Exposed pins can only be edited on nested graphs.")); return false; } if (Graph->IsA()) { ReportError(TEXT("Cannot rename exposed pins in function library graphs.")); return false; } URigVMLibraryNode* LibraryNode = Cast(Graph->GetOuter()); check(LibraryNode); URigVMPin* Pin = LibraryNode->FindPin(InOldPinName.ToString()); if (Pin == nullptr) { return false; } if (Pin->GetFName() == InNewPinName) { return false; } if(bSetupUndoRedo) { if(RequestBulkEditDialogDelegate.IsBound()) { FRigVMController_BulkEditResult Result = RequestBulkEditDialogDelegate.Execute(LibraryNode, ERigVMControllerBulkEditType::RenameExposedPin); if(Result.bCanceled) { return false; } bSetupUndoRedo = Result.bSetupUndoRedo; } } FName PinName = GetUniqueName(InNewPinName, [LibraryNode](const FName& InName) { const TArray& LocalVariables = LibraryNode->GetContainedGraph()->GetLocalVariables(true); for(const FRigVMGraphVariableDescription& VariableDescription : LocalVariables) { if (VariableDescription.Name == InName) { return false; } } return true; }, false, true); FRigVMControllerCompileBracketScope CompileScope(this); FRigVMRenameExposedPinAction Action; if (bSetupUndoRedo) { Action = FRigVMRenameExposedPinAction(Pin->GetFName(), PinName); ActionStack->BeginAction(Action); } struct Local { static bool RenamePin(URigVMController* InController, URigVMPin* InPin, const FName& InNewName) { FRigVMControllerGraphGuard GraphGuard(InController, InPin->GetGraph(), false); TArray Links; Links.Append(InPin->GetSourceLinks(true)); Links.Append(InPin->GetTargetLinks(true)); // store both the ptr + pin path for (URigVMLink* Link : Links) { Link->PrepareForCopy(); InController->Notify(ERigVMGraphNotifType::LinkRemoved, Link); } if (!InController->RenameObject(InPin, *InNewName.ToString())) { return false; } InPin->SetDisplayName(InNewName); // update the eventually stored pin path to the new name for (URigVMLink* Link : Links) { Link->PrepareForCopy(); } InController->Notify(ERigVMGraphNotifType::PinRenamed, InPin); for (URigVMLink* Link : Links) { InController->Notify(ERigVMGraphNotifType::LinkAdded, Link); } return true; } }; if (!Local::RenamePin(this, Pin, PinName)) { ActionStack->CancelAction(Action, this); return false; } TArray InterfaceNodes = {Graph->GetEntryNode(), Graph->GetReturnNode()}; for (URigVMTemplateNode* InterfaceNode : InterfaceNodes) { if (InterfaceNode) { if (URigVMPin* InterfacePin = InterfaceNode->FindPin(InOldPinName.ToString())) { Local::RenamePin(this, InterfacePin, PinName); } } } if (URigVMFunctionLibrary* FunctionLibrary = Cast(LibraryNode->GetGraph())) { FunctionLibrary->ForEachReference(LibraryNode->GetFName(), [this, InOldPinName, PinName](URigVMFunctionReferenceNode* ReferenceNode) { if (URigVMPin* EntryPin = ReferenceNode->FindPin(InOldPinName.ToString())) { FRigVMControllerGraphGuard GraphGuard(this, ReferenceNode->GetGraph(), false); Local::RenamePin(this, EntryPin, PinName); } }); } for (URigVMNode* Node : Graph->GetNodes()) { if (URigVMVariableNode* VariableNode = Cast(Node)) { if (VariableNode->GetVariableName() == InOldPinName) { SetVariableName(VariableNode, InNewPinName, bSetupUndoRedo); } } } if (bSetupUndoRedo) { ActionStack->EndAction(Action); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').rename_exposed_pin('%s', '%s')"), *GraphName, *GetSanitizedPinName(InOldPinName.ToString()), *GetSanitizedPinName(InNewPinName.ToString()))); } return true; } bool URigVMController::ChangeExposedPinType(const FName& InPinName, const FString& InCPPType, const FName& InCPPTypeObjectPath, bool& bSetupUndoRedo, bool bSetupOrphanPins, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); if (Graph->IsTopLevelGraph()) { ReportError(TEXT("Exposed pins can only be edited on nested graphs.")); return false; } if (Graph->IsA()) { ReportError(TEXT("Cannot change exposed pin types in function library graphs.")); return false; } URigVMLibraryNode* LibraryNode = Cast(Graph->GetOuter()); check(LibraryNode); URigVMPin* Pin = LibraryNode->FindPin(InPinName.ToString()); if (Pin == nullptr) { return false; } // We do not allow unresolving exposed pins if (InCPPType == RigVMTypeUtils::GetWildCardCPPType()) { ReportError(TEXT("Cannot change exposed pin type to wildcard.")); return false; } // only allow one exposed pin of type execute context per direction UObject* CPPTypeObject = nullptr; if (!InCPPTypeObjectPath.IsNone()) { CPPTypeObject = RigVMTypeUtils::FindObjectFromCPPTypeObjectPath(InCPPTypeObjectPath.ToString()); if(CPPTypeObject) { if(const UScriptStruct* CPPTypeStruct = Cast(CPPTypeObject)) { if(CPPTypeStruct->IsChildOf(FRigVMExecuteContext::StaticStruct())) { for(URigVMPin* ExistingPin : LibraryNode->Pins) { if(ExistingPin != Pin) { if(ExistingPin->IsExecuteContext()) { return false; } } } } } } } if (Pin->GetDirection() == ERigVMPinDirection::IO) { bool bIsExecute = false; if (CPPTypeObject) { if(const UScriptStruct* CPPTypeStruct = Cast(CPPTypeObject)) { if(CPPTypeStruct->IsChildOf(FRigVMExecuteContext::StaticStruct())) { bIsExecute = true; } } } if (!bIsExecute) { ReportAndNotifyError(TEXT("Input/Output pins only allow Execute Context types.")); return false; } } if(bSetupUndoRedo) { if(RequestBulkEditDialogDelegate.IsBound()) { const FRigVMController_BulkEditResult Result = RequestBulkEditDialogDelegate.Execute(LibraryNode, ERigVMControllerBulkEditType::ChangeExposedPinType); if(Result.bCanceled) { return false; } bSetupUndoRedo = Result.bSetupUndoRedo; } } FRigVMControllerCompileBracketScope CompileScope(this); FRigVMBaseAction Action; if (bSetupUndoRedo) { Action.Title = FString::Printf(TEXT("Change Exposed Pin Type")); ActionStack->BeginAction(Action); } FRigVMRegistry& Registry = FRigVMRegistry::Get(); // If the pin does not support the type, first break all links in the contained graph to this pin (and subpins) TArray InterfaceNodes = {Graph->GetEntryNode(), Graph->GetReturnNode()}; // Break all links to this pin { TArray InterfacePinLinks; TArray ExtendedInterfacePins; for (URigVMNode* Node : Graph->GetNodes()) { if (Node->IsA() || Node->IsA()) { if (URigVMPin* InterfacePin = Node->FindPin(Pin->GetName())) { ExtendedInterfacePins.Add(InterfacePin); } } else if (URigVMVariableNode* VariableNode = Cast(Node)) { if (VariableNode->GetVariableName() == InPinName) { ExtendedInterfacePins.Add(VariableNode->GetValuePin()); } } } for (URigVMPin* InterfacePin : ExtendedInterfacePins) { TArray PinsToProcess; PinsToProcess.Add(InterfacePin); for (int32 i=0; iGetLinks()); PinsToProcess.Append(PinsToProcess[i]->GetSubPins()); } } for (int32 i=0; iGetSourcePin(), InterfacePinLinks[i]->GetTargetPin(), bSetupUndoRedo); } } // Change pin type of the library node in the function library { bool bSuccessChangingType; { FRigVMControllerGraphGuard GraphGuard(this, LibraryNode->GetGraph(), bSetupUndoRedo); bSuccessChangingType = ChangePinType(Pin, InCPPType, InCPPTypeObjectPath, bSetupUndoRedo, bSetupOrphanPins); if (bSuccessChangingType) { RemoveUnusedOrphanedPins(LibraryNode); } } if (!bSuccessChangingType) { if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return false; } } // Repopulate pin on interface nodes for (URigVMTemplateNode* InterfaceNode : InterfaceNodes) { if (InterfaceNode) { RepopulatePinsOnNode(InterfaceNode, true, bSetupOrphanPins, true); RemoveUnusedOrphanedPins(InterfaceNode); } } // Change pin type on function references if (URigVMFunctionLibrary* FunctionLibrary = Cast(LibraryNode->GetGraph())) { FunctionLibrary->ForEachReference(LibraryNode->GetFName(), [this, &Pin, InCPPType, InCPPTypeObjectPath, bSetupUndoRedo, bSetupOrphanPins](URigVMFunctionReferenceNode* ReferenceNode) { if (URigVMPin* ReferencedNodePin = ReferenceNode->FindPin(Pin->GetName())) { FRigVMControllerGraphGuard GraphGuard(this, ReferenceNode->GetGraph(), bSetupUndoRedo); ChangePinType(ReferencedNodePin, InCPPType, InCPPTypeObjectPath, bSetupUndoRedo, bSetupOrphanPins); RemoveUnusedOrphanedPins(ReferenceNode); } }); } // Change pin types on input variable nodes for (URigVMNode* Node : Graph->GetNodes()) { if (URigVMVariableNode* VariableNode = Cast(Node)) { if (VariableNode->GetVariableName() == InPinName) { URigVMPin* ValuePin = VariableNode->FindPin(URigVMVariableNode::ValueName); if (ValuePin) { ChangePinType(ValuePin, InCPPType, InCPPTypeObjectPath, bSetupUndoRedo, bSetupOrphanPins); RemoveUnusedOrphanedPins(VariableNode); } } } } if (bSetupUndoRedo) { ActionStack->EndAction(Action); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').change_exposed_pin_type('%s', '%s', '%s', %s)"), *GraphName, *GetSanitizedPinName(InPinName.ToString()), *InCPPType, *InCPPTypeObjectPath.ToString(), (bSetupUndoRedo) ? TEXT("True") : TEXT("False"))); } return true; } bool URigVMController::SetExposedPinIndex(const FName& InPinName, int32 InNewIndex, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); FString PinPath = InPinName.ToString(); if (PinPath.Contains(TEXT("."))) { ReportError(TEXT("Cannot change pin index for pins on nodes for now - only within collapse nodes.")); return false; } URigVMLibraryNode* LibraryNode = Cast(Graph->GetOuter()); if (LibraryNode == nullptr) { ReportError(TEXT("Graph is not under a Collapse Node")); return false; } URigVMPin* Pin = LibraryNode->FindPin(PinPath); if (Pin == nullptr) { ReportErrorf(TEXT("Cannot find exposed pin '%s'."), *PinPath); return false; } if (Pin->GetPinIndex() == InNewIndex) { return true; // Nothing to do, do not fail } if (InNewIndex < 0 || InNewIndex >= LibraryNode->GetPins().Num()) { ReportErrorf(TEXT("Invalid new pin index '%d'."), InNewIndex); return false; } FRigVMControllerCompileBracketScope CompileBracketScope(this); FRigVMSetPinIndexAction PinIndexAction(Pin, InNewIndex); { LibraryNode->Pins.Remove(Pin); LibraryNode->Pins.Insert(Pin, InNewIndex); FRigVMControllerGraphGuard GraphGuard(this, LibraryNode->GetGraph(), false); Notify(ERigVMGraphNotifType::PinIndexChanged, Pin); } RefreshFunctionPins(LibraryNode->GetEntryNode()); RefreshFunctionPins(LibraryNode->GetReturnNode()); RefreshFunctionReferences(LibraryNode, false); if (bSetupUndoRedo) { ActionStack->AddAction(PinIndexAction); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').set_exposed_pin_index('%s', %d)"), *GraphName, *GetSanitizedPinName(InPinName.ToString()), InNewIndex)); } return true; } URigVMFunctionReferenceNode* URigVMController::AddFunctionReferenceNode(URigVMLibraryNode* InFunctionDefinition, const FVector2D& InNodePosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return nullptr; } if (!InFunctionDefinition) { return nullptr; } if (URigVMFunctionReferenceNode* ReferenceNode = AddFunctionReferenceNodeFromDescription(InFunctionDefinition->GetFunctionHeader(), InNodePosition, InNodeName, bSetupUndoRedo, bPrintPythonCommand)) { return ReferenceNode; } return nullptr; } URigVMFunctionReferenceNode* URigVMController::AddFunctionReferenceNodeFromDescription(const FRigVMGraphFunctionHeader& InFunctionDefinition, const FVector2D& InNodePosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand, bool bAllowPrivateFunctions) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); if (Graph->IsA()) { ReportError(TEXT("Cannot add function reference nodes to function library graphs.")); return nullptr; } if(!CanAddFunctionRefForDefinition(InFunctionDefinition, true, bAllowPrivateFunctions)) { return nullptr; } FString NodeName = GetValidNodeName(InNodeName.IsEmpty() ? InFunctionDefinition.Name.ToString() : InNodeName); URigVMFunctionReferenceNode* FunctionRefNode = NewObject(Graph, *NodeName); FunctionRefNode->Position = InNodePosition; FunctionRefNode->ReferencedFunctionHeader = InFunctionDefinition; Graph->Nodes.Add(FunctionRefNode); FRigVMControllerCompileBracketScope CompileScope(this); RepopulatePinsOnNode(FunctionRefNode, false, false, false); Notify(ERigVMGraphNotifType::NodeAdded, FunctionRefNode); if (URigVMBuildData* BuildData = URigVMBuildData::Get()) { BuildData->RegisterFunctionReference(FunctionRefNode->GetReferencedFunctionHeader().LibraryPointer, FunctionRefNode); } for (const FRigVMGraphFunctionArgument& Argument : InFunctionDefinition.Arguments) { if (URigVMPin* TargetPin = FunctionRefNode->FindPin(Argument.Name.ToString())) { const FString& DefaultValue = Argument.DefaultValue; if (!DefaultValue.IsEmpty()) { SetPinDefaultValue(TargetPin, DefaultValue, true, false, false); } } } if (bSetupUndoRedo) { FRigVMInverseAction InverseAction; InverseAction.Title = TEXT("Add function node"); ActionStack->BeginAction(InverseAction); ActionStack->AddAction(FRigVMRemoveNodeAction(FunctionRefNode, this)); ActionStack->EndAction(InverseAction); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); const FString FunctionDefinitionName = GetSanitizedNodeName(InFunctionDefinition.Name.ToString()); bool bLocal = false; if(IRigVMClientHost* ClientHost = GetImplementingOuter()) { if (InFunctionDefinition.LibraryPointer.HostObject == Cast(ClientHost->GetRigVMGraphFunctionHost())) { bLocal = true; RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_function_reference_node(library.find_function('%s'), %s, '%s')"), *GraphName, *FunctionDefinitionName, *RigVMPythonUtils::Vector2DToPythonString(InNodePosition), *NodeName)); } } if (!bLocal) { RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_external_function_reference_node('%s', '%s', %s, '%s')"), *GraphName, *InFunctionDefinition.LibraryPointer.HostObject.ToString(), *FunctionDefinitionName, *RigVMPythonUtils::Vector2DToPythonString(InNodePosition), *NodeName)); } } return FunctionRefNode; } URigVMFunctionReferenceNode* URigVMController::AddExternalFunctionReferenceNode(const FString& InHostPath, const FName& InFunctionName, const FVector2D& InNodePosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); if (Graph->IsA()) { ReportError(TEXT("Cannot add function reference nodes to function library graphs.")); return nullptr; } UObject* HostObject = StaticLoadObject(UObject::StaticClass(), NULL, *InHostPath, NULL, LOAD_None, NULL); if (!HostObject) { ReportErrorf(TEXT("Failed to load the Host object %s."), *InHostPath); return nullptr; } IRigVMGraphFunctionHost* FunctionHost = Cast(HostObject); if (!FunctionHost) { ReportError(TEXT("Host object is not a IRigVMGraphFunctionHost.")); return nullptr; } FRigVMGraphFunctionData* Data = FunctionHost->GetRigVMGraphFunctionStore()->FindFunctionByName(InFunctionName); if (!Data) { ReportErrorf(TEXT("Function %s not found in host %s."), *InFunctionName.ToString(), *InHostPath); return nullptr; } return AddFunctionReferenceNodeFromDescription(Data->Header, InNodePosition, InNodeName, bSetupUndoRedo, bPrintPythonCommand); } bool URigVMController::SetRemappedVariable(URigVMFunctionReferenceNode* InFunctionRefNode, const FName& InInnerVariableName, const FName& InOuterVariableName, bool bSetupUndoRedo) { if(!InFunctionRefNode) { return false; } if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } if(InInnerVariableName.IsNone()) { return false; } const FName OldOuterVariableName = InFunctionRefNode->GetOuterVariableName(InInnerVariableName); if(OldOuterVariableName == InOuterVariableName) { return false; } if(!InFunctionRefNode->RequiresVariableRemapping()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); FRigVMExternalVariable InnerExternalVariable; { if (FRigVMExternalVariable* Variable = InFunctionRefNode->ReferencedFunctionHeader.ExternalVariables.FindByPredicate([InInnerVariableName](const FRigVMExternalVariable& Variable) { return Variable.Name == InInnerVariableName; })) { InnerExternalVariable = *Variable; } } if(!InnerExternalVariable.IsValid(true)) { ReportErrorf(TEXT("External variable '%s' cannot be found."), *InInnerVariableName.ToString()); return false; } ensure(InnerExternalVariable.Name == InInnerVariableName); if(InOuterVariableName.IsNone()) { InFunctionRefNode->Modify(); InFunctionRefNode->VariableMap.Remove(InInnerVariableName); } else { const FRigVMExternalVariable OuterExternalVariable = GetVariableByName(InOuterVariableName); if(!OuterExternalVariable.IsValid(true)) { ReportErrorf(TEXT("External variable '%s' cannot be found."), *InOuterVariableName.ToString()); return false; } ensure(OuterExternalVariable.Name == InOuterVariableName); if((InnerExternalVariable.TypeObject != nullptr) && (InnerExternalVariable.TypeObject != OuterExternalVariable.TypeObject)) { ReportErrorf(TEXT("Inner and Outer External variables '%s' and '%s' are not compatible."), *InInnerVariableName.ToString(), *InOuterVariableName.ToString()); return false; } if((InnerExternalVariable.TypeObject == nullptr) && (InnerExternalVariable.TypeName != OuterExternalVariable.TypeName)) { ReportErrorf(TEXT("Inner and Outer External variables '%s' and '%s' are not compatible."), *InInnerVariableName.ToString(), *InOuterVariableName.ToString()); return false; } InFunctionRefNode->Modify(); InFunctionRefNode->VariableMap.FindOrAdd(InInnerVariableName) = InOuterVariableName; } Notify(ERigVMGraphNotifType::VariableRemappingChanged, InFunctionRefNode); FRigVMControllerCompileBracketScope CompileScope(this); if(bSetupUndoRedo) { ActionStack->AddAction(FRigVMSetRemappedVariableAction(InFunctionRefNode, InInnerVariableName, OldOuterVariableName, InOuterVariableName)); } return true; } URigVMLibraryNode* URigVMController::AddFunctionToLibrary(const FName& InFunctionName, bool bMutable, const FVector2D& InNodePosition, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); if (!Graph->IsA()) { ReportError(TEXT("Can only add function definitions to function library graphs.")); return nullptr; } FString FunctionName = GetValidNodeName(InFunctionName.IsNone() ? FString(TEXT("Function")) : InFunctionName.ToString()); URigVMCollapseNode* CollapseNode = NewObject(Graph, *FunctionName); CollapseNode->ContainedGraph = NewObject(CollapseNode, TEXT("ContainedGraph")); CollapseNode->Position = InNodePosition; Graph->Nodes.Add(CollapseNode); FRigVMControllerCompileBracketScope CompileScope(this); Notify(ERigVMGraphNotifType::NodeAdded, CollapseNode); if (bMutable) { const UScriptStruct* ExecuteContextStruct = FRigVMExecuteContext::StaticStruct(); FRigVMControllerGraphGuard GraphGuard(this, CollapseNode->ContainedGraph, bSetupUndoRedo); AddExposedPin(FRigVMStruct::ExecuteContextName, ERigVMPinDirection::IO, FString::Printf(TEXT("F%s"), *ExecuteContextStruct->GetName()), *ExecuteContextStruct->GetPathName(), FString(), bSetupUndoRedo); } { FRigVMControllerGraphGuard GraphGuard(this, CollapseNode->GetContainedGraph(), false); TGuardValue GuardEditGraph(CollapseNode->ContainedGraph->bEditable, true); URigVMNode* EntryNode = CollapseNode->ContainedGraph->FindNode(TEXT("Entry")); URigVMNode* ReturnNode = CollapseNode->ContainedGraph->FindNode(TEXT("Return")); if (EntryNode == nullptr) { EntryNode = NewObject(CollapseNode->ContainedGraph, TEXT("Entry")); CollapseNode->ContainedGraph->Nodes.Add(EntryNode); { TGuardValue SuspendNotifications(bSuspendNotifications, true); RefreshFunctionPins(EntryNode); } EntryNode->Position = FVector2D(-250.f, 0.f); Notify(ERigVMGraphNotifType::NodeAdded, EntryNode); } if (ReturnNode == nullptr) { ReturnNode = NewObject(CollapseNode->ContainedGraph, TEXT("Return")); CollapseNode->ContainedGraph->Nodes.Add(ReturnNode); { TGuardValue SuspendNotifications(bSuspendNotifications, true); RefreshFunctionPins(ReturnNode); } ReturnNode->Position = FVector2D(250.f, 0.f); Notify(ERigVMGraphNotifType::NodeAdded, ReturnNode); } if (bMutable) { AddLink(EntryNode->FindPin(FRigVMStruct::ExecuteContextName.ToString()), ReturnNode->FindPin(FRigVMStruct::ExecuteContextName.ToString()), false); } } if (bSetupUndoRedo) { FRigVMInverseAction InverseAction; InverseAction.Title = TEXT("Add function to library"); ActionStack->BeginAction(InverseAction); ActionStack->AddAction(FRigVMRemoveNodeAction(CollapseNode, this)); ActionStack->EndAction(InverseAction); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); //AddFunctionToLibrary(const FName& InFunctionName, bool bMutable, const FVector2D& InNodePosition, bool bSetupUndoRedo, bool bPrintPythonCommand) RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("library_controller.add_function_to_library('%s', %s, %s)"), *GetSanitizedNodeName(InFunctionName.ToString()), (bMutable) ? TEXT("True") : TEXT("False"), *RigVMPythonUtils::Vector2DToPythonString(InNodePosition))); } return CollapseNode; } bool URigVMController::RemoveFunctionFromLibrary(const FName& InFunctionName, bool bSetupUndoRedo) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); if (!Graph->IsA()) { ReportError(TEXT("Can only remove function definitions from function library graphs.")); return false; } return RemoveNodeByName(InFunctionName, bSetupUndoRedo); } bool URigVMController::RenameFunction(const FName& InOldFunctionName, const FName& InNewFunctionName, bool bSetupUndoRedo) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); if (!Graph->IsA()) { ReportError(TEXT("Can only remove function definitions from function library graphs.")); return false; } URigVMNode* Node = Graph->FindNode(InOldFunctionName.ToString()); if (!Node) { ReportErrorf(TEXT("Could not find function called '%s'."), *InOldFunctionName.ToString()); return false; } return RenameNode(Node, InNewFunctionName, bSetupUndoRedo); } bool URigVMController::MarkFunctionAsPublic(const FName& InFunctionName, bool bInIsPublic, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); if (!Graph->IsA()) { ReportError(TEXT("Can only change function definitions from function library graphs.")); return false; } URigVMNode* Node = Graph->FindNode(InFunctionName.ToString()); if (!Node) { ReportErrorf(TEXT("Could not find function called '%s'."), *InFunctionName.ToString()); return false; } if (URigVMFunctionLibrary* FunctionLibrary = Cast(Graph)) { bool bOldIsPublic = FunctionLibrary->PublicFunctionNames.Contains(InFunctionName); if ((bInIsPublic && bOldIsPublic) || (!bInIsPublic && !bOldIsPublic)) { return true; } if (bSetupUndoRedo) { FRigVMBaseAction BaseAction; BaseAction.Title = FString::Printf(TEXT("Mark function %s as %s"), *InFunctionName.ToString(), (bInIsPublic) ? TEXT("Public") : TEXT("Private")); ActionStack->BeginAction(BaseAction); ActionStack->AddAction(FRigVMMarkFunctionPublicAction(InFunctionName, bInIsPublic)); ActionStack->EndAction(BaseAction); } if (bInIsPublic) { FunctionLibrary->PublicFunctionNames.Add(InFunctionName); } else { FunctionLibrary->PublicFunctionNames.Remove(InFunctionName); } } Notify(ERigVMGraphNotifType::FunctionAccessChanged, Node); if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } if (bPrintPythonCommand) { RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("library_controller.mark_function_as_public('%s', %s)"), *GetSanitizedNodeName(InFunctionName.ToString()), (bInIsPublic) ? TEXT("True") : TEXT("False"))); } return true; } bool URigVMController::IsFunctionPublic(const FName& InFunctionName) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); if (!Graph->IsA()) { ReportError(TEXT("Can only check function definitions from function library graphs.")); return false; } URigVMNode* Node = Graph->FindNode(InFunctionName.ToString()); if (!Node) { ReportErrorf(TEXT("Could not find function called '%s'."), *InFunctionName.ToString()); return false; } if (URigVMFunctionLibrary* FunctionLibrary = Cast(Graph)) { return FunctionLibrary->PublicFunctionNames.Contains(InFunctionName); } return false; } FRigVMGraphVariableDescription URigVMController::AddLocalVariable(const FName& InVariableName, const FString& InCPPType, UObject* InCPPTypeObject, const FString& InDefaultValue, bool bSetupUndoRedo, bool bPrintPythonCommand) { FRigVMGraphVariableDescription NewVariable; if (!IsValidGraph()) { return NewVariable; } if (!bIsTransacting && !IsGraphEditable()) { return NewVariable; } URigVMGraph* Graph = GetGraph(); check(Graph); // Check this is the main graph of a function { if (URigVMLibraryNode* LibraryNode = Cast(Graph->GetOuter())) { if (!LibraryNode->GetOuter()->IsA()) { return NewVariable; } } else { return NewVariable; } } FName VariableName = GetUniqueName(InVariableName, [Graph](const FName& InName) { for (FRigVMGraphVariableDescription LocalVariable : Graph->GetLocalVariables(true)) { if (LocalVariable.Name == InName) { return false; } } return true; }, false, true); NewVariable.Name = VariableName; NewVariable.CPPType = InCPPType; NewVariable.CPPTypeObject = InCPPTypeObject; NewVariable.DefaultValue = InDefaultValue; Graph->LocalVariables.Add(NewVariable); FRigVMControllerCompileBracketScope CompileScope(this); for (URigVMNode* Node : Graph->GetNodes()) { if (URigVMVariableNode* VariableNode = Cast(Node)) { if (VariableName == VariableNode->GetVariableName()) { RefreshVariableNode(VariableNode->GetFName(), VariableName, InCPPType, InCPPTypeObject, bSetupUndoRedo, false); } } } if (bSetupUndoRedo) { FRigVMInverseAction InverseAction; InverseAction.Title = FString::Printf(TEXT("Add Local Variable %s"), *InVariableName.ToString()); ActionStack->BeginAction(InverseAction); ActionStack->AddAction(FRigVMRemoveLocalVariableAction(NewVariable)); ActionStack->EndAction(InverseAction); } if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').add_local_variable_from_object_path('%s', '%s', '%s', '%s')"), *GraphName, *NewVariable.Name.ToString(), *NewVariable.CPPType, (NewVariable.CPPTypeObject) ? *NewVariable.CPPTypeObject->GetPathName() : *FString(), *NewVariable.DefaultValue)); } return NewVariable; } FRigVMGraphVariableDescription URigVMController::AddLocalVariableFromObjectPath(const FName& InVariableName, const FString& InCPPType, const FString& InCPPTypeObjectPath, const FString& InDefaultValue, bool bSetupUndoRedo) { FRigVMGraphVariableDescription Description; if (!IsValidGraph()) { return Description; } if (!bIsTransacting && !IsGraphEditable()) { return Description; } UObject* CPPTypeObject = nullptr; if (!InCPPTypeObjectPath.IsEmpty()) { CPPTypeObject = RigVMTypeUtils::FindObjectFromCPPTypeObjectPath(InCPPTypeObjectPath); if (CPPTypeObject == nullptr) { ReportErrorf(TEXT("Cannot find cpp type object for path '%s'."), *InCPPTypeObjectPath); return Description; } } return AddLocalVariable(InVariableName, InCPPType, CPPTypeObject, InDefaultValue, bSetupUndoRedo); } bool URigVMController::RemoveLocalVariable(const FName& InVariableName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); TArray& LocalVariables = Graph->LocalVariables; int32 FoundIndex = INDEX_NONE; for (int32 Index = 0; Index < LocalVariables.Num(); ++Index) { if (LocalVariables[Index].Name == InVariableName) { FoundIndex = Index; break; } } if (FoundIndex != INDEX_NONE) { FRigVMControllerCompileBracketScope CompileScope(this); FRigVMBaseAction BaseAction; if (bSetupUndoRedo) { BaseAction.Title = FString::Printf(TEXT("Remove Local Variable %s"), *InVariableName.ToString()); ActionStack->BeginAction(BaseAction); } const FString VarNameStr = InVariableName.ToString(); bool bSwitchToMemberVariable = false; FRigVMExternalVariable ExternalVariableToSwitch; { TArray ExternalVariables; if (GetExternalVariablesDelegate.IsBound()) { ExternalVariables.Append(GetExternalVariablesDelegate.Execute(GetGraph())); } for (const FRigVMExternalVariable& ExternalVariable : ExternalVariables) { if (ExternalVariable.Name == InVariableName) { bSwitchToMemberVariable = true; ExternalVariableToSwitch = ExternalVariable; break; } } } if (!bSwitchToMemberVariable) { TArray Nodes = Graph->GetNodes(); for (URigVMNode* Node : Nodes) { if (URigVMVariableNode* VariableNode = Cast(Node)) { if (URigVMPin* VariablePin = VariableNode->FindPin(URigVMVariableNode::VariableName)) { if (VariablePin->GetDefaultValue() == VarNameStr) { RemoveNode(Node, bSetupUndoRedo, true); continue; } } } } } else { TArray Nodes = Graph->GetNodes(); for (URigVMNode* Node : Nodes) { if (URigVMVariableNode* VariableNode = Cast(Node)) { if (URigVMPin* VariablePin = VariableNode->FindPin(URigVMVariableNode::VariableName)) { if (VariablePin->GetDefaultValue() == VarNameStr) { RefreshVariableNode(VariableNode->GetFName(), ExternalVariableToSwitch.Name, ExternalVariableToSwitch.TypeName.ToString(), ExternalVariableToSwitch.TypeObject, bSetupUndoRedo, false); continue; } } } TArray AllPins = Node->GetAllPinsRecursively(); for (URigVMPin* Pin : AllPins) { if (Pin->GetBoundVariableName() == InVariableName.ToString()) { if (Pin->GetCPPType() != ExternalVariableToSwitch.TypeName.ToString() || Pin->GetCPPTypeObject() == ExternalVariableToSwitch.TypeObject) { UnbindPinFromVariable(Pin, bSetupUndoRedo); } } } } } if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } if (bSetupUndoRedo) { ActionStack->AddAction(FRigVMRemoveLocalVariableAction(LocalVariables[FoundIndex])); } LocalVariables.RemoveAt(FoundIndex); if (bSetupUndoRedo) { ActionStack->EndAction(BaseAction); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').remove_local_variable('%s')"), *GraphName, *GetSanitizedVariableName(InVariableName.ToString()))); } return true; } return false; } bool URigVMController::RenameLocalVariable(const FName& InVariableName, const FName& InNewVariableName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); TArray& LocalVariables = Graph->LocalVariables; int32 FoundIndex = INDEX_NONE; for (int32 Index = 0; Index < LocalVariables.Num(); ++Index) { if (LocalVariables[Index].Name == InVariableName) { FoundIndex = Index; break; } } if (FoundIndex == INDEX_NONE) { return false; } for (int32 Index = 0; Index < LocalVariables.Num(); ++Index) { if (LocalVariables[Index].Name == InNewVariableName) { return false; } } if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } FRigVMControllerCompileBracketScope CompileScope(this); if (bSetupUndoRedo) { FRigVMBaseAction BaseAction; BaseAction.Title = FString::Printf(TEXT("Rename Local Variable %s to %s"), *InVariableName.ToString(), *InNewVariableName.ToString()); ActionStack->BeginAction(BaseAction); ActionStack->AddAction(FRigVMRenameLocalVariableAction(LocalVariables[FoundIndex].Name, InNewVariableName)); ActionStack->EndAction(BaseAction); } LocalVariables[FoundIndex].Name = InNewVariableName; TArray RenamedNodes; for (URigVMNode* Node : Graph->Nodes) { if(URigVMVariableNode* VariableNode = Cast(Node)) { if (VariableNode->GetVariableName() == InVariableName) { VariableNode->FindPin(URigVMVariableNode::VariableName)->DefaultValue = InNewVariableName.ToString(); RenamedNodes.Add(Node); } } } for (URigVMNode* RenamedNode : RenamedNodes) { Notify(ERigVMGraphNotifType::VariableRenamed, RenamedNode); if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').rename_local_variable('%s', '%s')"), *GraphName, *GetSanitizedVariableName(InVariableName.ToString()), *GetSanitizedVariableName(InNewVariableName.ToString()))); } return true; } bool URigVMController::SetLocalVariableType(const FName& InVariableName, const FString& InCPPType, UObject* InCPPTypeObject, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); TArray& LocalVariables = Graph->LocalVariables; int32 FoundIndex = INDEX_NONE; for (int32 Index = 0; Index < LocalVariables.Num(); ++Index) { if (LocalVariables[Index].Name == InVariableName) { FoundIndex = Index; break; } } if (FoundIndex == INDEX_NONE) { return false; } if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } FRigVMControllerCompileBracketScope CompileScope(this); FRigVMBaseAction BaseAction; if (bSetupUndoRedo) { BaseAction.Title = FString::Printf(TEXT("Change Local Variable type %s to %s"), *InVariableName.ToString(), *InCPPType); ActionStack->BeginAction(BaseAction); ActionStack->AddAction(FRigVMChangeLocalVariableTypeAction(LocalVariables[FoundIndex], InCPPType, InCPPTypeObject)); } LocalVariables[FoundIndex].CPPType = InCPPType; LocalVariables[FoundIndex].CPPTypeObject = InCPPTypeObject; // Set default value if (UScriptStruct* ScriptStruct = Cast(InCPPTypeObject)) { FString DefaultValue; CreateDefaultValueForStructIfRequired(ScriptStruct, DefaultValue); LocalVariables[FoundIndex].DefaultValue = DefaultValue; } else { LocalVariables[FoundIndex].DefaultValue = FString(); } // Change pin types on variable nodes TArray Nodes = Graph->GetNodes(); for (URigVMNode* Node : Nodes) { if (URigVMVariableNode* VariableNode = Cast(Node)) { if (URigVMPin* VariablePin = VariableNode->FindPin(URigVMVariableNode::VariableName)) { if (VariablePin->GetDefaultValue() == InVariableName.ToString()) { RefreshVariableNode(Node->GetFName(), InVariableName, InCPPType, InCPPTypeObject, bSetupUndoRedo, false); continue; } } } const TArray AllPins = Node->GetAllPinsRecursively(); for (URigVMPin* Pin : AllPins) { if (Pin->GetBoundVariableName() == InVariableName.ToString()) { UnbindPinFromVariable(Pin, bSetupUndoRedo); } } } if (bSetupUndoRedo) { ActionStack->EndAction(BaseAction); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); //bool URigVMController::SetLocalVariableType(const FName& InVariableName, const FString& InCPPType, UObject* InCPPTypeObject, bool bSetupUndoRedo, bool bPrintPythonCommand) RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').set_local_variable_type_from_object_path('%s', '%s', '%s')"), *GraphName, *GetSanitizedVariableName(InVariableName.ToString()), *InCPPType, (InCPPTypeObject) ? *InCPPTypeObject->GetPathName() : *FString())); } return true; } bool URigVMController::SetLocalVariableTypeFromObjectPath(const FName& InVariableName, const FString& InCPPType, const FString& InCPPTypeObjectPath, bool bSetupUndoRedo, bool bPrintPythonCommand) { UObject* CPPTypeObject = nullptr; if (!InCPPTypeObjectPath.IsEmpty()) { CPPTypeObject = RigVMTypeUtils::FindObjectFromCPPTypeObjectPath(InCPPTypeObjectPath); if (CPPTypeObject == nullptr) { ReportErrorf(TEXT("Cannot find cpp type object for path '%s'."), *InCPPTypeObjectPath); return false; } } return SetLocalVariableType(InVariableName, InCPPType, CPPTypeObject, bSetupUndoRedo, bPrintPythonCommand); } bool URigVMController::SetLocalVariableDefaultValue(const FName& InVariableName, const FString& InDefaultValue, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); TArray& LocalVariables = Graph->LocalVariables; int32 FoundIndex = INDEX_NONE; for (int32 Index = 0; Index < LocalVariables.Num(); ++Index) { if (LocalVariables[Index].Name == InVariableName) { FoundIndex = Index; break; } } if (FoundIndex == INDEX_NONE) { return false; } FRigVMControllerCompileBracketScope CompileScope(this); if (bSetupUndoRedo) { FRigVMInverseAction InverseAction; InverseAction.Title = FString::Printf(TEXT("Change Local Variable %s default value"), *InVariableName.ToString()); ActionStack->BeginAction(InverseAction); ActionStack->AddAction(FRigVMChangeLocalVariableDefaultValueAction(LocalVariables[FoundIndex], InDefaultValue)); ActionStack->EndAction(InverseAction); } FRigVMGraphVariableDescription& VariableDescription = LocalVariables[FoundIndex]; VariableDescription.DefaultValue = InDefaultValue; if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').set_local_variable_default_value('%s', '%s')"), *GraphName, *GetSanitizedVariableName(InVariableName.ToString()), *InDefaultValue)); } return true; } URigVMUserWorkflowOptions* URigVMController::MakeOptionsForWorkflow(UObject* InSubject, const FRigVMUserWorkflow& InWorkflow) { URigVMUserWorkflowOptions* Options = nullptr; UClass* Class = InWorkflow.GetOptionsClass(); if(Class == nullptr) { return Options; } if(!Class->IsChildOf(URigVMUserWorkflowOptions::StaticClass())) { return Options; } Options = NewObject(GetTransientPackage(), Class, NAME_None, RF_Transient); Options->Subject = InSubject; Options->Workflow = InWorkflow; TWeakObjectPtr WeakThis = this; Options->ReportDelegate = FRigVMReportDelegate::CreateLambda([WeakThis]( EMessageSeverity::Type InSeverity, UObject* InSubject, const FString& InMessage) { if(URigVMController* StrongThis = WeakThis.Get()) { if(InSeverity == EMessageSeverity::Error) { StrongThis->ReportAndNotifyError(InMessage); } else if(InSeverity == EMessageSeverity::Warning || InSeverity == EMessageSeverity::PerformanceWarning) { StrongThis->ReportAndNotifyWarning(InMessage); } else { StrongThis->ReportInfo(InMessage); } } } ); if(ConfigureWorkflowOptionsDelegate.IsBound()) { ConfigureWorkflowOptionsDelegate.Execute(Options); } return Options; } bool URigVMController::PerformUserWorkflow(const FRigVMUserWorkflow& InWorkflow, const URigVMUserWorkflowOptions* InOptions, bool bSetupUndoRedo) { if(!InWorkflow.IsValid() || !ensure(InOptions != nullptr)) { return false; } FRigVMBaseAction Bracket; Bracket.Title = InWorkflow.GetTitle(); ActionStack->BeginAction(Bracket); const bool bSuccess = InWorkflow.Perform(InOptions, this); ActionStack->EndAction(Bracket); if(!bSuccess) { // if the workflow was run as the top level action we'll undo if(ActionStack->CurrentActions.IsEmpty()) { ActionStack->Undo(this); } } return bSuccess; } TArray> URigVMController::GetAffectedReferences(ERigVMControllerBulkEditType InEditType, bool bForceLoad) { TArray> FunctionReferencePtrs; #if WITH_EDITOR check(IsValidGraph()); URigVMGraph* Graph = GetGraph(); URigVMFunctionLibrary* FunctionLibrary = Graph->GetTypedOuter(); if(FunctionLibrary == nullptr) { return FunctionReferencePtrs; } URigVMLibraryNode* Function = FunctionLibrary->FindFunctionForNode(Graph->GetTypedOuter()); if(Function == nullptr) { return FunctionReferencePtrs; } // get the immediate references FunctionReferencePtrs = FunctionLibrary->GetReferencesForFunction(Function->GetFName()); TMap VisitedPaths; for(int32 FunctionReferenceIndex = 0; FunctionReferenceIndex < FunctionReferencePtrs.Num(); FunctionReferenceIndex++) { TSoftObjectPtr FunctionReferencePtr = FunctionReferencePtrs[FunctionReferenceIndex]; VisitedPaths.Add(FunctionReferencePtr.ToSoftObjectPath().ToString(), FunctionReferenceIndex); } for(int32 FunctionReferenceIndex = 0; FunctionReferenceIndex < FunctionReferencePtrs.Num(); FunctionReferenceIndex++) { TSoftObjectPtr FunctionReferencePtr = FunctionReferencePtrs[FunctionReferenceIndex]; if(bForceLoad) { if(OnBulkEditProgressDelegate.IsBound() && !bSuspendNotifications) { OnBulkEditProgressDelegate.Execute(FunctionReferencePtr, InEditType, ERigVMControllerBulkEditProgress::BeginLoad, FunctionReferenceIndex, FunctionReferencePtrs.Num()); } if(!FunctionReferencePtr.IsValid()) { FunctionReferencePtr.LoadSynchronous(); } if(OnBulkEditProgressDelegate.IsBound() && !bSuspendNotifications) { OnBulkEditProgressDelegate.Execute(FunctionReferencePtr, InEditType, ERigVMControllerBulkEditProgress::FinishedLoad, FunctionReferenceIndex, FunctionReferencePtrs.Num()); } } // adding pins / renaming doesn't cause any recursion, so we can stop here if((InEditType == ERigVMControllerBulkEditType::AddExposedPin) || (InEditType == ERigVMControllerBulkEditType::RemoveExposedPin) || (InEditType == ERigVMControllerBulkEditType::RenameExposedPin) || (InEditType == ERigVMControllerBulkEditType::ChangeExposedPinType) || (InEditType == ERigVMControllerBulkEditType::RenameVariable)) { continue; } // for loaded assets we'll recurse now if(FunctionReferencePtr.IsValid()) { if(URigVMFunctionReferenceNode* AffectedFunctionReferenceNode = FunctionReferencePtr.Get()) { if(URigVMLibraryNode* AffectedFunction = AffectedFunctionReferenceNode->FindFunctionForNode()) { FRigVMControllerGraphGuard GraphGuard(this, AffectedFunction->GetContainedGraph(), false); TGuardValue SuspendNotifications(bSuspendNotifications, true); TArray> AffectedFunctionReferencePtrs = GetAffectedReferences(InEditType, bForceLoad); for(TSoftObjectPtr AffectedFunctionReferencePtr : AffectedFunctionReferencePtrs) { const FString Key = AffectedFunctionReferencePtr.ToSoftObjectPath().ToString(); if(VisitedPaths.Contains(Key)) { continue; } VisitedPaths.Add(Key, FunctionReferencePtrs.Add(AffectedFunctionReferencePtr)); } } } } } #endif return FunctionReferencePtrs; } TArray URigVMController::GetAffectedAssets(ERigVMControllerBulkEditType InEditType, bool bForceLoad) { TArray Assets; #if WITH_EDITOR if(!IsValidGraph()) { return Assets; } TArray> FunctionReferencePtrs = GetAffectedReferences(InEditType, bForceLoad); TMap VisitedAssets; URigVMGraph* Graph = GetGraph(); TSoftObjectPtr GraphPtr = Graph; const FString ThisAssetPath = GraphPtr.ToSoftObjectPath().GetAssetPath().ToString(); const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); for(int32 FunctionReferenceIndex = 0; FunctionReferenceIndex < FunctionReferencePtrs.Num(); FunctionReferenceIndex++) { TSoftObjectPtr FunctionReferencePtr = FunctionReferencePtrs[FunctionReferenceIndex]; const FString AssetPath = FunctionReferencePtr.ToSoftObjectPath().GetAssetPath().ToString(); if(AssetPath.StartsWith(TEXT("/Engine/Transient"))) { continue; } if(VisitedAssets.Contains(AssetPath)) { continue; } if(AssetPath == ThisAssetPath) { continue; } const FAssetData AssetData = AssetRegistryModule.Get().GetAssetByObjectPath(FSoftObjectPath(AssetPath)); if(AssetData.IsValid()) { VisitedAssets.Add(AssetPath, Assets.Add(AssetData)); } } #endif return Assets; } void URigVMController::ExpandPinRecursively(URigVMPin* InPin, bool bSetupUndoRedo) { if (InPin == nullptr) { return; } if (bSetupUndoRedo) { OpenUndoBracket(TEXT("Expand Pin Recursively")); } bool bExpandedSomething = false; while (InPin) { if (SetPinExpansion(InPin, true, bSetupUndoRedo)) { bExpandedSomething = true; } InPin = InPin->GetParentPin(); } if (bSetupUndoRedo) { if (bExpandedSomething) { CloseUndoBracket(); } else { CancelUndoBracket(); } } } bool URigVMController::SetVariableName(URigVMVariableNode* InVariableNode, const FName& InVariableName, bool bSetupUndoRedo) { if (!bIsTransacting && !IsGraphEditable()) { return false; } if (!IsValidNodeForGraph(InVariableNode)) { return false; } if (InVariableNode->GetVariableName() == InVariableName) { return false; } if (InVariableName == NAME_None) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); TArray Descriptions = GetAllVariables(); TMap NameToIndex; for (int32 VariableIndex = 0; VariableIndex < Descriptions.Num(); VariableIndex++) { NameToIndex.Add(Descriptions[VariableIndex].Name, VariableIndex); } const FRigVMExternalVariable VariableType = RigVMTypeUtils::ExternalVariableFromCPPType(InVariableName, InVariableNode->GetCPPType(), InVariableNode->GetCPPTypeObject()); FName VariableName = GetUniqueName(InVariableName, [Descriptions, NameToIndex, VariableType](const FName& InName) { const int32* FoundIndex = NameToIndex.Find(InName); if (FoundIndex == nullptr) { return true; } return VariableType.TypeName == Descriptions[*FoundIndex].TypeName && VariableType.TypeObject == Descriptions[*FoundIndex].TypeObject && VariableType.bIsArray == Descriptions[*FoundIndex].bIsArray; }, false, true); int32 NodesSharingName = 0; for (URigVMNode* Node : Graph->Nodes) { if (URigVMVariableNode* OtherVariableNode = Cast(Node)) { if (OtherVariableNode->GetVariableName() == InVariableNode->GetVariableName()) { NodesSharingName++; } } } if (NodesSharingName == 1) { Notify(ERigVMGraphNotifType::VariableRemoved, InVariableNode); } SetPinDefaultValue(InVariableNode->FindPin(URigVMVariableNode::VariableName), VariableName.ToString(), false, bSetupUndoRedo, false); Notify(ERigVMGraphNotifType::VariableAdded, InVariableNode); Notify(ERigVMGraphNotifType::VariableRenamed, InVariableNode); return true; } URigVMRerouteNode* URigVMController::AddFreeRerouteNode(bool bShowAsFullNode, const FString& InCPPType, const FName& InCPPTypeObjectPath, bool bIsConstant, const FName& InCustomWidgetName, const FString& InDefaultValue, const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); if (Graph->IsA()) { ReportError(TEXT("Cannot add reroutes to function library graphs.")); return nullptr; } FRigVMControllerCompileBracketScope CompileScope(this); FRigVMBaseAction Action; if (bSetupUndoRedo) { Action.Title = FString::Printf(TEXT("Add Reroute")); ActionStack->BeginAction(Action); } FString Name = GetValidNodeName(InNodeName.IsEmpty() ? FString(TEXT("RerouteNode")) : InNodeName); URigVMRerouteNode* Node = NewObject(Graph, *Name); Node->Position = InPosition; Node->bShowAsFullNode = bShowAsFullNode; URigVMPin* ValuePin = NewObject(Node, *URigVMRerouteNode::ValueName); ValuePin->CPPType = InCPPType; ValuePin->CPPTypeObjectPath = InCPPTypeObjectPath; ValuePin->bIsConstant = bIsConstant; ValuePin->CustomWidgetName = InCustomWidgetName; ValuePin->Direction = ERigVMPinDirection::IO; AddNodePin(Node, ValuePin); Graph->Nodes.Add(Node); if (ValuePin->IsStruct()) { FString DefaultValue = InDefaultValue; CreateDefaultValueForStructIfRequired(ValuePin->GetScriptStruct(), DefaultValue); { TGuardValue SuspendNotifications(bSuspendNotifications, true); AddPinsForStruct(ValuePin->GetScriptStruct(), Node, ValuePin, ValuePin->Direction, DefaultValue, false); } } else if (!InDefaultValue.IsEmpty() && InDefaultValue != TEXT("()")) { SetPinDefaultValue(ValuePin, InDefaultValue, true, false, false); } if (bSetupUndoRedo) { ActionStack->AddAction(FRigVMAddRerouteNodeAction(Node)); } Notify(ERigVMGraphNotifType::NodeAdded, Node); if (bSetupUndoRedo) { ActionStack->EndAction(Action); } return Node; } URigVMNode* URigVMController::AddBranchNode(const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { return AddUnitNode(FRigVMFunction_ControlFlowBranch::StaticStruct(), FRigVMStruct::ExecuteName, InPosition, InNodeName, bSetupUndoRedo, bPrintPythonCommand); } URigVMNode* URigVMController::AddIfNode(const FString& InCPPType, const FName& InCPPTypeObjectPath, const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if(!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); ensure(!InCPPType.IsEmpty()); UObject* CPPTypeObject = nullptr; if(!InCPPTypeObjectPath.IsNone()) { CPPTypeObject = RigVMTypeUtils::FindObjectFromCPPTypeObjectPath(InCPPTypeObjectPath.ToString()); if (CPPTypeObject == nullptr) { ReportErrorf(TEXT("Cannot find cpp type object for path '%s'."), *InCPPTypeObjectPath.ToString()); return nullptr; } } if(bSetupUndoRedo) { OpenUndoBracket(TEXT("Add If Node")); } const FString CPPType = RigVMTypeUtils::PostProcessCPPType(InCPPType, CPPTypeObject); const FString Name = GetValidNodeName(InNodeName.IsEmpty() ? FString(TEXT("IfNode")) : InNodeName); const TRigVMTypeIndex& TypeIndex = FRigVMRegistry::Get().FindOrAddType({*CPPType, CPPTypeObject}); const FRigVMDispatchFactory* Factory = FRigVMRegistry::Get().FindOrAddDispatchFactory(FRigVMDispatch_If::StaticStruct()); URigVMNode* Node = AddTemplateNode(Factory->GetTemplate()->GetNotation(), InPosition, Name, bSetupUndoRedo, bPrintPythonCommand); if(Node) { ResolveWildCardPin(Node->GetPins().Last(), TypeIndex, bSetupUndoRedo, bPrintPythonCommand); } if(bSetupUndoRedo) { CloseUndoBracket(); } return Node; } URigVMNode* URigVMController::AddIfNodeFromStruct(UScriptStruct* InScriptStruct, const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo) { if (!InScriptStruct) { return nullptr; } return AddIfNode(RigVMTypeUtils::GetUniqueStructTypeName(InScriptStruct), FName(InScriptStruct->GetPathName()), InPosition, InNodeName, bSetupUndoRedo); } URigVMNode* URigVMController::AddSelectNode(const FString& InCPPType, const FName& InCPPTypeObjectPath, const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); ensure(!InCPPType.IsEmpty()); UObject* CPPTypeObject = nullptr; if (!InCPPTypeObjectPath.IsNone()) { CPPTypeObject = RigVMTypeUtils::FindObjectFromCPPTypeObjectPath(InCPPTypeObjectPath.ToString()); if (CPPTypeObject == nullptr) { ReportErrorf(TEXT("Cannot find cpp type object for path '%s'."), *InCPPTypeObjectPath.ToString()); return nullptr; } } if(bSetupUndoRedo) { OpenUndoBracket(TEXT("Add Select Node")); } const FString CPPType = RigVMTypeUtils::PostProcessCPPType(InCPPType, CPPTypeObject); const FString Name = GetValidNodeName(InNodeName.IsEmpty() ? FString(TEXT("SelectNode")) : InNodeName); const TRigVMTypeIndex& TypeIndex = FRigVMRegistry::Get().FindOrAddType({*CPPType, CPPTypeObject}); const FRigVMDispatchFactory* Factory = FRigVMRegistry::Get().FindOrAddDispatchFactory(FRigVMDispatch_SelectInt32::StaticStruct()); URigVMNode* Node = AddTemplateNode(Factory->GetTemplate()->GetNotation(), InPosition, Name, bSetupUndoRedo, bPrintPythonCommand); if(Node) { ResolveWildCardPin(Node->GetPins().Last(), TypeIndex, bSetupUndoRedo, bPrintPythonCommand); } if(bSetupUndoRedo) { CloseUndoBracket(); } return Node; } URigVMNode* URigVMController::AddSelectNodeFromStruct(UScriptStruct* InScriptStruct, const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo) { if (!InScriptStruct) { return nullptr; } return AddSelectNode(RigVMTypeUtils::GetUniqueStructTypeName(InScriptStruct), FName(InScriptStruct->GetPathName()), InPosition, InNodeName, bSetupUndoRedo); } URigVMTemplateNode* URigVMController::AddTemplateNode(const FName& InNotation, const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); ensure(!InNotation.IsNone()); const FRigVMTemplate* Template = FRigVMRegistry::Get().FindTemplate(InNotation); if (Template == nullptr) { ReportErrorf(TEXT("Template '%s' cannot be found."), *InNotation.ToString()); return nullptr; } if(IRigVMClientHost* ClientHost = GetImplementingOuter()) { if(const FRigVMClient* Client = ClientHost->GetRigVMClient()) { if(!Template->SupportsExecuteContextStruct(Client->GetExecuteContextStruct())) { ReportErrorf(TEXT("Cannot add node for template '%s' - incompatible execute context: '%s' vs '%s'."), *Template->GetNotation().ToString(), *Template->GetExecuteContextStruct()->GetStructCPPName(), *Client->GetExecuteContextStruct()->GetStructCPPName()); return nullptr; } } } FString Name = GetValidNodeName(InNodeName.IsEmpty() ? Template->GetName().ToString() : InNodeName); URigVMTemplateNode* Node = nullptr; // determine what kind of node we need to create if(Template->UsesDispatch()) { Node = NewObject(Graph, *Name); } else if(const FRigVMFunction* FirstFunction = Template->GetPermutation(0)) { const UScriptStruct* PotentialUnitStruct = FirstFunction->Struct; if(PotentialUnitStruct && PotentialUnitStruct->IsChildOf(FRigVMStruct::StaticStruct())) { Node = NewObject(Graph, *Name); } } if(Node == nullptr) { const FString TemplateName = Template->GetName().ToString(); if(TemplateName == URigVMRerouteNode::RerouteName) { Node = NewObject(Graph, *Name); } } if(Node == nullptr) { ReportErrorf(TEXT("Template node '%s' cannot be created. Unknown template."), *InNotation.ToString()); return nullptr; } Node->TemplateNotation = Template->GetNotation(); Node->Position = InPosition; int32 PermutationIndex = INDEX_NONE; FRigVMTemplate::FTypeMap Types; Template->FullyResolve(Types, PermutationIndex); FRigVMRegistry& Registry = FRigVMRegistry::Get(); AddPinsForTemplate(Template, Types, Node); if (Node->HasWildCardPin()) { UpdateTemplateNodePinTypes(Node, false); } else { if (!Node->IsA() && !Node->IsA()) { FullyResolveTemplateNode(Node, INDEX_NONE, false); } } Graph->Nodes.Add(Node); Notify(ERigVMGraphNotifType::NodeAdded, Node); FRigVMAddTemplateNodeAction Action; FRigVMControllerCompileBracketScope CompileScope(this); if (bSetupUndoRedo) { Action = FRigVMAddTemplateNodeAction(Node); ActionStack->BeginAction(Action); } ResolveTemplateNodeMetaData(Node, bSetupUndoRedo); if (bSetupUndoRedo) { ActionStack->EndAction(Action); } if (bPrintPythonCommand) { TArray Commands = GetAddNodePythonCommands(Node); for (const FString& Command : Commands) { RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("%s"), *Command)); } } return Node; } TArray URigVMController::GetRegisteredUnitStructs() { TArray UnitStructs; for(const FRigVMFunction& Function : FRigVMRegistry::Get().GetFunctions()) { if(!Function.IsValid()) { continue; } if(UScriptStruct* Struct = Function.Struct) { if (!Struct->IsChildOf(FRigVMStruct::StaticStruct())) { continue; } UnitStructs.Add(Struct); } } return UnitStructs; } TArray URigVMController::GetRegisteredTemplates() { TArray Templates; for(const FRigVMTemplate& Template : FRigVMRegistry::Get().GetTemplates()) { if(!Template.IsValid() || Template.NumPermutations() < 2) { continue; } Templates.Add(Template.GetNotation().ToString()); } return Templates; } TArray URigVMController::GetUnitStructsForTemplate(const FName& InNotation) { TArray UnitStructs; const FRigVMTemplate* Template = FRigVMRegistry::Get().FindTemplate(InNotation); if(Template) { if(!Template->UsesDispatch()) { for(int32 PermutationIndex = 0; PermutationIndex < Template->NumPermutations(); PermutationIndex++) { UnitStructs.Add(Template->GetPermutation(PermutationIndex)->Struct); } } } return UnitStructs; } FString URigVMController::GetTemplateForUnitStruct(UScriptStruct* InFunction, const FString& InMethodName) { if(const FRigVMFunction* Function = FRigVMRegistry::Get().FindFunction(InFunction, *InMethodName)) { if(const FRigVMTemplate* Template = Function->GetTemplate()) { return Template->GetNotation().ToString(); } } return FString(); } URigVMEnumNode* URigVMController::AddEnumNode(const FName& InCPPTypeObjectPath, const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); UObject* CPPTypeObject = RigVMTypeUtils::FindObjectFromCPPTypeObjectPath(InCPPTypeObjectPath.ToString()); if (CPPTypeObject == nullptr) { ReportErrorf(TEXT("Cannot find cpp type object for path '%s'."), *InCPPTypeObjectPath.ToString()); return nullptr; } UEnum* Enum = Cast(CPPTypeObject); if(Enum == nullptr) { ReportErrorf(TEXT("Cpp type object for path '%s' is not an enum."), *InCPPTypeObjectPath.ToString()); return nullptr; } FString Name = GetValidNodeName(InNodeName.IsEmpty() ? FString(TEXT("IfNode")) : InNodeName); URigVMEnumNode* Node = NewObject(Graph, *Name); Node->Position = InPosition; URigVMPin* EnumValuePin = NewObject(Node, *URigVMEnumNode::EnumValueName); EnumValuePin->CPPType = CPPTypeObject->GetName(); EnumValuePin->CPPTypeObject = CPPTypeObject; EnumValuePin->CPPTypeObjectPath = InCPPTypeObjectPath; EnumValuePin->Direction = ERigVMPinDirection::Visible; EnumValuePin->DefaultValue = Enum->GetNameStringByValue(0); AddNodePin(Node, EnumValuePin); URigVMPin* EnumIndexPin = NewObject(Node, *URigVMEnumNode::EnumIndexName); EnumIndexPin->CPPType = RigVMTypeUtils::Int32Type; EnumIndexPin->Direction = ERigVMPinDirection::Output; EnumIndexPin->DisplayName = TEXT("Result"); AddNodePin(Node, EnumIndexPin); Graph->Nodes.Add(Node); Notify(ERigVMGraphNotifType::NodeAdded, Node); FRigVMControllerCompileBracketScope CompileScope(this); if (bSetupUndoRedo) { ActionStack->AddAction(FRigVMAddEnumNodeAction(Node)); } if (bPrintPythonCommand) { TArray Commands = GetAddNodePythonCommands(Node); for (const FString& Command : Commands) { RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("%s"), *Command)); } } return Node; } URigVMNode* URigVMController::AddArrayNode(ERigVMOpCode InOpCode, const FString& InCPPType, UObject* InCPPTypeObject, const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand, bool bIsPatching) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } const FString CPPType = RigVMTypeUtils::IsArrayType(InCPPType) ? RigVMTypeUtils::BaseTypeFromArrayType(InCPPType) : InCPPType; const TRigVMTypeIndex& ElementTypeIndex = FRigVMRegistry::Get().FindOrAddType({*CPPType, InCPPTypeObject}); if(ElementTypeIndex == INDEX_NONE) { return nullptr; } const TRigVMTypeIndex& ArrayTypeIndex = FRigVMRegistry::Get().GetArrayTypeFromBaseTypeIndex(ElementTypeIndex); const FName FactoryName = FRigVMDispatch_ArrayBase::GetFactoryNameForOpCode(InOpCode); if(FactoryName.IsNone()) { ReportErrorf(TEXT("OpCode '%s' is not valid for Array Node."), *StaticEnum()->GetNameStringByValue((int64)InOpCode)); return nullptr; } const FRigVMDispatchFactory* Factory = FRigVMRegistry::Get().FindDispatchFactory(FactoryName); if(Factory == nullptr) { ReportErrorf(TEXT("Cannot find array dispatch '%s'."), *FactoryName.ToString()); return nullptr; } if(bSetupUndoRedo) { OpenUndoBracket(TEXT("Add Array Node")); } const FRigVMTemplate* Template = Factory->GetTemplate(); URigVMTemplateNode* Node = AddTemplateNode( Template->GetNotation(), InPosition, InNodeName, bSetupUndoRedo, bPrintPythonCommand); if(!FRigVMRegistry::Get().IsWildCardType(ElementTypeIndex)) { FName ArgumentNameToResolve = NAME_None; TRigVMTypeIndex TypeIndex = INDEX_NONE; for(int32 Index = 0; Index < Template->NumArguments(); Index++) { const FRigVMTemplateArgument* Argument = Template->GetArgument(Index); if(Argument->IsSingleton()) { continue; } if(Argument->GetArrayType() == FRigVMTemplateArgument::EArrayType_SingleValue) { ArgumentNameToResolve = Argument->GetName(); TypeIndex = ElementTypeIndex; break; } if(Argument->GetArrayType() == FRigVMTemplateArgument::EArrayType_ArrayValue) { ArgumentNameToResolve = Argument->GetName(); TypeIndex = ArrayTypeIndex; break; } } if(!ArgumentNameToResolve.IsNone() && TypeIndex != INDEX_NONE) { if(bIsPatching) { FRigVMTemplate::FTypeMap TypeMap; TypeMap.Add(ArgumentNameToResolve, TypeIndex); TArray Permutations; Template->Resolve(TypeMap, Permutations, false); check(Permutations.Num() == 1); for(const FRigVMTemplate::FTypePair& Pair : TypeMap) { if(!FRigVMRegistry::Get().IsWildCardType(Pair.Value)) { if(URigVMPin* Pin = Node->FindPin(Pair.Key.ToString())) { ChangePinType(Pin, Pair.Value, false, false); } } } FullyResolveTemplateNode(Node, Permutations[0], false); } else if(const URigVMPin* Pin = Node->FindPin(ArgumentNameToResolve.ToString())) { ResolveWildCardPin(Pin->GetPinPath(), TypeIndex, bSetupUndoRedo, bPrintPythonCommand); } } } if(bSetupUndoRedo) { CloseUndoBracket(); } return Node; } URigVMNode* URigVMController::AddArrayNodeFromObjectPath(ERigVMOpCode InOpCode, const FString& InCPPType, const FString& InCPPTypeObjectPath, const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand, bool bIsPatching) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } UObject* CPPTypeObject = nullptr; if (!InCPPTypeObjectPath.IsEmpty()) { CPPTypeObject = RigVMTypeUtils::FindObjectFromCPPTypeObjectPath(InCPPTypeObjectPath); if (CPPTypeObject == nullptr) { ReportErrorf(TEXT("Cannot find cpp type object for path '%s'."), *InCPPTypeObjectPath); return nullptr; } } return AddArrayNode(InOpCode, InCPPType, CPPTypeObject, InPosition, InNodeName, bSetupUndoRedo, bPrintPythonCommand, bIsPatching); } URigVMInvokeEntryNode* URigVMController::AddInvokeEntryNode(const FName& InEntryName, const FVector2D& InPosition, const FString& InNodeName, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return nullptr; } if (!bIsTransacting && !IsGraphEditable()) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); if (Graph->IsA()) { ReportError(TEXT("Cannot add invoke entry nodes to function library graphs.")); return nullptr; } FString Name = GetValidNodeName(InNodeName.IsEmpty() ? FString(TEXT("InvokeEntryNode")) : InNodeName); URigVMInvokeEntryNode* Node = NewObject(Graph, *Name); Node->Position = InPosition; URigVMPin* ExecutePin = MakeExecutePin(Node, FRigVMStruct::ExecuteContextName); ExecutePin->Direction = ERigVMPinDirection::IO; AddNodePin(Node, ExecutePin); URigVMPin* EntryNamePin = NewObject(Node, *URigVMInvokeEntryNode::EntryName); EntryNamePin->CPPType = RigVMTypeUtils::FNameType; EntryNamePin->Direction = ERigVMPinDirection::Input; EntryNamePin->bIsConstant = true; EntryNamePin->DefaultValue = InEntryName.ToString(); EntryNamePin->CustomWidgetName = TEXT("EntryName"); AddNodePin(Node, EntryNamePin); Graph->Nodes.Add(Node); if (!bSuspendNotifications) { Graph->MarkPackageDirty(); } FRigVMControllerCompileBracketScope CompileScope(this); Notify(ERigVMGraphNotifType::NodeAdded, Node); Notify(ERigVMGraphNotifType::VariableAdded, Node); if (bSetupUndoRedo) { FRigVMAddInvokeEntryNodeAction Action(Node); Action.Title = FString::Printf(TEXT("Add Invoke %s Entry"), *InEntryName.ToString()); ActionStack->AddAction(Action); } if (bPrintPythonCommand) { TArray Commands = GetAddNodePythonCommands(Node); for (const FString& Command : Commands) { RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("%s"), *Command)); } } return Node; } void URigVMController::ForEveryPinRecursively(URigVMPin* InPin, TFunction OnEachPinFunction) { OnEachPinFunction(InPin); for (URigVMPin* SubPin : InPin->SubPins) { ForEveryPinRecursively(SubPin, OnEachPinFunction); } } void URigVMController::ForEveryPinRecursively(URigVMNode* InNode, TFunction OnEachPinFunction) { for (URigVMPin* Pin : InNode->GetPins()) { ForEveryPinRecursively(Pin, OnEachPinFunction); } } FString URigVMController::GetValidNodeName(const FString& InPrefix) { URigVMGraph* Graph = GetGraph(); check(Graph); return GetUniqueName(*InPrefix, [&](const FName& InName) { return Graph->IsNameAvailable(InName.ToString()); }, false, true).ToString(); } bool URigVMController::IsValidGraph() const { URigVMGraph* Graph = GetGraph(); if (Graph == nullptr) { ReportError(TEXT("Controller does not have a graph associated - use SetGraph / set_graph.")); return false; } if (!IsValid(Graph)) { return false; } return true; } bool URigVMController::IsGraphEditable() const { if(!IsValidGraph()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); return Graph->bEditable; } bool URigVMController::IsValidNodeForGraph(URigVMNode* InNode) { if(!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } if (InNode == nullptr) { ReportError(TEXT("InNode is nullptr.")); return false; } if (InNode->GetGraph() != GetGraph()) { ReportWarningf(TEXT("InNode '%s' is on a different graph. InNode graph is %s, this graph is %s"), *InNode->GetNodePath(), *GetNameSafe(InNode->GetGraph()), *GetNameSafe(GetGraph())); return false; } if (InNode->GetNodeIndex() == INDEX_NONE) { ReportErrorf(TEXT("InNode '%s' is transient (not yet nested to a graph)."), *InNode->GetName()); } return true; } bool URigVMController::IsValidPinForGraph(URigVMPin* InPin) { if(!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } if (InPin == nullptr) { ReportError(TEXT("InPin is nullptr.")); return false; } if (!IsValidNodeForGraph(InPin->GetNode())) { return false; } if (InPin->GetPinIndex() == INDEX_NONE) { ReportErrorf(TEXT("InPin '%s' is transient (not yet nested properly)."), *InPin->GetName()); } return true; } bool URigVMController::IsValidLinkForGraph(URigVMLink* InLink) { if(!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } if (InLink == nullptr) { ReportError(TEXT("InLink is nullptr.")); return false; } if (InLink->GetGraph() != GetGraph()) { ReportError(TEXT("InLink is on a different graph.")); return false; } if(InLink->GetSourcePin() == nullptr) { ReportError(TEXT("InLink has no source pin.")); return false; } if(InLink->GetTargetPin() == nullptr) { ReportError(TEXT("InLink has no target pin.")); return false; } if (InLink->GetLinkIndex() == INDEX_NONE) { ReportError(TEXT("InLink is transient (not yet nested properly).")); } if(!IsValidPinForGraph(InLink->GetSourcePin())) { return false; } if(!IsValidPinForGraph(InLink->GetTargetPin())) { return false; } return true; } bool URigVMController::CanAddNode(URigVMNode* InNode, bool bReportErrors, bool bIgnoreFunctionEntryReturnNodes) { if(!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } check(InNode); URigVMGraph* Graph = GetGraph(); check(Graph); if (Graph->IsA()) { if (!InNode->IsA()) { return false; } } if (URigVMFunctionReferenceNode* FunctionRefNode = Cast(InNode)) { if (URigVMFunctionLibrary* FunctionLibrary = FunctionRefNode->GetLibrary()) { FRigVMGraphFunctionHeader FunctionDefinition = FunctionRefNode->GetReferencedFunctionHeader(); if(!CanAddFunctionRefForDefinition(FunctionDefinition, false)) { URigVMFunctionLibrary* TargetLibrary = Graph->GetDefaultFunctionLibrary(); URigVMLibraryNode* NewFunctionDefinition = TargetLibrary->FindPreviouslyLocalizedFunction(FunctionDefinition.LibraryPointer); if((NewFunctionDefinition == nullptr) && RequestLocalizeFunctionDelegate.IsBound()) { if(RequestLocalizeFunctionDelegate.Execute(FunctionDefinition.LibraryPointer)) { NewFunctionDefinition = TargetLibrary->FindPreviouslyLocalizedFunction(FunctionDefinition.LibraryPointer); } } if(NewFunctionDefinition == nullptr) { return false; } SetReferencedFunction(FunctionRefNode, NewFunctionDefinition, false); FunctionDefinition = FunctionRefNode->GetReferencedFunctionHeader(); } if(!CanAddFunctionRefForDefinition(FunctionDefinition, bReportErrors)) { DestroyObject(InNode); return false; } } } else if(!bIgnoreFunctionEntryReturnNodes && (InNode->IsA() || InNode->IsA())) { // only allow entry / return nodes on sub graphs if(Graph->IsRootGraph()) { return false; } // only allow one function entry node if(InNode->IsA()) { if(Graph->GetEntryNode() != nullptr) { return false; } } // only allow one function return node else if(InNode->IsA()) { if(Graph->GetReturnNode() != nullptr) { return false; } } } else if(URigVMCollapseNode* CollapseNode = Cast(InNode)) { FRigVMControllerGraphGuard GraphGuard(this, CollapseNode->GetContainedGraph(), false); TArray ContainedNodes = CollapseNode->GetContainedNodes(); for(URigVMNode* ContainedNode : ContainedNodes) { if(!CanAddNode(ContainedNode, bReportErrors, true)) { return false; } } } else if(URigVMVariableNode* VariableNode = Cast(InNode)) { if (URigVMPin* NamePin = VariableNode->FindPin(URigVMVariableNode::VariableName)) { FString VarName = NamePin->GetDefaultValue(); if (!VarName.IsEmpty()) { TArray AllVariables = URigVMController::GetAllVariables(true); for(const FRigVMExternalVariable& Variable : AllVariables) { if(Variable.Name.ToString() == VarName) { return true; } } return false; } } } else if (InNode->IsEvent()) { if (URigVMUnitNode* InUnitNode = Cast(InNode)) { if (!CanAddEventNode(InUnitNode->GetScriptStruct(), bReportErrors)) { return false; } } } return true; } TObjectPtr URigVMController::FindEventNode(const UScriptStruct* InScriptStruct) const { check(InScriptStruct); // construct equivalent default struct FStructOnScope InDefaultStructScope(InScriptStruct); if (URigVMGraph* Graph = GetGraph()) { TObjectPtr* FoundNode = Graph->Nodes.FindByPredicate( [&InDefaultStructScope](const TObjectPtr& Node) { if (Node->IsEvent()) { if (URigVMUnitNode* UnitNode = Cast(Node)) { // compare default structures TSharedPtr DefaultStructScope = UnitNode->ConstructStructInstance(true); if (DefaultStructScope.IsValid() && InDefaultStructScope.GetStruct() == DefaultStructScope->GetStruct()) { return true; } } } return false; }); if (FoundNode) { return *FoundNode; } } return TObjectPtr(); } bool URigVMController::CanAddEventNode(UScriptStruct* InScriptStruct, const bool bReportErrors) const { if(!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } check(InScriptStruct); URigVMGraph* Graph = GetGraph(); check(Graph); // check if we're trying to add a node within a graph which is not the top level one if (!Graph->IsTopLevelGraph()) { if (bReportErrors) { ReportAndNotifyError(TEXT("Event nodes can only be added to top level graphs.")); } return false; } TObjectPtr EventNode = FindEventNode(InScriptStruct); const bool bHasEventNode = (EventNode != nullptr) && EventNode->CanOnlyExistOnce(); if (bHasEventNode && bReportErrors) { const FString ErrorMessage = FString::Printf(TEXT("Rig Graph can only contain one single %s node."), *InScriptStruct->GetDisplayNameText().ToString()); ReportAndNotifyError(ErrorMessage); } return !bHasEventNode; } bool URigVMController::CanAddFunctionRefForDefinition(const FRigVMGraphFunctionHeader& InFunctionDefinition, bool bReportErrors, bool bAllowPrivateFunctions) { if(!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); if (!bAllowPrivateFunctions) { if(IRigVMClientHost* ClientHost = GetImplementingOuter()) { bool bIsAvailable = ClientHost->GetRigVMClient()->GetFunctionLibrary()->GetFunctionHostObjectPath() == InFunctionDefinition.LibraryPointer.HostObject; if (!bIsAvailable) { if (IRigVMGraphFunctionHost* Host = Cast(InFunctionDefinition.LibraryPointer.HostObject.TryLoad())) { bIsAvailable = Host->GetRigVMGraphFunctionStore()->IsFunctionPublic(InFunctionDefinition.LibraryPointer); } } if (!bIsAvailable) { if(bReportErrors) { ReportAndNotifyError(TEXT("Function is not available for placement in another graph host.")); } return false; } } } if (URigVMNode* Node = Cast(Graph->GetOuter())) { if (URigVMLibraryNode* LibraryNode = Node->FindFunctionForNode()) { if (InFunctionDefinition.Dependencies.Contains(LibraryNode->GetFunctionIdentifier())) { if(bReportErrors) { ReportAndNotifyError(TEXT("Function is not available for placement in this graph host due to dependency cycles.")); } return false; } } } URigVMLibraryNode* ParentLibraryNode = Cast(Graph->GetOuter()); while (ParentLibraryNode) { if (TSoftObjectPtr(ParentLibraryNode).ToSoftObjectPath() == InFunctionDefinition.LibraryPointer.LibraryNode) { if(bReportErrors) { ReportAndNotifyError(TEXT("You cannot place functions inside of itself or an indirect recursion.")); } return false; } ParentLibraryNode = Cast(ParentLibraryNode->GetGraph()->GetOuter()); } return true; } void URigVMController::AddPinsForStruct(UStruct* InStruct, URigVMNode* InNode, URigVMPin* InParentPin, ERigVMPinDirection InPinDirection, const FString& InDefaultValue, bool bAutoExpandArrays, const FPinInfoArray* PreviousPins) { if(!ShouldStructBeUnfolded(InStruct)) { return; } // todo: reuse the default values when creating the pins TArray MemberNameValuePairs = URigVMPin::SplitDefaultValue(InDefaultValue); TMap MemberValues; for (const FString& MemberNameValuePair : MemberNameValuePairs) { FString MemberName, MemberValue; if (MemberNameValuePair.Split(TEXT("="), &MemberName, &MemberValue)) { MemberValues.Add(*MemberName, MemberValue); } } TArray StructsToVisit = FRigVMTemplate::GetSuperStructs(InStruct, true); for(UStruct* StructToVisit : StructsToVisit) { // using EFieldIterationFlags::None excludes the // properties of the super struct in this iterator. for (TFieldIterator It(StructToVisit, EFieldIterationFlags::None); It; ++It) { FName PropertyName = It->GetFName(); URigVMPin* Pin = NewObject(InParentPin == nullptr ? Cast(InNode) : Cast(InParentPin), PropertyName); ConfigurePinFromProperty(*It, Pin, InPinDirection); if (InParentPin) { AddSubPin(InParentPin, Pin); } else { AddNodePin(InNode, Pin); } FString* DefaultValuePtr = MemberValues.Find(Pin->GetFName()); FStructProperty* StructProperty = CastField(*It); if (StructProperty) { if (ShouldStructBeUnfolded(StructProperty->Struct)) { FString DefaultValue; if (DefaultValuePtr != nullptr) { DefaultValue = *DefaultValuePtr; } CreateDefaultValueForStructIfRequired(StructProperty->Struct, DefaultValue); { TGuardValue SuspendNotifications(bSuspendNotifications, true); AddPinsForStruct(StructProperty->Struct, InNode, Pin, Pin->GetDirection(), DefaultValue, bAutoExpandArrays); } } else if(DefaultValuePtr != nullptr) { Pin->DefaultValue = *DefaultValuePtr; } } FArrayProperty* ArrayProperty = CastField(*It); if (ArrayProperty) { ensure(Pin->IsArray()); if (DefaultValuePtr) { if (ShouldPinBeUnfolded(Pin)) { TArray ElementDefaultValues = URigVMPin::SplitDefaultValue(*DefaultValuePtr); AddPinsForArray(ArrayProperty, InNode, Pin, Pin->Direction, ElementDefaultValues, bAutoExpandArrays); } else { FString DefaultValue = *DefaultValuePtr; PostProcessDefaultValue(Pin, DefaultValue); Pin->DefaultValue = *DefaultValuePtr; } } } if (!Pin->IsArray() && !Pin->IsStruct() && DefaultValuePtr != nullptr) { FString DefaultValue = *DefaultValuePtr; PostProcessDefaultValue(Pin, DefaultValue); Pin->DefaultValue = DefaultValue; } if (!bSuspendNotifications) { Notify(ERigVMGraphNotifType::PinAdded, Pin); } } } } void URigVMController::AddPinsForArray(FArrayProperty* InArrayProperty, URigVMNode* InNode, URigVMPin* InParentPin, ERigVMPinDirection InPinDirection, const TArray& InDefaultValues, bool bAutoExpandArrays) { check(InParentPin); if (!ShouldPinBeUnfolded(InParentPin)) { return; } for (int32 ElementIndex = 0; ElementIndex < InDefaultValues.Num(); ElementIndex++) { FString ElementName = FString::FormatAsNumber(InParentPin->SubPins.Num()); URigVMPin* Pin = NewObject(InParentPin, *ElementName); ConfigurePinFromProperty(InArrayProperty->Inner, Pin, InPinDirection); FString DefaultValue = InDefaultValues[ElementIndex]; AddSubPin(InParentPin, Pin); if (bAutoExpandArrays) { TGuardValue ErrorGuard(bReportWarningsAndErrors, false); ExpandPinRecursively(Pin, false); } FStructProperty* StructProperty = CastField(InArrayProperty->Inner); if (StructProperty) { if (ShouldPinBeUnfolded(Pin)) { // DefaultValue before this point only contains parent struct overrides, // see comments in CreateDefaultValueForStructIfRequired UScriptStruct* ScriptStruct = Pin->GetScriptStruct(); if (ScriptStruct) { CreateDefaultValueForStructIfRequired(ScriptStruct, DefaultValue); } { TGuardValue SuspendNotifications(bSuspendNotifications, true); AddPinsForStruct(StructProperty->Struct, InNode, Pin, Pin->Direction, DefaultValue, bAutoExpandArrays); } } else if (!DefaultValue.IsEmpty()) { PostProcessDefaultValue(Pin, DefaultValue); Pin->DefaultValue = DefaultValue; } } FArrayProperty* ArrayProperty = CastField(InArrayProperty->Inner); if (ArrayProperty) { if (ShouldPinBeUnfolded(Pin)) { TArray ElementDefaultValues = URigVMPin::SplitDefaultValue(DefaultValue); AddPinsForArray(ArrayProperty, InNode, Pin, Pin->Direction, ElementDefaultValues, bAutoExpandArrays); } else if (!DefaultValue.IsEmpty()) { PostProcessDefaultValue(Pin, DefaultValue); Pin->DefaultValue = DefaultValue; } } if (!Pin->IsArray() && !Pin->IsStruct()) { PostProcessDefaultValue(Pin, DefaultValue); Pin->DefaultValue = DefaultValue; } } } void URigVMController::AddPinsForTemplate(const FRigVMTemplate* InTemplate, const FRigVMTemplateTypeMap& InPinTypeMap, URigVMNode* InNode) { const FRigVMRegistry& Registry = FRigVMRegistry::Get(); FRigVMDispatchContext DispatchContext; if(const URigVMDispatchNode* DispatchNode = Cast(InNode)) { DispatchContext = DispatchNode->GetDispatchContext(); } auto AddExecutePins = [InTemplate, InNode, &Registry, &DispatchContext, this](ERigVMPinDirection InPinDirection) { for (int32 ArgIndex = 0; ArgIndex < InTemplate->NumExecuteArguments(DispatchContext); ArgIndex++) { const FRigVMExecuteArgument* Arg = InTemplate->GetExecuteArgument(ArgIndex, DispatchContext); if(Arg->Direction != InPinDirection) { continue; } URigVMPin* Pin = NewObject(InNode, Arg->Name); const FRigVMTemplateArgumentType Type = Registry.GetType(Arg->TypeIndex); Pin->CPPType = Type.CPPType.ToString(); Pin->CPPTypeObject = Type.CPPTypeObject; if (Pin->CPPTypeObject) { Pin->CPPTypeObjectPath = *Pin->CPPTypeObject->GetPathName(); } Pin->Direction = Arg->Direction; Pin->LastKnownTypeIndex = Arg->TypeIndex; Pin->LastKnownCPPType = Pin->CPPType; AddNodePin(InNode, Pin); if(Registry.IsArrayType(Arg->TypeIndex)) { if(const URigVMDispatchNode* DispatchNode = Cast(Pin->GetNode())) { if(const FRigVMDispatchFactory* Factory = DispatchNode->GetFactory()) { const FString DefaultValue = Factory->GetArgumentDefaultValue(Pin->GetFName(), Arg->TypeIndex); if(!DefaultValue.IsEmpty()) { SetPinDefaultValue(Pin, DefaultValue, true, false, false); } } } } } }; AddExecutePins(ERigVMPinDirection::IO); AddExecutePins(ERigVMPinDirection::Input); for (int32 ArgIndex = 0; ArgIndex < InTemplate->NumArguments(); ArgIndex++) { const FRigVMTemplateArgument* Arg = InTemplate->GetArgument(ArgIndex); URigVMPin* Pin = NewObject(InNode, Arg->GetName()); const TRigVMTypeIndex& TypeIndex = InPinTypeMap.FindChecked(Arg->GetName()); const FRigVMTemplateArgumentType Type = FRigVMRegistry::Get().GetType(TypeIndex); Pin->CPPType = Type.CPPType.ToString(); Pin->CPPTypeObject = Type.CPPTypeObject; if (Pin->CPPTypeObject) { Pin->CPPTypeObjectPath = *Pin->CPPTypeObject->GetPathName(); } Pin->Direction = Arg->GetDirection(); AddNodePin(InNode, Pin); if(!Pin->IsWildCard() && !Pin->IsArray()) { FString DefaultValue; if(const URigVMTemplateNode* TemplateNode = Cast(InNode)) { DefaultValue = TemplateNode->GetInitialDefaultValueForPin(Pin->GetFName()); } TGuardValue SuspendNotifications(bSuspendNotifications, true); if(UScriptStruct* ScriptStruct = Cast(Pin->CPPTypeObject)) { AddPinsForStruct(ScriptStruct, Pin->GetNode(), Pin, Pin->Direction, DefaultValue, false); } else if(!DefaultValue.IsEmpty()) { SetPinDefaultValue(Pin, DefaultValue, true, false, false); } } else if(Pin->IsFixedSizeArray()) { if(const URigVMDispatchNode* DispatchNode = Cast(Pin->GetNode())) { if(const FRigVMDispatchFactory* Factory = DispatchNode->GetFactory()) { const FString DefaultValue = Factory->GetArgumentDefaultValue(Pin->GetFName(), RigVMTypeUtils::TypeIndex::WildCardArray); if(!DefaultValue.IsEmpty()) { SetPinDefaultValue(Pin, DefaultValue, true, false, false); } } } } } AddExecutePins(ERigVMPinDirection::Output); } void URigVMController::ConfigurePinFromProperty(FProperty* InProperty, URigVMPin* InOutPin, ERigVMPinDirection InPinDirection) { if (InPinDirection == ERigVMPinDirection::Invalid) { InOutPin->Direction = FRigVMStruct::GetPinDirectionFromProperty(InProperty); } else { InOutPin->Direction = InPinDirection; } #if WITH_EDITOR if (!InOutPin->IsArrayElement()) { FString DisplayNameText = InProperty->GetDisplayNameText().ToString(); if (!DisplayNameText.IsEmpty()) { InOutPin->DisplayName = *DisplayNameText; } else { InOutPin->DisplayName = NAME_None; } } InOutPin->bIsConstant = InProperty->HasMetaData(TEXT("Constant")); FString CustomWidgetName = InProperty->GetMetaData(TEXT("CustomWidget")); InOutPin->CustomWidgetName = CustomWidgetName.IsEmpty() ? FName(NAME_None) : FName(*CustomWidgetName); if (InProperty->HasMetaData(FRigVMStruct::ExpandPinByDefaultMetaName)) { InOutPin->bIsExpanded = true; } #endif FString ExtendedCppType; InOutPin->CPPType = InProperty->GetCPPType(&ExtendedCppType); InOutPin->CPPType += ExtendedCppType; InOutPin->bIsDynamicArray = false; #if WITH_EDITOR if (InOutPin->Direction == ERigVMPinDirection::Hidden) { if (!InProperty->HasMetaData(TEXT("ArraySize"))) { InOutPin->bIsDynamicArray = true; } } if (InOutPin->bIsDynamicArray) { if (InProperty->HasMetaData(FRigVMStruct::SingletonMetaName)) { InOutPin->bIsDynamicArray = false; } } #endif FProperty* PropertyForType = InProperty; FArrayProperty* ArrayProperty = CastField(PropertyForType); if (ArrayProperty) { PropertyForType = ArrayProperty->Inner; } if (FStructProperty* StructProperty = CastField(PropertyForType)) { InOutPin->CPPTypeObject = StructProperty->Struct; } else if (FObjectProperty* ObjectProperty = CastField(PropertyForType)) { if(RigVMCore::SupportsUObjects()) { InOutPin->CPPTypeObject = ObjectProperty->PropertyClass; } else { ReportErrorf(TEXT("Unsupported type '%s' for pin."), *ObjectProperty->PropertyClass->GetName(), *InOutPin->GetName()); InOutPin->CPPType = FString(); InOutPin->CPPTypeObject = nullptr; } } else if (FInterfaceProperty* InterfaceProperty = CastField(PropertyForType)) { if (RigVMCore::SupportsUInterfaces()) { InOutPin->CPPTypeObject = InterfaceProperty->InterfaceClass; } else { ReportErrorf(TEXT("Unsupported type '%s' for pin."), *InterfaceProperty->InterfaceClass->GetName(), *InOutPin->GetName()); InOutPin->CPPType = FString(); InOutPin->CPPTypeObject = nullptr; } } else if (FEnumProperty* EnumProperty = CastField(PropertyForType)) { InOutPin->CPPTypeObject = EnumProperty->GetEnum(); } else if (FByteProperty* ByteProperty = CastField(PropertyForType)) { InOutPin->CPPTypeObject = ByteProperty->Enum; } if (InOutPin->CPPTypeObject) { InOutPin->CPPTypeObjectPath = *InOutPin->CPPTypeObject->GetPathName(); } InOutPin->CPPType = RigVMTypeUtils::PostProcessCPPType(InOutPin->CPPType, InOutPin->GetCPPTypeObject()); if(InOutPin->IsExecuteContext() && InOutPin->CPPTypeObject != FRigVMExecuteContext::StaticStruct()) { MakeExecutePin(InOutPin); } } void URigVMController::ConfigurePinFromPin(URigVMPin* InOutPin, URigVMPin* InPin, bool bCopyDisplayName) { // it is important we copy things that define the identity of the pin // things that defines the state of the pin is copied during GetPinState() // though addmittedly these two functions have overlaps currently InOutPin->bIsConstant = InPin->bIsConstant; InOutPin->Direction = InPin->Direction; InOutPin->CPPType = InPin->CPPType; InOutPin->CPPTypeObjectPath = InPin->CPPTypeObjectPath; InOutPin->CPPTypeObject = InPin->CPPTypeObject; InOutPin->DefaultValue = InPin->DefaultValue; InOutPin->bIsDynamicArray = InPin->bIsDynamicArray; if(bCopyDisplayName) { InOutPin->SetDisplayName(InPin->GetDisplayName()); } if(InOutPin->IsExecuteContext() && InOutPin->CPPTypeObject != FRigVMExecuteContext::StaticStruct()) { MakeExecutePin(InOutPin); } } void URigVMController::ConfigurePinFromArgument(URigVMPin* InOutPin, const FRigVMGraphFunctionArgument& InArgument, bool bCopyDisplayName) { // it is important we copy things that define the identity of the pin // things that defines the state of the pin is copied during GetPinState() // though addmittedly these two functions have overlaps currently InOutPin->bIsConstant = InArgument.bIsConst; InOutPin->Direction = InArgument.Direction; InOutPin->CPPType = InArgument.CPPType.ToString(); InOutPin->CPPTypeObjectPath = *InArgument.CPPTypeObject.ToSoftObjectPath().ToString(); InOutPin->CPPTypeObject = InArgument.CPPTypeObject.Get(); InOutPin->DefaultValue = InArgument.DefaultValue; InOutPin->bIsDynamicArray = InArgument.bIsArray; if(bCopyDisplayName) { InOutPin->SetDisplayName(InArgument.DisplayName); } if(InOutPin->IsExecuteContext() && InOutPin->CPPTypeObject != FRigVMExecuteContext::StaticStruct()) { MakeExecutePin(InOutPin); } } bool URigVMController::ShouldStructBeUnfolded(const UStruct* Struct) { if (Struct == nullptr) { return false; } if (Struct->IsChildOf(UClass::StaticClass())) { return false; } if(Struct->IsChildOf(FRigVMExecuteContext::StaticStruct())) { return false; } if(Struct->IsChildOf(RigVMTypeUtils::GetWildCardCPPTypeObject())) { return false; } if (UnfoldStructDelegate.IsBound()) { if (!UnfoldStructDelegate.Execute(Struct)) { return false; } } return true; } bool URigVMController::ShouldPinBeUnfolded(URigVMPin* InPin) { if (InPin->IsStruct()) { return ShouldStructBeUnfolded(InPin->GetScriptStruct()); } if (InPin->IsArray()) { return InPin->GetDirection() == ERigVMPinDirection::Input || InPin->GetDirection() == ERigVMPinDirection::IO || InPin->IsFixedSizeArray(); } return false; } FProperty* URigVMController::FindPropertyForPin(const FString& InPinPath) { if(!IsValidGraph()) { return nullptr; } TArray Parts; if (!URigVMPin::SplitPinPath(InPinPath, Parts)) { return nullptr; } URigVMGraph* Graph = GetGraph(); check(Graph); URigVMPin* Pin = Graph->FindPin(InPinPath); if (Pin == nullptr) { ReportErrorf(TEXT("Cannot find pin '%s'."), *InPinPath); return nullptr; } URigVMNode* Node = Pin->GetNode(); URigVMUnitNode* UnitNode = Cast(Node); if (UnitNode) { int32 PartIndex = 1; // cut off the first one since it's the node UStruct* Struct = UnitNode->GetScriptStruct(); FProperty* Property = Struct->FindPropertyByName(*Parts[PartIndex++]); while (PartIndex < Parts.Num() && Property != nullptr) { if (FArrayProperty* ArrayProperty = CastField(Property)) { Property = ArrayProperty->Inner; PartIndex++; continue; } if (FStructProperty* StructProperty = CastField(Property)) { Struct = StructProperty->Struct; Property = Struct->FindPropertyByName(*Parts[PartIndex++]); continue; } break; } if (PartIndex == Parts.Num()) { return Property; } } return nullptr; } int32 URigVMController::DetachLinksFromPinObjects(const TArray* InLinks) { URigVMGraph* Graph = GetGraph(); check(Graph); TArray Links; if (InLinks) { Links = *InLinks; } else { Links = Graph->Links; } for (URigVMLink* Link : Links) { Notify(ERigVMGraphNotifType::LinkRemoved, Link); URigVMPin* SourcePin = Link->GetSourcePin(); URigVMPin* TargetPin = Link->GetTargetPin(); if (SourcePin) { Link->SourcePinPath = SourcePin->GetPinPath(); SourcePin->Links.Remove(Link); } if (TargetPin) { Link->TargetPinPath = TargetPin->GetPinPath(); TargetPin->Links.Remove(Link); } Link->SourcePin = nullptr; Link->TargetPin = nullptr; } if (InLinks == nullptr) { for (URigVMNode* Node : Graph->Nodes) { if (URigVMCollapseNode* CollapseNode = Cast(Node)) { FRigVMControllerGraphGuard GraphGuard(this, CollapseNode->GetContainedGraph(), false); TGuardValue GuardEditGraph(CollapseNode->ContainedGraph->bEditable, true); DetachLinksFromPinObjects(InLinks); } } } return Links.Num(); } int32 URigVMController::ReattachLinksToPinObjects(bool bFollowCoreRedirectors, const TArray* InLinks, bool bSetupOrphanedPins, bool bAllowNonArgumentLinks) { URigVMGraph* Graph = GetGraph(); check(Graph); FScopeLock Lock(&PinPathCoreRedirectorsLock); bool bReplacingAllLinks = false; TArray Links; if (InLinks) { Links = *InLinks; } else { Links = Graph->Links; bReplacingAllLinks = true; } TMap RedirectedPinPaths; if (bFollowCoreRedirectors) { for (URigVMLink* Link : Links) { FString RedirectedSourcePinPath; if (ShouldRedirectPin(Link->SourcePinPath, RedirectedSourcePinPath)) { OutputPinRedirectors.FindOrAdd(Link->SourcePinPath, RedirectedSourcePinPath); } FString RedirectedTargetPinPath; if (ShouldRedirectPin(Link->TargetPinPath, RedirectedTargetPinPath)) { InputPinRedirectors.FindOrAdd(Link->TargetPinPath, RedirectedTargetPinPath); } } } // fix up the pin links based on the persisted data TArray NewLinks; for (URigVMLink* Link : Links) { if (FString* RedirectedSourcePinPath = OutputPinRedirectors.Find(Link->SourcePinPath)) { ensure(Link->SourcePin == nullptr); Link->SourcePinPath = *RedirectedSourcePinPath; } if (FString* RedirectedTargetPinPath = InputPinRedirectors.Find(Link->TargetPinPath)) { ensure(Link->TargetPin == nullptr); Link->TargetPinPath = *RedirectedTargetPinPath; } URigVMPin* SourcePin = Link->GetSourcePin(); URigVMPin* TargetPin = Link->GetTargetPin(); if(bSetupOrphanedPins && (SourcePin != nullptr) && (TargetPin != nullptr)) { // ignore duplicated links that have been processed if (SourcePin->IsLinkedTo(TargetPin)) { continue; } if (!URigVMPin::CanLink(SourcePin, TargetPin, nullptr, nullptr, ERigVMPinDirection::IO, bAllowNonArgumentLinks)) { if(SourcePin->GetNode()->HasOrphanedPins() && bSetupOrphanedPins) { SourcePin = nullptr; } else if(TargetPin->GetNode()->HasOrphanedPins() && bSetupOrphanedPins) { TargetPin = nullptr; } else { ReportWarningf(TEXT("Unable to re-create link %s"), *URigVMLink::GetPinPathRepresentation(Link->SourcePinPath, Link->TargetPinPath)); TargetPin->Links.Remove(Link); SourcePin->Links.Remove(Link); continue; } } } if(bSetupOrphanedPins) { bool bRelayedBackToOrphanPin = false; for(int32 PinIndex=0; PinIndex<2; PinIndex++) { URigVMPin*& PinToFind = PinIndex == 0 ? SourcePin : TargetPin; if(PinToFind == nullptr) { const FString& PinPathToFind = PinIndex == 0 ? Link->SourcePinPath : Link->TargetPinPath; FString NodeName, RemainingPinPath; URigVMPin::SplitPinPathAtStart(PinPathToFind, NodeName, RemainingPinPath); check(!NodeName.IsEmpty() && !RemainingPinPath.IsEmpty()); URigVMNode* Node = Graph->FindNode(NodeName); if(Node == nullptr) { continue; } RemainingPinPath = FString::Printf(TEXT("%s%s"), *URigVMPin::OrphanPinPrefix, *RemainingPinPath); PinToFind = Node->FindPin(RemainingPinPath); if(PinToFind != nullptr) { if(PinIndex == 0) { Link->SourcePinPath = PinToFind->GetPinPath(); Link->SourcePin = nullptr; SourcePin = Link->GetSourcePin(); } else { Link->TargetPinPath = PinToFind->GetPinPath(); Link->TargetPin = nullptr; TargetPin = Link->GetTargetPin(); } bRelayedBackToOrphanPin = true; } } } } if (SourcePin == nullptr) { ReportWarningf(TEXT("Unable to re-create link %s"), *URigVMLink::GetPinPathRepresentation(Link->SourcePinPath, Link->TargetPinPath)); if (TargetPin != nullptr) { TargetPin->Links.Remove(Link); } continue; } if (TargetPin == nullptr) { ReportWarningf(TEXT("Unable to re-create link %s"), *URigVMLink::GetPinPathRepresentation(Link->SourcePinPath, Link->TargetPinPath)); if (SourcePin != nullptr) { SourcePin->Links.Remove(Link); } continue; } SourcePin->Links.AddUnique(Link); TargetPin->Links.AddUnique(Link); NewLinks.Add(Link); } if (bReplacingAllLinks) { Graph->Links = NewLinks; for (URigVMLink* Link : Graph->Links) { Notify(ERigVMGraphNotifType::LinkAdded, Link); } } else { // if we are running of a subset of links // find the ones we weren't able to connect // again and remove them. for (URigVMLink* Link : Links) { if (!NewLinks.Contains(Link)) { Graph->Links.Remove(Link); Notify(ERigVMGraphNotifType::LinkRemoved, Link); } else { Notify(ERigVMGraphNotifType::LinkAdded, Link); } } } if (InLinks == nullptr) { for (URigVMNode* Node : Graph->Nodes) { if (URigVMCollapseNode* CollapseNode = Cast(Node)) { FRigVMControllerGraphGuard GraphGuard(this, CollapseNode->GetContainedGraph(), false); TGuardValue GuardEditGraph(CollapseNode->ContainedGraph->bEditable, true); ReattachLinksToPinObjects(bFollowCoreRedirectors, nullptr, bSetupOrphanedPins, bAllowNonArgumentLinks); } } } InputPinRedirectors.Reset(); OutputPinRedirectors.Reset(); return NewLinks.Num(); } void URigVMController::RemoveStaleNodes() { if (!IsValidGraph()) { return; } URigVMGraph* Graph = GetGraph(); check(Graph); Graph->Nodes.Remove(nullptr); } void URigVMController::AddPinRedirector(bool bInput, bool bOutput, const FString& OldPinPath, const FString& NewPinPath) { if (OldPinPath.IsEmpty() || NewPinPath.IsEmpty() || OldPinPath == NewPinPath) { return; } if (bInput) { InputPinRedirectors.FindOrAdd(OldPinPath) = NewPinPath; } if (bOutput) { OutputPinRedirectors.FindOrAdd(OldPinPath) = NewPinPath; } } #if WITH_EDITOR bool URigVMController::ShouldRedirectPin(UScriptStruct* InOwningStruct, const FString& InOldRelativePinPath, FString& InOutNewRelativePinPath) const { if(InOwningStruct == nullptr) // potentially a template node { return false; } FControlRigStructPinRedirectorKey RedirectorKey(InOwningStruct, InOldRelativePinPath); if (const FString* RedirectedPinPath = PinPathCoreRedirectors.Find(RedirectorKey)) { InOutNewRelativePinPath = *RedirectedPinPath; return InOutNewRelativePinPath != InOldRelativePinPath; } FString RelativePinPath = InOldRelativePinPath; FString PinName, SubPinPath; if (!URigVMPin::SplitPinPathAtStart(RelativePinPath, PinName, SubPinPath)) { PinName = RelativePinPath; SubPinPath.Empty(); } bool bShouldRedirect = false; FCoreRedirectObjectName OldObjectName(*PinName, InOwningStruct->GetFName(), *InOwningStruct->GetOutermost()->GetPathName()); FCoreRedirectObjectName NewObjectName = FCoreRedirects::GetRedirectedName(ECoreRedirectFlags::Type_Property, OldObjectName); if (OldObjectName != NewObjectName) { PinName = NewObjectName.ObjectName.ToString(); bShouldRedirect = true; } FProperty* Property = InOwningStruct->FindPropertyByName(*PinName); if (Property == nullptr) { return false; } if (!SubPinPath.IsEmpty()) { if (FStructProperty* StructProperty = CastField(Property)) { FString NewSubPinPath; if (ShouldRedirectPin(StructProperty->Struct, SubPinPath, NewSubPinPath)) { SubPinPath = NewSubPinPath; bShouldRedirect = true; } } else if (FArrayProperty* ArrayProperty = CastField(Property)) { FString SubPinName, SubSubPinPath; if (URigVMPin::SplitPinPathAtStart(SubPinPath, SubPinName, SubSubPinPath)) { if (FStructProperty* InnerStructProperty = CastField(ArrayProperty->Inner)) { FString NewSubSubPinPath; if (ShouldRedirectPin(InnerStructProperty->Struct, SubSubPinPath, NewSubSubPinPath)) { SubSubPinPath = NewSubSubPinPath; SubPinPath = URigVMPin::JoinPinPath(SubPinName, SubSubPinPath); bShouldRedirect = true; } } } } } if (bShouldRedirect) { if (SubPinPath.IsEmpty()) { InOutNewRelativePinPath = PinName; PinPathCoreRedirectors.Add(RedirectorKey, InOutNewRelativePinPath); } else { InOutNewRelativePinPath = URigVMPin::JoinPinPath(PinName, SubPinPath); TArray OldParts, NewParts; if (URigVMPin::SplitPinPath(InOldRelativePinPath, OldParts) && URigVMPin::SplitPinPath(InOutNewRelativePinPath, NewParts)) { ensure(OldParts.Num() == NewParts.Num()); FString OldPath = OldParts[0]; FString NewPath = NewParts[0]; for (int32 PartIndex = 0; PartIndex < OldParts.Num(); PartIndex++) { if (PartIndex > 0) { OldPath = URigVMPin::JoinPinPath(OldPath, OldParts[PartIndex]); NewPath = URigVMPin::JoinPinPath(NewPath, NewParts[PartIndex]); } // this is also going to cache paths which haven't been redirected. // consumers of the table have to still compare old != new FControlRigStructPinRedirectorKey SubRedirectorKey(InOwningStruct, OldPath); if (!PinPathCoreRedirectors.Contains(SubRedirectorKey)) { PinPathCoreRedirectors.Add(SubRedirectorKey, NewPath); } } } } } return bShouldRedirect; } bool URigVMController::ShouldRedirectPin(const FString& InOldPinPath, FString& InOutNewPinPath) const { URigVMGraph* Graph = GetGraph(); check(Graph); FString PinPathInNode, NodeName; URigVMPin::SplitPinPathAtStart(InOldPinPath, NodeName, PinPathInNode); URigVMNode* Node = Graph->FindNode(NodeName); if (URigVMUnitNode* UnitNode = Cast(Node)) { FString NewPinPathInNode; if (ShouldRedirectPin(UnitNode->GetScriptStruct(), PinPathInNode, NewPinPathInNode)) { InOutNewPinPath = URigVMPin::JoinPinPath(NodeName, NewPinPathInNode); return true; } } else if (URigVMRerouteNode* RerouteNode = Cast(Node)) { URigVMPin* ValuePin = RerouteNode->Pins[0]; if (ValuePin->IsStruct()) { FString ValuePinPath = ValuePin->GetPinPath(); if (InOldPinPath == ValuePinPath) { return false; } else if (!InOldPinPath.StartsWith(ValuePinPath)) { return false; } FString PinPathInStruct, NewPinPathInStruct; if (URigVMPin::SplitPinPathAtStart(PinPathInNode, NodeName, PinPathInStruct)) { if (ShouldRedirectPin(ValuePin->GetScriptStruct(), PinPathInStruct, NewPinPathInStruct)) { InOutNewPinPath = URigVMPin::JoinPinPath(ValuePin->GetPinPath(), NewPinPathInStruct); return true; } } } } return false; } void URigVMController::RepopulatePinsOnNode(URigVMNode* InNode, bool bFollowCoreRedirectors, bool bSetupOrphanedPins, bool bDetachAndReattachLinks) { if (InNode == nullptr) { ReportError(TEXT("InNode is nullptr.")); return; } FRigVMControllerCompileBracketScope CompileBracketScope(this); URigVMUnitNode* UnitNode = Cast(InNode); URigVMRerouteNode* RerouteNode = Cast(InNode); URigVMFunctionEntryNode* EntryNode = Cast(InNode); URigVMFunctionReturnNode* ReturnNode = Cast(InNode); URigVMCollapseNode* CollapseNode = Cast(InNode); URigVMFunctionReferenceNode* FunctionRefNode = Cast(InNode); URigVMVariableNode* VariableNode = Cast(InNode); URigVMDispatchNode* DispatchNode = Cast(InNode); FScopeLock Lock(&PinPathCoreRedirectorsLock); const FRigVMRegistry& Registry = FRigVMRegistry::Get(); URigVMGraph* Graph = GetGraph(); check(Graph); // step 0/3: update execute pins for(URigVMPin* Pin : InNode->Pins) { if(Pin->IsExecuteContext()) { MakeExecutePin(Pin); } } bool bSetupOrphanPinsForThisNode = bSetupOrphanedPins; if(CollapseNode) { if(CollapseNode->GetOuter()->IsA()) { bSetupOrphanPinsForThisNode = false; } } const FPinInfoArray PreviousPinInfos(InNode); const uint32 PreviousPinHash = GetTypeHash(PreviousPinInfos); FPinInfoArray NewPinInfos; // step 2/3: clear pins on the node and repopulate the node with new pins if (UnitNode != nullptr) { UScriptStruct* ScriptStruct = UnitNode->GetScriptStruct(); if (ScriptStruct == nullptr) { // this may be an unresolved template node // in that case there's nothing we can do here return; } FString NodeColorMetadata; ScriptStruct->GetStringMetaDataHierarchical(*URigVMNode::NodeColorName, &NodeColorMetadata); if (!NodeColorMetadata.IsEmpty()) { UnitNode->NodeColor = GetColorFromMetadata(NodeColorMetadata); } const TSharedPtr DefaultValueContent = UnitNode->ConstructStructInstance(false); NewPinInfos.AddPins(ScriptStruct, this, ERigVMPinDirection::Invalid, INDEX_NONE, DefaultValueContent->GetStructMemory()); } else if (DispatchNode) { TMap PinTypeMap; for (const URigVMPin* Pin : DispatchNode->Pins) { PinTypeMap.Add(Pin->GetFName(), Pin->GetTypeIndex()); } const FRigVMTemplate* Template = DispatchNode->GetTemplate(); FRigVMDispatchContext DispatchContext = DispatchNode->GetDispatchContext(); auto AddExecutePins = [Template, DispatchNode, &Registry, &DispatchContext, &NewPinInfos, &PreviousPinInfos, this](ERigVMPinDirection InPinDirection) { for (int32 ArgIndex = 0; ArgIndex < Template->NumExecuteArguments(DispatchContext); ArgIndex++) { const FRigVMExecuteArgument* Arg = Template->GetExecuteArgument(ArgIndex, DispatchContext); const FRigVMTemplateArgumentType Type = Registry.GetType(Arg->TypeIndex); const TRigVMTypeIndex TypeIndex = Registry.GetTypeIndex(Type); FString DefaultValue; if(Registry.IsArrayType(Arg->TypeIndex)) { if(const FRigVMDispatchFactory* Factory = DispatchNode->GetFactory()) { DefaultValue = Factory->GetArgumentDefaultValue(Arg->Name, Arg->TypeIndex); } } (void)NewPinInfos.AddPin(this, INDEX_NONE, Arg->Name, Arg->Direction, TypeIndex, DefaultValue, nullptr, &PreviousPinInfos); } }; AddExecutePins(ERigVMPinDirection::IO); AddExecutePins(ERigVMPinDirection::Input); for (int32 ArgIndex = 0; ArgIndex < Template->NumArguments(); ArgIndex++) { const FRigVMTemplateArgument* Arg = Template->GetArgument(ArgIndex); TRigVMTypeIndex TypeIndex = INDEX_NONE; if(const TRigVMTypeIndex* ExistingTypeIndex = PinTypeMap.Find(Arg->GetName())) { TypeIndex = *ExistingTypeIndex; if(!Arg->SupportsTypeIndex(TypeIndex)) { TypeIndex = INDEX_NONE; } } if(TypeIndex == INDEX_NONE) { if(Arg->IsSingleton()) { TypeIndex = Arg->GetSupportedTypeIndices()[0]; } else if(Arg->GetArrayType() == FRigVMTemplateArgument::EArrayType_ArrayValue) { TypeIndex = RigVMTypeUtils::TypeIndex::WildCardArray; } else { TypeIndex = RigVMTypeUtils::TypeIndex::WildCard; } } FString DefaultValue; UScriptStruct* ArgumentScriptStruct = nullptr; const uint8* DefaultValueMemory = nullptr; if(const URigVMPin* ArgumentPin = DispatchNode->FindPin(Arg->Name.ToString())) { DefaultValue = ArgumentPin->GetDefaultValue(); ArgumentScriptStruct = Cast(ArgumentPin->GetCPPTypeObject()); } else if(const FRigVMDispatchFactory* Factory = DispatchNode->GetFactory()) { if(Arg->IsSingleton()) { DefaultValue = Factory->GetArgumentDefaultValue(Arg->Name, Arg->GetTypeIndices()[0]); const FRigVMTemplateArgumentType& Type = Registry.GetType(Arg->GetTypeIndices()[0]); ArgumentScriptStruct = Cast(Type.CPPTypeObject); } } FStructOnScope DefaultValueMemoryScope; // has to be in this scope so that DefaultValueMemory is valid if(ArgumentScriptStruct && !DefaultValue.IsEmpty()) { DefaultValueMemoryScope = FStructOnScope(ArgumentScriptStruct); FRigVMPinDefaultValueImportErrorContext ErrorPipe; ArgumentScriptStruct->ImportText(*DefaultValue, DefaultValueMemoryScope.GetStructMemory(), nullptr, PPF_None, &ErrorPipe, FString()); DefaultValueMemory = DefaultValueMemoryScope.GetStructMemory(); } (void)NewPinInfos.AddPin(this, INDEX_NONE, Arg->Name, Arg->GetDirection(), TypeIndex, DefaultValue, DefaultValueMemory, &PreviousPinInfos); } AddExecutePins(ERigVMPinDirection::Output); } else if ((RerouteNode != nullptr) || (VariableNode != nullptr)) { if (InNode->GetPins().Num() == 0) { return; } URigVMPin* ValuePin = nullptr; if(RerouteNode) { ValuePin = RerouteNode->Pins[0]; } else { ValuePin = VariableNode->FindPin(URigVMVariableNode::ValueName); } check(ValuePin); EnsurePinValidity(ValuePin, false); if(VariableNode) { // this includes local variables for validation const TArray ExternalVariables = GetAllVariables(false); const FRigVMGraphVariableDescription VariableDescription = VariableNode->GetVariableDescription(); const FRigVMExternalVariable CurrentExternalVariable = VariableDescription.ToExternalVariable(); FRigVMExternalVariable Variable; if (VariableNode->IsInputArgument()) { if (URigVMFunctionEntryNode* GraphEntryNode = Graph->GetEntryNode()) { if (URigVMPin* EntryPin = GraphEntryNode->FindPin(VariableDescription.Name.ToString())) { Variable = RigVMTypeUtils::ExternalVariableFromCPPType(VariableDescription.Name, EntryPin->GetCPPType(), EntryPin->GetCPPTypeObject()); } } } else { for(const FRigVMExternalVariable& ExternalVariable : ExternalVariables) { if(ExternalVariable.Name == CurrentExternalVariable.Name) { Variable = ExternalVariable; break; } } } if (Variable.IsValid(true)) { if(Variable.TypeName != CurrentExternalVariable.TypeName || Variable.TypeObject != CurrentExternalVariable.TypeObject || Variable.bIsArray != CurrentExternalVariable.bIsArray) { FString CPPType; UObject* CPPTypeObject; if(RigVMTypeUtils::CPPTypeFromExternalVariable(Variable, CPPType, &CPPTypeObject)) { RefreshVariableNode(VariableNode->GetFName(), Variable.Name, CPPType, Variable.TypeObject, false, bSetupOrphanPinsForThisNode); } else { ReportErrorf( TEXT("Control Rig '%s', Type of Variable '%s' cannot be resolved."), *InNode->GetOutermost()->GetPathName(), *Variable.Name.ToString() ); } } } else { ReportWarningf( TEXT("Control Rig '%s', Variable '%s' not found."), *InNode->GetOutermost()->GetPathName(), *CurrentExternalVariable.Name.ToString() ); } } NewPinInfos = FPinInfoArray(InNode, this, &PreviousPinInfos); } else if (EntryNode || ReturnNode) { if (URigVMLibraryNode* LibraryNode = Cast(InNode->GetGraph()->GetOuter())) { bool bIsEntryNode = EntryNode != nullptr; TArray SortedLibraryPins; // add execute pins first for (URigVMPin* LibraryPin : LibraryNode->GetPins()) { if (LibraryPin->IsExecuteContext()) { SortedLibraryPins.Add(LibraryPin); } } // add remaining pins for (URigVMPin* LibraryPin : LibraryNode->GetPins()) { SortedLibraryPins.AddUnique(LibraryPin); } for (URigVMPin* LibraryPin : SortedLibraryPins) { if (LibraryPin->GetDirection() == ERigVMPinDirection::IO && !LibraryPin->IsExecuteContext()) { continue; } if (bIsEntryNode) { if (LibraryPin->GetDirection() == ERigVMPinDirection::Output) { continue; } } else { if (LibraryPin->GetDirection() == ERigVMPinDirection::Input) { continue; } } ERigVMPinDirection Direction = bIsEntryNode ? ERigVMPinDirection::Output : ERigVMPinDirection::Input; (void)NewPinInfos.AddPin(LibraryPin, INDEX_NONE, Direction); } } else { // due to earlier bugs with copy and paste we can find entry and return nodes under the top level // graph. we'll ignore these for now. } } else if (CollapseNode) { NewPinInfos = FPinInfoArray(InNode, this, &PreviousPinInfos); } else if (FunctionRefNode) { const FRigVMGraphFunctionHeader& FunctionHeader = FunctionRefNode->GetReferencedFunctionHeader(); if(FunctionHeader.IsValid()) { NewPinInfos = FPinInfoArray(FunctionHeader, this, &PreviousPinInfos); } // if we can't find referenced node anymore // let's keep all pins else { NewPinInfos = FPinInfoArray(FunctionRefNode, this, &PreviousPinInfos); } } else { return; } auto RecursivelyRepopulatePinsOnCollapseNode = [this, bFollowCoreRedirectors, bSetupOrphanedPins] (URigVMCollapseNode* InCollapseNode) { if(InCollapseNode) { FRigVMControllerGraphGuard GraphGuard(this, InCollapseNode->GetContainedGraph(), false); TGuardValue GuardEditGraph(InCollapseNode->ContainedGraph->bEditable, true); // need to get a copy of the node array since the following function could remove nodes from the graph // we don't want to remove elements from the array we are iterating over. TArray ContainedNodes = InCollapseNode->GetContainedNodes(); for (URigVMNode* ContainedNode : ContainedNodes) { RepopulatePinsOnNode(ContainedNode, bFollowCoreRedirectors, bSetupOrphanedPins); } } }; // if the nodes match in structure - nothing to do here if(GetTypeHash(NewPinInfos) == PreviousPinHash) { // we at least need to recurse into a collapse node RecursivelyRepopulatePinsOnCollapseNode(CollapseNode); return; } #if UE_RIGVMCONTROLLER_VERBOSE_REPOPULATE UE_LOG(LogRigVMDeveloper, Display, TEXT("Repopulating pins on node %s"), *InNode->GetPathName()); #endif bool bRequireDetachLinks = false; bool bRequirePinStates = false; TArray NewPinsToAdd, PreviousPinsToRemove, PreviousPinsToOrphan, PreviousPinsToUpdate; for(int32 Index = 0; Index < PreviousPinInfos.Num(); Index++) { const FString PinPath = PreviousPinInfos.GetPinPath(Index); const int32 NewIndex = NewPinInfos.GetIndexFromPinPath(PinPath); if(NewIndex == INDEX_NONE) { const int32 RootIndex = PreviousPinInfos.GetRootIndex(Index); if(PreviousPinInfos[Index].Direction != ERigVMPinDirection::Hidden) { if(URigVMPin* Pin = InNode->FindPin(PinPath)) { if(!Pin->GetLinks().IsEmpty()) { bRequireDetachLinks = true; bRequirePinStates = true; } if(!PreviousPinsToOrphan.Contains(RootIndex)) { URigVMPin* RootPin = Pin->GetRootPin(); bool bPinShouldBeOrphaned = bSetupOrphanPinsForThisNode; if(!bPinShouldBeOrphaned) { if(RootPin->GetSourceLinks(true).Num() > 0 || RootPin->GetTargetLinks(true).Num() > 0) { bPinShouldBeOrphaned = true; } } if(bPinShouldBeOrphaned) { PreviousPinsToOrphan.Add(RootIndex); bRequireDetachLinks = true; bRequirePinStates = true; #if UE_RIGVMCONTROLLER_VERBOSE_REPOPULATE UE_LOG(LogRigVMDeveloper, Display, TEXT("Previously existing pin '%s' needs to be orphaned."), *RootPin->GetPinPath()); #endif } } } } if(!PreviousPinsToOrphan.Contains(RootIndex)) { PreviousPinsToRemove.Add(Index); #if UE_RIGVMCONTROLLER_VERBOSE_REPOPULATE UE_LOG(LogRigVMDeveloper, Display, TEXT("Previously existing pin '%s' is now obsolete."), *PinPath); #endif } } else if(GetTypeHash(PreviousPinInfos[Index]) != GetTypeHash(NewPinInfos[NewIndex])) { const bool bTypesDiffer = !Registry.CanMatchTypes(PreviousPinInfos[Index].TypeIndex, NewPinInfos[NewIndex].TypeIndex, true); if(PreviousPinInfos[Index].Direction != NewPinInfos[NewIndex].Direction) { bRequireDetachLinks = true; } else if(PreviousPinInfos[Index].Direction != ERigVMPinDirection::Hidden) { bRequireDetachLinks |= bTypesDiffer; } if(Registry.CanMatchTypes(PreviousPinInfos[Index].TypeIndex, NewPinInfos[NewIndex].TypeIndex, true)) { PreviousPinsToUpdate.Add(Index); } else { PreviousPinsToRemove.Add(Index); NewPinsToAdd.Add(NewIndex); } #if UE_RIGVMCONTROLLER_VERBOSE_REPOPULATE const FName& PreviousCPPType = Registry.GetType(PreviousPinInfos[Index].TypeIndex).CPPType; const FName& NewCPPType = Registry.GetType(NewPinInfos[NewIndex].TypeIndex).CPPType; const FString PreviousDirection = StaticEnum()->GetDisplayNameTextByValue((int64)PreviousPinInfos[Index].Direction).ToString(); const FString NewDirection = StaticEnum()->GetDisplayNameTextByValue((int64)NewPinInfos[NewIndex].Direction).ToString(); UE_LOG(LogRigVMDeveloper, Display, TEXT("Previous pin '%s' (Index %d, %s, %s) differs with new pin (Index %d, %s, %s)."), *PinPath, Index, *PreviousCPPType.ToString(), *PreviousDirection, NewIndex, *NewCPPType.ToString(), *NewDirection ); #endif } } for(int32 Index = 0; Index < NewPinInfos.Num(); Index++) { const FString PinPath = NewPinInfos.GetPinPath(Index); const int32 PreviousIndex = PreviousPinInfos.GetIndexFromPinPath(PinPath); if(PreviousIndex == INDEX_NONE) { NewPinsToAdd.Add(Index); #if UE_RIGVMCONTROLLER_VERBOSE_REPOPULATE UE_LOG(LogRigVMDeveloper, Display, TEXT("Newly required pin '%s' needs to be added."), *PinPath); #endif } else { // the previous pin exists - but it has been orphaned const int32 PreviousRootIndex = PreviousPinInfos.GetRootIndex(PreviousIndex); if(PreviousPinsToOrphan.Contains(PreviousRootIndex)) { NewPinsToAdd.Add(Index); #if UE_RIGVMCONTROLLER_VERBOSE_REPOPULATE UE_LOG(LogRigVMDeveloper, Display, TEXT("Orphaned pin '%s' needs to be re-added."), *PinPath); #endif } } } auto CreatePinFromPinInfo = [this, &Registry, &PreviousPinInfos](const FPinInfo& InPinInfo, const FString& InPinPath, UObject* InOuter) -> URigVMPin* { check(InOuter); URigVMPin* Pin = NewObject(InOuter, InPinInfo.Name); if(InPinInfo.Property) { ConfigurePinFromProperty(InPinInfo.Property, Pin, InPinInfo.Direction); } else { const FRigVMTemplateArgumentType& Type = Registry.GetType(InPinInfo.TypeIndex); Pin->CPPType = Type.CPPType.ToString(); Pin->CPPTypeObject = Type.CPPTypeObject; if(Pin->CPPTypeObject) { Pin->CPPTypeObjectPath = *Pin->CPPTypeObject->GetPathName(); } if(Registry.IsExecuteType(InPinInfo.TypeIndex)) { MakeExecutePin(Pin); } Pin->Direction = InPinInfo.Direction; Pin->DisplayName = InPinInfo.DisplayName.IsEmpty() ? NAME_None : FName(*InPinInfo.DisplayName); Pin->bIsConstant = InPinInfo.bIsConstant; Pin->bIsDynamicArray = InPinInfo.bIsDynamicArray; Pin->CustomWidgetName = InPinInfo.CustomWidgetName.IsEmpty() ? NAME_None : FName(*InPinInfo.CustomWidgetName); } Pin->bIsExpanded = InPinInfo.bIsExpanded; Pin->DefaultValue = InPinInfo.DefaultValue; // reuse expansion state and default value if(const FPinInfo* PreviousPin = PreviousPinInfos.GetPinFromPinPath(InPinPath)) { if(PreviousPin->TypeIndex == InPinInfo.TypeIndex) { Pin->bIsExpanded = PreviousPin->bIsExpanded; Pin->DefaultValue = PreviousPin->DefaultValue; } } if (URigVMPin* ParentPin = Cast(InOuter)) { AddSubPin(ParentPin, Pin); } else { AddNodePin(CastChecked(InOuter), Pin); } Notify(ERigVMGraphNotifType::PinAdded, Pin); return Pin; }; // step 1/3: keep a record of the current state of the node's pins TMap RedirectedPinPaths; if (bFollowCoreRedirectors) { RedirectedPinPaths = GetRedirectedPinPaths(InNode); } // also in case this node is part of an injection FName InjectionInputPinName = NAME_None; FName InjectionOutputPinName = NAME_None; if (URigVMInjectionInfo* InjectionInfo = InNode->GetInjectionInfo()) { InjectionInputPinName = InjectionInfo->InputPin ? InjectionInfo->InputPin->GetFName() : NAME_None; InjectionOutputPinName = InjectionInfo->OutputPin ? InjectionInfo->OutputPin->GetFName() : NAME_None; } TMap PinStates; TArray DetachedLinks; if(bRequirePinStates) { PinStates = GetPinStates(InNode); } if(bDetachAndReattachLinks && bRequireDetachLinks) { #if UE_RIGVMCONTROLLER_VERBOSE_REPOPULATE UE_LOG(LogRigVMDeveloper, Display, TEXT("Detaching links of node %s."), *InNode->GetPathName()); #endif DetachedLinks = InNode->GetLinks(); DetachLinksFromPinObjects(&DetachedLinks); } // we can do a simpler version of the update and simply // add new pins, remove obsolete pins and change types as needed #if UE_RIGVMCONTROLLER_VERBOSE_REPOPULATE UE_LOG(LogRigVMDeveloper, Display, TEXT("Performing fast update of node %s ..."), *InNode->GetPathName()); #endif // orphan pins for(int32 Index = 0; Index < PreviousPinsToOrphan.Num(); Index++) { const FString& PinPath = PreviousPinInfos.GetPinPath(PreviousPinsToOrphan[Index]); if(URigVMPin* Pin = InNode->FindPin(PinPath)) { #if UE_RIGVMCONTROLLER_VERBOSE_REPOPULATE UE_LOG(LogRigVMDeveloper, Display, TEXT("Orphaning pin '%s'."), *PinPath); #endif check(Pin->IsRootPin()); const FString OrphanedName = FString::Printf(TEXT("%s%s"), *URigVMPin::OrphanPinPrefix, *Pin->GetName()); if(InNode->FindPin(OrphanedName) == nullptr) { Pin->DisplayName = Pin->GetFName(); RenameObject(Pin, *OrphanedName, nullptr); InNode->Pins.Remove(Pin); Notify(ERigVMGraphNotifType::PinRemoved, Pin); InNode->OrphanedPins.Add(Pin); Notify(ERigVMGraphNotifType::PinAdded, Pin); } else { RemovePin(Pin, false); } } } // remove obsolete pins for(int32 Index = PreviousPinsToRemove.Num() - 1; Index >= 0; Index--) { const FString& PinPath = PreviousPinInfos.GetPinPath(PreviousPinsToRemove[Index]); if(URigVMPin* Pin = InNode->FindPin(PinPath)) { #if UE_RIGVMCONTROLLER_VERBOSE_REPOPULATE UE_LOG(LogRigVMDeveloper, Display, TEXT("Removing pin '%s'."), *PinPath); #endif RemovePin(Pin, false); } } // add missing pins for(int32 Index = 0; Index < NewPinsToAdd.Num(); Index++) { const FString& PinPath = NewPinInfos.GetPinPath(NewPinsToAdd[Index]); FString ParentPinPath, PinName; UObject* OuterForPin = InNode; if(URigVMPin::SplitPinPathAtEnd(PinPath, ParentPinPath, PinName)) { OuterForPin = InNode->FindPin(ParentPinPath); } (void)CreatePinFromPinInfo(NewPinInfos[NewPinsToAdd[Index]], PinPath, OuterForPin); #if UE_RIGVMCONTROLLER_VERBOSE_REPOPULATE UE_LOG(LogRigVMDeveloper, Display, TEXT("Adding new pin '%s'."), *PinPath); #endif } // update existing pins for(int32 Index = 0; Index < PreviousPinsToUpdate.Num(); Index++) { const FString& PinPath = PreviousPinInfos.GetPinPath(PreviousPinsToUpdate[Index]); const FPinInfo* NewPinInfo = NewPinInfos.GetPinFromPinPath(PinPath); check(NewPinInfo); if(URigVMPin* Pin = InNode->FindPin(PinPath)) { if(Pin->IsExecuteContext()) { MakeExecutePin(Pin); } if(Pin->GetTypeIndex() != NewPinInfo->TypeIndex) { // we expect these changes to only apply to float and double pins. check((NewPinInfo->TypeIndex == RigVMTypeUtils::TypeIndex::Float) || (NewPinInfo->TypeIndex == RigVMTypeUtils::TypeIndex::FloatArray) || (NewPinInfo->TypeIndex == RigVMTypeUtils::TypeIndex::Double) || (NewPinInfo->TypeIndex == RigVMTypeUtils::TypeIndex::DoubleArray)); const FRigVMTemplateArgumentType& NewType = Registry.GetType(NewPinInfo->TypeIndex); #if UE_RIGVMCONTROLLER_VERBOSE_REPOPULATE UE_LOG(LogRigVMDeveloper, Display, TEXT("Changing pin '%s' type from %s to %s."), *PinPath, *Pin->CPPType, *NewType.CPPType.ToString() ); #endif Pin->CPPType = NewType.CPPType.ToString(); Pin->CPPTypeObject = NewType.CPPTypeObject; check(Pin->CPPTypeObject == nullptr); Pin->CPPTypeObjectPath = NAME_None; Pin->LastKnownTypeIndex = NewPinInfo->TypeIndex; Notify(ERigVMGraphNotifType::PinTypeChanged, Pin); } } } // create a map representing the order of expected pins TMap> PinOrder; for(int32 Index = 0; Index < NewPinInfos.Num(); Index++) { const FPinInfo& NewPin = NewPinInfos[Index]; FString ParentPinPath; if(NewPin.ParentIndex != INDEX_NONE) { ParentPinPath = NewPinInfos.GetPinPath(NewPin.ParentIndex); if(NewPinInfos[NewPin.ParentIndex].bIsArray) { continue; } } TArray& SubPinOrder = PinOrder.FindOrAdd(ParentPinPath); SubPinOrder.Add(NewPin.Name); } auto SortPinArray = [this](TArray>& Pins, const TArray* PinOrder) { if(PinOrder == nullptr) { return; } if(Pins.Num() < 2) { return; } const TArray> PreviousPins = Pins; if(Pins[0]->IsArrayElement()) { Algo::Sort(Pins, [PinOrder](const TObjectPtr& A, const TObjectPtr& B) -> bool { return A->GetFName().Compare(B->GetFName()) < 0; }); } else { Algo::Sort(Pins, [PinOrder](const TObjectPtr& A, const TObjectPtr& B) -> bool { const int32 IndexA = PinOrder->Find(A->GetFName()); const int32 IndexB = PinOrder->Find(B->GetFName()); return IndexA < IndexB; }); } for(int32 Index=0;IndexGetPinPath(), PreviousPins.Find(Pins[Index]), Index ); #endif } } }; SortPinArray(InNode->Pins, PinOrder.Find(FString())); for(URigVMPin* Pin : InNode->Pins) { SortPinArray(Pin->SubPins, PinOrder.Find(Pin->GetPinPath())); } if(DispatchNode) { ResolveTemplateNodeMetaData(DispatchNode, false); } else if(CollapseNode) { if (!CollapseNode->GetOuter()->IsA()) { // no need to notify since the function library graph is invisible anyway RemoveUnusedOrphanedPins(CollapseNode); } RecursivelyRepopulatePinsOnCollapseNode(CollapseNode); } else if (FunctionRefNode) { // we want to make sure notify the graph of a potential name change // when repopulating the function ref node Notify(ERigVMGraphNotifType::NodeRenamed, FunctionRefNode); } if(!PinStates.IsEmpty()) { #if UE_RIGVMCONTROLLER_VERBOSE_REPOPULATE UE_LOG(LogRigVMDeveloper, Display, TEXT("Reapplying pin-states of node %s..."), *InNode->GetPathName()); #endif ApplyPinStates(InNode, PinStates, RedirectedPinPaths); } if(!DetachedLinks.IsEmpty()) { #if UE_RIGVMCONTROLLER_VERBOSE_REPOPULATE UE_LOG(LogRigVMDeveloper, Display, TEXT("Reattaching links of node %s..."), *InNode->GetPathName()); #endif ReattachLinksToPinObjects(bFollowCoreRedirectors, &DetachedLinks, bSetupOrphanPinsForThisNode); } if (URigVMInjectionInfo* InjectionInfo = InNode->GetInjectionInfo()) { InjectionInfo->InputPin = InNode->FindPin(InjectionInputPinName.ToString()); InjectionInfo->OutputPin = InNode->FindPin(InjectionOutputPinName.ToString()); } #if UE_RIGVMCONTROLLER_VERBOSE_REPOPULATE UE_LOG(LogRigVMDeveloper, Display, TEXT("Repopulate of node %s is completed.\n"), *InNode->GetPathName()); #endif } void URigVMController::RemovePinsDuringRepopulate(URigVMNode* InNode, TArray& InPins, bool bSetupOrphanedPins) { TArray Pins = InPins; for (URigVMPin* Pin : Pins) { if(bSetupOrphanedPins && !Pin->IsExecuteContext()) { URigVMPin* RootPin = Pin->GetRootPin(); const FString OrphanedName = FString::Printf(TEXT("%s%s"), *URigVMPin::OrphanPinPrefix, *RootPin->GetName()); URigVMPin* OrphanedRootPin = nullptr; for(URigVMPin* OrphanedPin : InNode->OrphanedPins) { if(OrphanedPin->GetName() == OrphanedName) { OrphanedRootPin = OrphanedPin; break; } } if(OrphanedRootPin == nullptr) { if(Pin->IsRootPin()) // if we are passing root pins we can reparent them directly { RootPin->DisplayName = RootPin->GetFName(); RenameObject(RootPin, *OrphanedName, nullptr); InNode->Pins.Remove(RootPin); if(!bSuspendNotifications) { Notify(ERigVMGraphNotifType::PinRemoved, RootPin); } InNode->OrphanedPins.Add(RootPin); if(!bSuspendNotifications) { Notify(ERigVMGraphNotifType::PinAdded, RootPin); } } else // while if we are iterating over sub pins - we should reparent them { OrphanedRootPin = NewObject(RootPin->GetNode(), *OrphanedName); ConfigurePinFromPin(OrphanedRootPin, RootPin); OrphanedRootPin->DisplayName = RootPin->GetFName(); OrphanedRootPin->GetNode()->OrphanedPins.Add(OrphanedRootPin); if(!bSuspendNotifications) { Notify(ERigVMGraphNotifType::PinAdded, OrphanedRootPin); } } } if(!Pin->IsRootPin() && (OrphanedRootPin != nullptr)) { RenameObject(Pin, nullptr, OrphanedRootPin); RootPin->SubPins.Remove(Pin); EnsurePinValidity(Pin, false); AddSubPin(OrphanedRootPin, Pin); } } } for (URigVMPin* Pin : Pins) { if(!Pin->IsOrphanPin()) { RemovePin(Pin, false); } } InPins.Reset(); } bool URigVMController::RemoveUnusedOrphanedPins(URigVMNode* InNode) { if(!InNode->HasOrphanedPins()) { return true; } TArray OrphanedPins = InNode->OrphanedPins; TArray RemainingOrphanPins; for(int32 PinIndex=0; PinIndex < OrphanedPins.Num(); PinIndex++) { URigVMPin* OrphanedPin = OrphanedPins[PinIndex]; const int32 NumSourceLinks = OrphanedPin->GetSourceLinks(true).Num(); const int32 NumTargetLinks = OrphanedPin->GetTargetLinks(true).Num(); if(NumSourceLinks + NumTargetLinks == 0) { RemovePin(OrphanedPin, false); } else { RemainingOrphanPins.Add(OrphanedPin); } } InNode->OrphanedPins = RemainingOrphanPins; return !InNode->HasOrphanedPins(); } #endif void URigVMController::SetupDefaultUnitNodeDelegates(TDelegate InCreateExternalVariableDelegate) { TWeakObjectPtr WeakThis(this); UnitNodeCreatedContext.GetAllExternalVariablesDelegate().BindLambda([WeakThis]() -> TArray { if (WeakThis.IsValid()) { return WeakThis->GetAllVariables(); } return TArray(); }); UnitNodeCreatedContext.GetBindPinToExternalVariableDelegate().BindLambda([WeakThis](FString InPinPath, FString InVariablePath) -> bool { if (WeakThis.IsValid()) { return WeakThis->BindPinToVariable(InPinPath, InVariablePath, true); } return false; }); UnitNodeCreatedContext.GetCreateExternalVariableDelegate() = InCreateExternalVariableDelegate; } void URigVMController::ResetUnitNodeDelegates() { UnitNodeCreatedContext.GetAllExternalVariablesDelegate().Unbind(); UnitNodeCreatedContext.GetBindPinToExternalVariableDelegate().Unbind(); UnitNodeCreatedContext.GetCreateExternalVariableDelegate().Unbind(); } FLinearColor URigVMController::GetColorFromMetadata(const FString& InMetadata) { FLinearColor Color = FLinearColor::Black; FString Metadata = InMetadata; Metadata.TrimStartAndEndInline(); FString SplitString(TEXT(" ")); FString Red, Green, Blue, GreenAndBlue; if (Metadata.Split(SplitString, &Red, &GreenAndBlue)) { Red.TrimEndInline(); GreenAndBlue.TrimStartInline(); if (GreenAndBlue.Split(SplitString, &Green, &Blue)) { Green.TrimEndInline(); Blue.TrimStartInline(); float RedValue = FCString::Atof(*Red); float GreenValue = FCString::Atof(*Green); float BlueValue = FCString::Atof(*Blue); Color = FLinearColor(RedValue, GreenValue, BlueValue); } } return Color; } TMap URigVMController::GetRedirectedPinPaths(URigVMNode* InNode) const { TMap RedirectedPinPaths; URigVMUnitNode* UnitNode = Cast(InNode); URigVMRerouteNode* RerouteNode = Cast(InNode); UScriptStruct* OwningStruct = nullptr; if (UnitNode) { OwningStruct = UnitNode->GetScriptStruct(); } else if (RerouteNode) { URigVMPin* ValuePin = RerouteNode->Pins[0]; if (ValuePin->IsStruct()) { OwningStruct = ValuePin->GetScriptStruct(); } } if (OwningStruct) { TArray AllPins = InNode->GetAllPinsRecursively(); for (URigVMPin* Pin : AllPins) { FString NodeName, PinPath; URigVMPin::SplitPinPathAtStart(Pin->GetPinPath(), NodeName, PinPath); if (RerouteNode) { FString ValuePinName, SubPinPath; if (URigVMPin::SplitPinPathAtStart(PinPath, ValuePinName, SubPinPath)) { FString RedirectedSubPinPath; if (ShouldRedirectPin(OwningStruct, SubPinPath, RedirectedSubPinPath)) { FString RedirectedPinPath = URigVMPin::JoinPinPath(ValuePinName, RedirectedSubPinPath); RedirectedPinPaths.Add(PinPath, RedirectedPinPath); } } } else { FString RedirectedPinPath; if (ShouldRedirectPin(OwningStruct, PinPath, RedirectedPinPath)) { RedirectedPinPaths.Add(PinPath, RedirectedPinPath); } } } }; return RedirectedPinPaths; } URigVMController::FPinState URigVMController::GetPinState(URigVMPin* InPin, bool bStoreWeakInjectionInfos) const { FPinState State; State.Direction = InPin->GetDirection(); State.CPPType = InPin->GetCPPType(); State.CPPTypeObject = InPin->GetCPPTypeObject(); State.DefaultValue = InPin->GetDefaultValue(); State.bIsExpanded = InPin->IsExpanded(); State.InjectionInfos = InPin->GetInjectedNodes(); if(bStoreWeakInjectionInfos) { for(URigVMInjectionInfo* InjectionInfo : State.InjectionInfos) { State.WeakInjectionInfos.Add(InjectionInfo->GetWeakInfo()); } State.InjectionInfos.Reset(); } return State; } TMap URigVMController::GetPinStates(URigVMNode* InNode, bool bStoreWeakInjectionInfos) const { TMap PinStates; TArray AllPins = InNode->GetAllPinsRecursively(); for (URigVMPin* Pin : AllPins) { FString PinPath, NodeName; URigVMPin::SplitPinPathAtStart(Pin->GetPinPath(), NodeName, PinPath); // we need to ensure validity here because GetPinState()-->GetDefaultValue() needs pin to be in a valid state. // some additional context: // right after load, some pins will be a invalid state because they don't have their CPPTypeObject, // which is expected since it is a transient property. // if the CPPTypeObject is not there, those pins may struggle with producing a valid default value // because Pin->IsStruct() will always be false if the pin does not have a valid type object. if (Pin->IsRootPin()) { EnsurePinValidity(Pin, true); } FPinState State = GetPinState(Pin, bStoreWeakInjectionInfos); PinStates.Add(PinPath, State); } return PinStates; } void URigVMController::ApplyPinState(URigVMPin* InPin, const FPinState& InPinState, bool bSetupUndoRedo) { for (URigVMInjectionInfo* InjectionInfo : InPinState.InjectionInfos) { if (InjectionInfo) { RenameObject(InjectionInfo, nullptr, InPin); InjectionInfo->InputPin = InjectionInfo->InputPin ? InjectionInfo->Node->FindPin(InjectionInfo->InputPin->GetName()) : nullptr; InjectionInfo->OutputPin = InjectionInfo->OutputPin ? InjectionInfo->Node->FindPin(InjectionInfo->OutputPin->GetName()) : nullptr; InPin->InjectionInfos.Add(InjectionInfo); } } // alternatively if the injection infos are not provided as strong pointers // we can fall back onto the weak ptr information and try again if(InPinState.InjectionInfos.IsEmpty()) { for (const URigVMInjectionInfo::FWeakInfo& InjectionInfo : InPinState.WeakInjectionInfos) { if(URigVMNode* FormerlyInjectedNode = InjectionInfo.Node.Get()) { if (FormerlyInjectedNode->IsInjected()) { URigVMInjectionInfo* Injection = Cast(FormerlyInjectedNode->GetOuter()); RenameObject(FormerlyInjectedNode, nullptr, InPin->GetGraph()); DestroyObject(Injection); } if(InjectionInfo.bInjectedAsInput) { const FString OutputPinPath = URigVMPin::JoinPinPath(FormerlyInjectedNode->GetNodePath(), InjectionInfo.OutputPinName.ToString()); AddLink(OutputPinPath, InPin->GetPinPath(), bSetupUndoRedo, false); } else { const FString InputPinPath = URigVMPin::JoinPinPath(FormerlyInjectedNode->GetNodePath(), InjectionInfo.InputPinName.ToString()); AddLink(InPin->GetPinPath(), InputPinPath, bSetupUndoRedo, false); } if(InPin->IsRootPin()) { InjectNodeIntoPin(InPin, InjectionInfo.bInjectedAsInput, InjectionInfo.InputPinName, InjectionInfo.OutputPinName, bSetupUndoRedo); } } } } if (!InPinState.DefaultValue.IsEmpty()) { FString DefaultValue = InPinState.DefaultValue; PostProcessDefaultValue(InPin, DefaultValue); if(!DefaultValue.IsEmpty()) { SetPinDefaultValue(InPin, DefaultValue, true, bSetupUndoRedo, false); } } SetPinExpansion(InPin, InPinState.bIsExpanded, bSetupUndoRedo); } void URigVMController::ApplyPinStates(URigVMNode* InNode, const TMap& InPinStates, const TMap& InRedirectedPinPaths, bool bSetupUndoRedo) { FRigVMControllerCompileBracketScope CompileBracketScope(this); for (const TPair& PinStatePair : InPinStates) { FString PinPath = PinStatePair.Key; const FPinState& PinState = PinStatePair.Value; if (InRedirectedPinPaths.Contains(PinPath)) { PinPath = InRedirectedPinPaths.FindChecked(PinPath); } if (URigVMPin* Pin = InNode->FindPin(PinPath)) { ApplyPinState(Pin, PinState, bSetupUndoRedo); } else { for (URigVMInjectionInfo* InjectionInfo : PinState.InjectionInfos) { if(URigVMPin* OuterPin = Cast(InjectionInfo->GetOuter())) { OuterPin->InjectionInfos.Remove(InjectionInfo); } RenameObject(InjectionInfo->Node, nullptr, InNode->GetGraph()); DestroyObject(InjectionInfo); } } } } void URigVMController::ReportInfo(const FString& InMessage) const { if (URigVMGraph* Graph = GetGraph()) { if (UPackage* Package = Cast(Graph->GetOutermost())) { UE_LOG(LogRigVMDeveloper, Display, TEXT("%s : %s"), *Package->GetPathName(), *InMessage); return; } } UE_LOG(LogRigVMDeveloper, Display, TEXT("%s"), *InMessage); } void URigVMController::ReportWarning(const FString& InMessage) const { if(!bReportWarningsAndErrors) { return; } FString Message = InMessage; if (URigVMGraph* Graph = GetGraph()) { if (UPackage* Package = Cast(Graph->GetOutermost())) { Message = FString::Printf(TEXT("%s : %s"), *Package->GetPathName(), *InMessage); } } FScriptExceptionHandler::Get().HandleException(ELogVerbosity::Warning, *Message, *FString()); } void URigVMController::ReportError(const FString& InMessage) const { if(!bReportWarningsAndErrors) { return; } FString Message = InMessage; if (URigVMGraph* Graph = GetGraph()) { if (UPackage* Package = Cast(Graph->GetOutermost())) { Message = FString::Printf(TEXT("%s : %s"), *Package->GetPathName(), *InMessage); } } FScriptExceptionHandler::Get().HandleException(ELogVerbosity::Error, *Message, *FString()); } void URigVMController::ReportAndNotifyInfo(const FString& InMessage) const { ReportWarning(InMessage); SendUserFacingNotification(InMessage, 0.f, nullptr, TEXT("MessageLog.Note")); } void URigVMController::ReportAndNotifyWarning(const FString& InMessage) const { if (!bReportWarningsAndErrors) { return; } ReportWarning(InMessage); SendUserFacingNotification(InMessage, 0.f, nullptr, TEXT("MessageLog.Warning")); } void URigVMController::ReportAndNotifyError(const FString& InMessage) const { if (!bReportWarningsAndErrors) { return; } ReportError(InMessage); SendUserFacingNotification(InMessage, 0.f, nullptr, TEXT("MessageLog.Error")); } void URigVMController::ReportPinTypeChange(URigVMPin* InPin, const FString& InNewCPPType) { /* UE_LOG(LogRigVMDeveloper, Warning, TEXT("Pin '%s' is about to change type from '%s' to '%s'."), *InPin->GetPinPath(), *InPin->GetCPPType(), *InNewCPPType ); */ } void URigVMController::SendUserFacingNotification(const FString& InMessage, float InDuration, const UObject* InSubject, const FName& InBrushName) const { #if WITH_EDITOR if(InDuration < SMALL_NUMBER) { InDuration = FMath::Clamp(0.1f * InMessage.Len(), 5.0f, 20.0f); } FNotificationInfo Info(FText::FromString(InMessage)); Info.bUseSuccessFailIcons = true; Info.Image = FAppStyle::GetBrush(InBrushName); Info.bFireAndForget = true; Info.bUseThrobber = true; // longer message needs more time to read Info.FadeOutDuration = FMath::Min(InDuration, 1.f); Info.ExpireDuration = InDuration; if(InSubject) { if(const URigVMNode* Node = Cast(InSubject)) { Info.HyperlinkText = FText::FromString(Node->GetNodePath()); } else if(const URigVMPin* Pin = Cast(InSubject)) { Info.HyperlinkText = FText::FromString(Pin->GetPinPath()); } else if(const URigVMLink* Link = Cast(InSubject)) { Info.HyperlinkText = FText::FromString(((URigVMLink*)Link)->GetPinPathRepresentation()); } else { Info.HyperlinkText = FText::FromName(InSubject->GetFName()); } Info.Hyperlink = FSimpleDelegate::CreateLambda([InSubject, this]() { if(RequestJumpToHyperlinkDelegate.IsBound()) { RequestJumpToHyperlinkDelegate.Execute(InSubject); } }); } TSharedPtr NotificationPtr = FSlateNotificationManager::Get().AddNotification(Info); if (NotificationPtr) { NotificationPtr->SetCompletionState(SNotificationItem::CS_Fail); } #endif } void URigVMController::CreateDefaultValueForStructIfRequired(UScriptStruct* InStruct, FString& InOutDefaultValue) { if (InStruct != nullptr) { TArray> TempBuffer; TempBuffer.AddUninitialized(InStruct->GetStructureSize()); // call the struct constructor to initialize the struct InStruct->InitializeDefaultValue(TempBuffer.GetData()); // apply any higher-level value overrides // for example, // struct B { int Test; B() {Test = 1;}}; ----> This is the constructor initialization, applied first in InitializeDefaultValue() above // struct A // { // Array TestArray; // A() // { // TestArray.Add(B()); // TestArray[0].Test = 2; ----> This is the overrride, applied below in ImportText() // } // } // See UnitTest RigVM->Graph->UnitNodeDefaultValue for more use case. if (!InOutDefaultValue.IsEmpty() && InOutDefaultValue != TEXT("()")) { FRigVMPinDefaultValueImportErrorContext ErrorPipe; InStruct->ImportText(*InOutDefaultValue, TempBuffer.GetData(), nullptr, PPF_None, &ErrorPipe, FString()); } // in case InOutDefaultValue is not empty, it needs to be cleared // before ExportText() because ExportText() appends to it. InOutDefaultValue.Reset(); InStruct->ExportText(InOutDefaultValue, TempBuffer.GetData(), nullptr, nullptr, PPF_None, nullptr); InStruct->DestroyStruct(TempBuffer.GetData()); } } void URigVMController::PostProcessDefaultValue(URigVMPin* Pin, FString& OutDefaultValue) { static const FString NoneString = FName(NAME_None).ToString(); static const FString QuotedNoneString = FString::Printf(TEXT("\"%s\""), *NoneString); if(OutDefaultValue == NoneString || OutDefaultValue == QuotedNoneString) { if(!Pin->IsStringType()) { OutDefaultValue.Reset(); } } if (Pin->IsStruct() || Pin->IsArray()) { if (!OutDefaultValue.IsEmpty()) { if (OutDefaultValue[0] != TCHAR('(') || OutDefaultValue[OutDefaultValue.Len()-1] != TCHAR(')')) { OutDefaultValue.Reset(); } } } if (Pin->IsArray() && OutDefaultValue.IsEmpty()) { OutDefaultValue = TEXT("()"); } else if (Pin->IsEnum() && OutDefaultValue.IsEmpty()) { int32 EnumIndex = Pin->GetEnum()->GetIndexByName(*NoneString); // make sure that none is a valid enum value if (EnumIndex != INDEX_NONE) { OutDefaultValue = NoneString; } else { // when none string is given but none is not a valid enum value, // it implies that the pin should use the default value of the enum // the value at index 0 is the best guess that can be made. // usually a user provided pin default is given later to override this value OutDefaultValue = Pin->GetEnum()->GetNameStringByIndex(0); } } else if (Pin->IsStruct() && (OutDefaultValue.IsEmpty() || OutDefaultValue == TEXT("()"))) { CreateDefaultValueForStructIfRequired(Pin->GetScriptStruct(), OutDefaultValue); } else if (Pin->IsStringType()) { while (OutDefaultValue.StartsWith(TEXT("\""))) { OutDefaultValue = OutDefaultValue.RightChop(1); } while (OutDefaultValue.EndsWith(TEXT("\""))) { OutDefaultValue = OutDefaultValue.LeftChop(1); } if(OutDefaultValue.IsEmpty() && Pin->GetCPPType() == RigVMTypeUtils::FNameType) { OutDefaultValue = FName(NAME_None).ToString(); } } } void URigVMController::ResolveTemplateNodeMetaData(URigVMTemplateNode* InNode, bool bSetupUndoRedo) { #if WITH_EDITOR check(InNode); TArray FilteredPermutationIndices = InNode->GetResolvedPermutationIndices(false); if(InNode->IsA()) { const FLinearColor PreviousColor = InNode->NodeColor; InNode->NodeColor = InNode->GetTemplate()->GetColor(FilteredPermutationIndices); if(!InNode->NodeColor.Equals(PreviousColor, 0.01f)) { Notify(ERigVMGraphNotifType::NodeColorChanged, InNode); } } #endif for(URigVMPin* Pin : InNode->GetPins()) { const FName DisplayName = InNode->GetDisplayNameForPin(Pin->GetFName()); if(Pin->DisplayName != DisplayName) { Pin->DisplayName = DisplayName; Notify(ERigVMGraphNotifType::PinRenamed, Pin); } } if(InNode->IsResolved()) { for(URigVMPin* Pin : InNode->GetPins()) { if(Pin->IsWildCard() || Pin->ContainsWildCardSubPin() || Pin->GetDirection() == ERigVMPinDirection::Hidden) { continue; } if(!Pin->IsValidDefaultValue(Pin->GetDefaultValue())) { const FString NewDefaultValue = InNode->GetInitialDefaultValueForPin(Pin->GetFName(), FilteredPermutationIndices); if(!NewDefaultValue.IsEmpty()) { SetPinDefaultValue(Pin, NewDefaultValue, true, bSetupUndoRedo, false); } } } } } bool URigVMController::FullyResolveTemplateNode(URigVMTemplateNode* InNode, int32 InPermutationIndex, bool bSetupUndoRedo) { if(bIsFullyResolvingTemplateNode) { return false; } TGuardValue ReentryGuard(bIsFullyResolvingTemplateNode, true); check(InNode); if (InNode->IsSingleton()) { return true; } const FRigVMTemplate* Template = InNode->GetTemplate(); const FRigVMDispatchFactory* Factory = Template->GetDispatchFactory(); InNode->ResolvedPermutation = InPermutationIndex; // Figure out the permutation index from the pin types if (InPermutationIndex == INDEX_NONE) { TArray Permutations = InNode->GetResolvedPermutationIndices(false); check(!Permutations.IsEmpty()); InNode->ResolvedPermutation = Permutations[0]; // make sure the permutation exists if(Factory) { FRigVMTemplateTypeMap TypeMap = InNode->GetTemplate()->GetTypesForPermutation(InNode->ResolvedPermutation); const FRigVMFunctionPtr DispatchFunction = Factory->GetDispatchFunction(TypeMap); const FRigVMFunction* ResolvedFunction = Template->GetPermutation(InNode->ResolvedPermutation); check(DispatchFunction); check(ResolvedFunction); check(ResolvedFunction->FunctionPtr == DispatchFunction); } } const FRigVMFunction* ResolvedFunction = Template->GetPermutation(InNode->ResolvedPermutation); const TArray PermutationIndices = {InNode->ResolvedPermutation}; // find all existing pins that we may need to change TArray MissingPins; TArray PinsToRemove; TMap PinTypesToChange; for(int32 ArgIndex = 0; ArgIndex < Template->NumArguments(); ArgIndex++) { const FRigVMTemplateArgument* Argument = Template->GetArgument(ArgIndex); const TRigVMTypeIndex ResolvedTypeIndex = Argument->GetSupportedTypeIndices(PermutationIndices)[0]; URigVMPin* Pin = InNode->FindPin(Argument->GetName().ToString()); if(Pin == nullptr) { ReportErrorf(TEXT("Template node %s is missing a pin for argument %s"), *InNode->GetNodePath(), *Argument->GetName().ToString() ); return false; } if(Pin->GetTypeIndex() != ResolvedTypeIndex && ResolvedTypeIndex != RigVMTypeUtils::TypeIndex::Execute) { PinTypesToChange.Add(Pin, ResolvedTypeIndex); } } // find all missing pins which are not arguments for the template if(ResolvedFunction) { if(ResolvedFunction->Struct == nullptr) { const TArray& Arguments = Template->Arguments; for(const FRigVMTemplateArgument& Argument : Arguments) { const TRigVMTypeIndex ExpectedTypeIndex = Argument.TypeIndices[InNode->ResolvedPermutation]; if(URigVMPin* Pin = InNode->FindPin(Argument.GetName().ToString())) { if(Pin->GetTypeIndex() != ExpectedTypeIndex && ExpectedTypeIndex != RigVMTypeUtils::TypeIndex::Execute) { PinTypesToChange.Add(Pin, ExpectedTypeIndex); } } else { MissingPins.Add(Argument); } } } else { TArray StructsToVisit = FRigVMTemplate::GetSuperStructs(ResolvedFunction->Struct, true); for(UStruct* StructToVisit : StructsToVisit) { for (TFieldIterator It(StructToVisit, EFieldIterationFlags::None); It; ++It) { const FRigVMTemplateArgument ExpectedArgument(*It); const TRigVMTypeIndex ExpectedTypeIndex = ExpectedArgument.GetSupportedTypeIndices()[0]; if(URigVMPin* Pin = InNode->FindPin(It->GetFName().ToString())) { if(Pin->GetTypeIndex() != ExpectedTypeIndex && ExpectedTypeIndex != RigVMTypeUtils::TypeIndex::Execute) { PinTypesToChange.Add(Pin, ExpectedTypeIndex); } } else { MissingPins.Add(ExpectedArgument); } } } } } // find all pins which don't have a matching arg on the function if(ResolvedFunction) { if(ResolvedFunction->Struct == nullptr) { FRigVMDispatchContext DispatchContext; if(const URigVMDispatchNode* DispatchNode = Cast(InNode)) { DispatchContext = DispatchNode->GetDispatchContext(); } const TArray& Arguments = Template->Arguments; for(URigVMPin* Pin : InNode->GetPins()) { bool bFound = Arguments.ContainsByPredicate([Pin](const FRigVMTemplateArgument& Argument) { return Pin->GetFName() == Argument.GetName(); }); if(!bFound) { bFound = Template->GetExecuteArguments(DispatchContext).ContainsByPredicate([Pin](const FRigVMExecuteArgument& Argument) { return Pin->GetFName() == Argument.Name; }); } if(!bFound) { PinsToRemove.Add(Pin); } } } else { for(URigVMPin* Pin : InNode->GetPins()) { if(ResolvedFunction->Struct->FindPropertyByName(Pin->GetFName()) == nullptr) { PinsToRemove.Add(Pin); } } } // update the cached resolved function name InNode->ResolvedFunctionName = ResolvedFunction->Name; } // exit out early if there's nothing to do if(PinTypesToChange.IsEmpty() && MissingPins.IsEmpty() && PinsToRemove.IsEmpty()) { ResolveTemplateNodeMetaData(InNode, bSetupUndoRedo); return true; } if(bSetupUndoRedo) { OpenUndoBracket(TEXT("Resolve Template Node")); } // update the incorrectly typed pins for(const TPair& Pair : PinTypesToChange) { URigVMPin* Pin = Pair.Key; const TRigVMTypeIndex& ExpectedTypeIndex = Pair.Value; if(!Pin->IsWildCard()) { if(Pin->GetTypeIndex() != ExpectedTypeIndex) { if (Pin->GetDirection() != ERigVMPinDirection::Hidden) { const int32 WildCardIndex = Pin->IsArray() ? RigVMTypeUtils::TypeIndex::WildCardArray : RigVMTypeUtils::TypeIndex::WildCard; if(!ChangePinType(Pin, WildCardIndex, bSetupUndoRedo, false, true, true)) { if(bSetupUndoRedo) { CancelUndoBracket(); } return false; } } if(!ChangePinType(Pin, ExpectedTypeIndex, bSetupUndoRedo, false, true, true)) { if(bSetupUndoRedo) { CancelUndoBracket(); } return false; } } } } // remove obsolete pins for(URigVMPin* PinToRemove : PinsToRemove) { RemovePin(PinToRemove, false); } // add missing pins if(ResolvedFunction) { if(ResolvedFunction->Struct == nullptr) { for(const FRigVMTemplateArgument& MissingPin : MissingPins) { check(MissingPin.GetDirection() == ERigVMPinDirection::Hidden); URigVMPin* Pin = NewObject(Cast(InNode), MissingPin.GetName()); const TRigVMTypeIndex TypeIndex = MissingPin.TypeIndices[InNode->ResolvedPermutation]; const FRigVMTemplateArgumentType& Type = FRigVMRegistry::Get().GetType(TypeIndex); Pin->Direction = MissingPin.GetDirection(); Pin->CPPType = Pin->LastKnownCPPType = Type.CPPType.ToString(); Pin->CPPTypeObject = Type.CPPTypeObject; Pin->CPPTypeObjectPath = Type.GetCPPTypeObjectPath(); Pin->LastKnownTypeIndex = TypeIndex; if(Factory) { const FName DisplayNameText = Factory->GetDisplayNameForArgument(MissingPin.GetName()); if(!DisplayNameText.IsNone()) { Pin->DisplayName = *DisplayNameText.ToString(); } } AddNodePin(InNode, Pin); Notify(ERigVMGraphNotifType::PinAdded, Pin); // we don't need to set the default value here since the pin is hidden } } else { for(const FRigVMTemplateArgument& MissingPin : MissingPins) { check(MissingPin.GetDirection() == ERigVMPinDirection::Hidden); FProperty* Property = ResolvedFunction->Struct->FindPropertyByName(MissingPin.GetName()); check(Property); URigVMPin* Pin = NewObject(Cast(InNode), MissingPin.GetName()); ConfigurePinFromProperty(Property, Pin, MissingPin.GetDirection()); AddNodePin(InNode, Pin); Notify(ERigVMGraphNotifType::PinAdded, Pin); // we don't need to set the default value here since the pin is hidden } } } if(bSetupUndoRedo) { #if WITH_EDITOR RegisterUseOfTemplate(InNode); #endif CloseUndoBracket(); } return true; } bool URigVMController::ResolveWildCardPin(const FString& InPinPath, const FString& InCPPType, const FName& InCPPTypeObjectPath, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); UObject* CPPTypeObject = nullptr; if (!InCPPTypeObjectPath.IsNone()) { CPPTypeObject = RigVMTypeUtils::FindObjectFromCPPTypeObjectPath(InCPPTypeObjectPath.ToString()); if (CPPTypeObject == nullptr) { ReportErrorf(TEXT("Cannot find cpp type object for path '%s'."), *InCPPTypeObjectPath.ToString()); return false; } } const FString CPPType = RigVMTypeUtils::PostProcessCPPType(InCPPType, CPPTypeObject); if (URigVMPin* Pin = Graph->FindPin(InPinPath)) { if (ResolveWildCardPin(Pin, FRigVMTemplateArgumentType(*CPPType, CPPTypeObject), bSetupUndoRedo, bPrintPythonCommand)) { if (bPrintPythonCommand) { const FString GraphName = GetSanitizedGraphName(GetGraph()->GetGraphName()); // bool ResolveWildCardPin(const FString& InPinPath, const FString& InCPPType, const FName& InCPPTypeObjectPath, bool bSetupUndoRedo = true, bool bPrintPythonCommand = false); RigVMPythonUtils::Print(GetGraphOuterName(), FString::Printf(TEXT("blueprint.get_controller_by_name('%s').resolve_wild_card_pin('%s', '%s', '%s')"), *GraphName, *InPinPath, *InCPPType, *InCPPTypeObjectPath.ToString())); } return true; } } return false; } bool URigVMController::ResolveWildCardPin(URigVMPin* InPin, const FRigVMTemplateArgumentType& InType, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } return ResolveWildCardPin(InPin, FRigVMRegistry::Get().GetTypeIndex(InType), bSetupUndoRedo, bPrintPythonCommand); } bool URigVMController::ResolveWildCardPin(const FString& InPinPath, TRigVMTypeIndex InTypeIndex, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); if (URigVMPin* Pin = Graph->FindPin(InPinPath)) { return ResolveWildCardPin(Pin, InTypeIndex, bSetupUndoRedo, bPrintPythonCommand); } return false; } bool URigVMController::ResolveWildCardPin(URigVMPin* InPin, TRigVMTypeIndex InTypeIndex, bool bSetupUndoRedo, bool bPrintPythonCommand) { if (InPin->IsStructMember()) { return false; } if(FRigVMRegistry::Get().IsWildCardType(InTypeIndex)) { return false; } if (InPin->GetTypeIndex() == InTypeIndex) { return false; } URigVMPin* RootPin = InPin; TRigVMTypeIndex Type = InTypeIndex; while (RootPin->IsArrayElement()) { RootPin = InPin->GetParentPin(); Type = FRigVMRegistry::Get().GetArrayTypeFromBaseTypeIndex(Type); } ensure(RootPin->GetNode()->IsA()); URigVMTemplateNode* TemplateNode = CastChecked(RootPin->GetNode()); TRigVMTypeIndex NewType = INDEX_NONE; TemplateNode->SupportsType(RootPin, Type, &NewType); if (NewType != INDEX_NONE) { // We support the new type, and its different than the pin type if (NewType == Type && InPin->GetTypeIndex() != NewType) { } // We support the new type, but its different than the one provided else if (NewType != Type) { // if its the same as the pin, we are done if (InPin->GetTypeIndex() == NewType) { return false; } Type = NewType; } } else { return false; } FRigVMBaseAction Action; if (bSetupUndoRedo) { Action.Title = TEXT("Resolve Wildcard Pin"); ActionStack->BeginAction(Action); } if (!InPin->IsWildCard()) { if (!UnresolveTemplateNodes({TemplateNode}, bSetupUndoRedo)) { return false; } } if (!ChangePinType(RootPin, Type, bSetupUndoRedo, true, true, false)) { if (bSetupUndoRedo) { ActionStack->CancelAction(Action, this); } return false; } UpdateTemplateNodePinTypes(TemplateNode, bSetupUndoRedo); if (bSetupUndoRedo) { ActionStack->EndAction(Action); } return true; } bool URigVMController::UpdateTemplateNodePinTypes(URigVMTemplateNode* InNode, bool bSetupUndoRedo, bool bInitializeDefaultValue) { URigVMGraph* Graph = GetGraph(); check(InNode->GetGraph() == Graph); bool bAnyTypeChanged = false; const FRigVMTemplate* Template = InNode->GetTemplate(); if(Template == nullptr) { return false; } const FRigVMRegistry& Registry = FRigVMRegistry::Get(); if (InNode->IsA() || InNode->IsA()) { return false; } TArray ResolvedPermutations = InNode->GetResolvedPermutationIndices(true); InNode->ResolvedPermutation = ResolvedPermutations.Num() == 1 ? ResolvedPermutations[0] : INDEX_NONE; TArray Pins = InNode->GetPins(); TArray Arguments; Arguments.SetNumUninitialized(Pins.Num()); for(int32 PinIndex=0; PinIndex < Pins.Num(); ++PinIndex) { URigVMPin* Pin = Pins[PinIndex]; Arguments[PinIndex] = Template->FindArgument(Pin->GetFName()); } // Remove invalid permutations ResolvedPermutations.RemoveAll([&](const int32& Permutation) { for (const FRigVMTemplateArgument* Argument : Arguments) { if (Argument && Argument->TypeIndices[Permutation] == INDEX_NONE) { return true; } } return false; }); FRigVMDispatchContext DispatchContext; if(const URigVMDispatchNode* DispatchNode = Cast(InNode)) { DispatchContext = DispatchNode->GetDispatchContext(); } // Find the types for each possible permutation TMap> PinTypes; // permutation to pin types for (int32 ResolvedPermutation : ResolvedPermutations) { for(int32 PinIndex=0; PinIndex < Pins.Num(); ++PinIndex) { URigVMPin* Pin = Pins[PinIndex]; if (Pin->GetDirection() == ERigVMPinDirection::Hidden) { PinTypes.FindOrAdd(ResolvedPermutation).Add(INDEX_NONE); continue; } bool bAddedType = false; TArray& Types = PinTypes.FindOrAdd(ResolvedPermutation); if (const FRigVMTemplateArgument* Argument = Arguments[PinIndex]) { Types.Add(Argument->TypeIndices[ResolvedPermutation]); bAddedType = true; } else if (const FRigVMExecuteArgument* ExecuteArgument = Template->FindExecuteArgument(Pin->GetFName(), DispatchContext)) { Types.Add(ExecuteArgument->TypeIndex); bAddedType = true; } if (!bAddedType) { // This is a pin with no argument // Its marked as invalid, and will show wildcard type Types.Add(INDEX_NONE); } } } // Some pins can benefit from reducing types to a single option // Even if some pins reduce to a single type (maybe represented by a single permutation), the rest should still display a wildcard pin // If reduction happens for multiple pins, we need to make sure that they reduce to the same permutation // If possible, we want to respect the current pin type if its not wildcard TArray WasReduced; WasReduced.SetNumZeroed(Pins.Num()); TMap> ReducedTypes = PinTypes; TArray FinalPinTypes; FinalPinTypes.SetNumUninitialized(Pins.Num()); // Reduce compatible types, try to find the permutation in the reduced types for(int32 PinIndex=0; PinIndex < Pins.Num(); ++PinIndex) { URigVMPin* Pin = Pins[PinIndex]; if (Pin->GetDirection() == ERigVMPinDirection::Hidden) { continue; } TArray Types; Types.Reserve(ResolvedPermutations.Num()); TRigVMTypeIndex PreferredType = INDEX_NONE; int32 TypesFoundInReduced = 0; for (int32 ResolvedPermutation : ResolvedPermutations) { Types.Add(PinTypes.FindChecked(ResolvedPermutation)[PinIndex]); if (ReducedTypes.Contains(ResolvedPermutation)) { TypesFoundInReduced++; if (PreferredType == INDEX_NONE) { PreferredType = PinTypes.FindChecked(ResolvedPermutation)[PinIndex]; } } } if (TypesFoundInReduced > 1) { PreferredType = Pin->GetTypeIndex(); } FinalPinTypes[PinIndex] = InNode->TryReduceTypesToSingle(Types, PreferredType); if (FinalPinTypes[PinIndex] != INDEX_NONE) { WasReduced[PinIndex] = true; } // Remove reduced types which do not match this type TArray PermutationToRemove; for (TPair>& Pair : ReducedTypes) { if (Pair.Value[PinIndex] != FinalPinTypes[PinIndex]) { PermutationToRemove.Add(Pair.Key); } } for (int32& ToRemove : PermutationToRemove) { ReducedTypes.Remove(ToRemove); } } // First unresolve any pins that need unresolving, then resolve everything else for (int32 UnresolveResolve=0; UnresolveResolve<2; ++UnresolveResolve) { for(int32 PinIndex=0; PinIndex < Pins.Num(); ++PinIndex) { URigVMPin* Pin = Pins[PinIndex]; if (UnresolveResolve == 0 && Pin->IsWildCard()) { continue; } if (Pin->GetDirection() == ERigVMPinDirection::Hidden) { continue; } bool bShouldUnresolve = FinalPinTypes[PinIndex] == INDEX_NONE; // If we are about to resolve the pin to a different type, first unresolve it if (!bShouldUnresolve && UnresolveResolve == 0 && Pin->GetTypeIndex() != FinalPinTypes[PinIndex]) { bShouldUnresolve = true; } if (bShouldUnresolve) { // Unresolve if (Pin->HasInjectedNodes()) { EjectNodeFromPin(Pin, bSetupUndoRedo); } const FRigVMTemplateArgument* Argument = Template->FindArgument(*Pin->GetName()); FRigVMTemplateArgument::EArrayType ArrayType; if (Argument) { ArrayType = Argument->GetArrayType(); } else { ArrayType = Pin->IsArray() ? FRigVMTemplateArgument::EArrayType::EArrayType_ArrayValue : FRigVMTemplateArgument::EArrayType::EArrayType_SingleValue; } FString CPPType = RigVMTypeUtils::GetWildCardCPPType(); UObject* CPPObjectType = RigVMTypeUtils::GetWildCardCPPTypeObject(); if (ArrayType == FRigVMTemplateArgument::EArrayType_ArrayValue) { CPPType = RigVMTypeUtils::GetWildCardArrayCPPType(); } else if(ArrayType == FRigVMTemplateArgument::EArrayType_Mixed) { CPPType = Pin->IsArray() ? RigVMTypeUtils::GetWildCardArrayCPPType() : RigVMTypeUtils::GetWildCardCPPType(); } // execute pins are no longer part of the template, avoid changing the type if(Argument == nullptr && Pin->IsExecuteContext()) { CPPType = Pin->GetCPPType(); CPPObjectType = Pin->GetCPPTypeObject(); } if (Pin->GetCPPType() != CPPType || Pin->GetCPPTypeObject() != CPPObjectType) { bAnyTypeChanged = !Pin->IsExecuteContext(); if(bAnyTypeChanged) { ReportPinTypeChange(Pin, CPPType); } ChangePinType(Pin, CPPType, CPPObjectType, bSetupUndoRedo, false, false, false, bInitializeDefaultValue); } } else { // Resolve if (Pin->GetTypeIndex() != FinalPinTypes[PinIndex]) { bAnyTypeChanged = !Pin->IsExecuteContext(); if(bAnyTypeChanged) { const FString CPPType = Registry.GetType(FinalPinTypes[PinIndex]).CPPType.ToString(); ReportPinTypeChange(Pin, CPPType); } ChangePinType(Pin, FinalPinTypes[PinIndex], bSetupUndoRedo, false, false, false, bInitializeDefaultValue); } } } } bool bHasWildcard = InNode->HasWildCardPin(); if (bHasWildcard) { InNode->ResolvedPermutation = INDEX_NONE; } else { // If we got into a resolved permutation through reducing types, we need to set the result in the node ResolvedPermutations = InNode->GetResolvedPermutationIndices(false); check(ResolvedPermutations.Num() == 1); InNode->ResolvedPermutation = ResolvedPermutations[0]; } if (URigVMUnitNode* UnitNode = Cast(InNode)) { if (const FRigVMFunction* Function = UnitNode->GetResolvedFunction()) { UnitNode->ResolvedFunctionName = Function->GetName(); } } return bAnyTypeChanged; } bool URigVMController::PrepareToLink(URigVMPin* FirstToResolve, URigVMPin* SecondToResolve, bool bSetupUndoRedo) { FRigVMRegistry& Registry = FRigVMRegistry::Get(); // Check if there is anything to do if (!FirstToResolve->IsWildCard() && !SecondToResolve->IsWildCard() && Registry.CanMatchTypes(FirstToResolve->GetTypeIndex(), SecondToResolve->GetTypeIndex(), true)) { return true; } // Find out the matching supported types TArray MatchingTypes; { auto GetPinSupportedTypes = [&Registry](URigVMPin* Pin) -> TArray { URigVMTemplateNode* TemplateNode = Cast(Pin->GetNode()); if (!TemplateNode || TemplateNode->IsSingleton() || !Pin->IsWildCard()) { return {Pin->GetTypeIndex()}; } else { if (const FRigVMTemplate* Template = TemplateNode->GetTemplate()) { URigVMPin* RootPin = Pin; uint8 ArrayLevels = 0; while (RootPin->GetParentPin() != nullptr) { ArrayLevels++; RootPin = RootPin->GetParentPin(); } if (const FRigVMTemplateArgument* Argument = Template->FindArgument(RootPin->GetFName())) { TArray ResolvedPermutations = TemplateNode->GetResolvedPermutationIndices(true); TArray Types = Argument->GetSupportedTypeIndices(ResolvedPermutations); for (int32 i=0; i SecondTypes = GetPinSupportedTypes(SecondToResolve); MatchingTypes.RemoveAll([&Registry, SecondTypes](const TRigVMTypeIndex& FirstType) { return !SecondTypes.ContainsByPredicate([&Registry, FirstType](const TRigVMTypeIndex& SecondType) { return Registry.CanMatchTypes(FirstType, SecondType, true); }); }); } if (MatchingTypes.IsEmpty()) { return false; } // reduce matching types by duplicate entries for float / double { TArray FilteredMatchingTypes; FilteredMatchingTypes.Reserve(MatchingTypes.Num()); for(const TRigVMTypeIndex& MatchingType : MatchingTypes) { // special case float singe & array if(MatchingType == RigVMTypeUtils::TypeIndex::Float) { if(MatchingTypes.Contains(RigVMTypeUtils::TypeIndex::Double)) { continue; } } if(MatchingType == RigVMTypeUtils::TypeIndex::FloatArray) { if(MatchingTypes.Contains(RigVMTypeUtils::TypeIndex::DoubleArray)) { continue; } } bool bAlreadyContainsMatch = false; for(const TRigVMTypeIndex& FilteredType : FilteredMatchingTypes) { if(Registry.CanMatchTypes(MatchingType, FilteredType, true)) { bAlreadyContainsMatch = true; break; } } if(bAlreadyContainsMatch) { continue; } FilteredMatchingTypes.Add(MatchingType); } Swap(FilteredMatchingTypes, MatchingTypes); } TRigVMTypeIndex FinalType = INDEX_NONE; if (MatchingTypes.Num() > 1) { // Query the user for the type to resolve the pin (from the list of MatchingTypes) if (RequestPinTypeSelectionDelegate.IsBound()) { FinalType = RequestPinTypeSelectionDelegate.Execute(MatchingTypes); } else { return false; } } else { FinalType = MatchingTypes[0]; } // If only one match, resolve both pins to that bool bSuccess = true; if (FinalType != INDEX_NONE) { if (FirstToResolve->IsWildCard()) { bSuccess &= ResolveWildCardPin(FirstToResolve, FinalType, bSetupUndoRedo); } if (SecondToResolve->IsWildCard()) { bSuccess &= ResolveWildCardPin(SecondToResolve, FinalType, bSetupUndoRedo); } } else { return false; } return bSuccess; } bool URigVMController::ChangePinType(const FString& InPinPath, const FString& InCPPType, const FName& InCPPTypeObjectPath, bool bSetupUndoRedo, bool bSetupOrphanPins, bool bBreakLinks, bool bRemoveSubPins, bool bInitializeDefaultValue) { if (!IsValidGraph()) { return false; } if (!bIsTransacting && !IsGraphEditable()) { return false; } URigVMGraph* Graph = GetGraph(); check(Graph); if (URigVMPin* Pin = Graph->FindPin(InPinPath)) { return ChangePinType(Pin, InCPPType, InCPPTypeObjectPath, bSetupUndoRedo, bSetupOrphanPins, bBreakLinks, bRemoveSubPins, bInitializeDefaultValue); } return false; } bool URigVMController::ChangePinType(URigVMPin* InPin, const FString& InCPPType, const FName& InCPPTypeObjectPath, bool bSetupUndoRedo, bool bSetupOrphanPins, bool bBreakLinks, bool bRemoveSubPins, bool bInitializeDefaultValue) { if (!bIsTransacting && !IsGraphEditable()) { return false; } if (InCPPType == TEXT("None") || InCPPType.IsEmpty()) { return false; } UObject* CPPTypeObject = RigVMTypeUtils::FindObjectFromCPPTypeObjectPath(InCPPTypeObjectPath.ToString()); // always refresh pin type if it is a user defined struct, whose internal layout can change at anytime bool bForceRefresh = false; if (CPPTypeObject && CPPTypeObject->IsA()) { bForceRefresh = true; } if (!bForceRefresh) { if (InPin->CPPType == InCPPType && InPin->CPPTypeObject == CPPTypeObject) { return true; } } return ChangePinType(InPin, InCPPType, CPPTypeObject, bSetupUndoRedo, bSetupOrphanPins, bBreakLinks, bRemoveSubPins, bInitializeDefaultValue); } bool URigVMController::ChangePinType(URigVMPin* InPin, const FString& InCPPType, UObject* InCPPTypeObject, bool bSetupUndoRedo, bool bSetupOrphanPins, bool bBreakLinks, bool bRemoveSubPins, bool bInitializeDefaultValue) { if (!bIsTransacting && !IsGraphEditable()) { return false; } if (InCPPType == TEXT("None") || InCPPType.IsEmpty()) { return false; } if (RigVMTypeUtils::RequiresCPPTypeObject(InCPPType) && !InCPPTypeObject) { return false; } const FRigVMTemplateArgumentType Type(*InCPPType, InCPPTypeObject); // pin types are chosen from the graph pin type menu so it is not guaranteed that the chosen type // is registered, hence the use of FindOrAddType const TRigVMTypeIndex TypeIndex = FRigVMRegistry::Get().FindOrAddType(Type); return ChangePinType(InPin, TypeIndex, bSetupUndoRedo, bSetupOrphanPins, bBreakLinks, bRemoveSubPins, bInitializeDefaultValue); } bool URigVMController::ChangePinType(URigVMPin* InPin, TRigVMTypeIndex InTypeIndex, bool bSetupUndoRedo, bool bSetupOrphanPins, bool bBreakLinks, bool bRemoveSubPins, bool bInitializeDefaultValue) { if (!bIsTransacting && !IsGraphEditable()) { return false; } if (InTypeIndex == INDEX_NONE) { return false; } check(InPin->GetGraph() == GetGraph()); if(InPin->IsExecuteContext() && FRigVMRegistry::Get().IsExecuteType(InTypeIndex)) { return false; } // only allow valid pin cpp types on template nodes TRigVMTypeIndex TypeIndex = InTypeIndex; if(URigVMTemplateNode* TemplateNode = Cast(InPin->GetNode())) { if (!bIsTransacting) { if (InPin->GetDirection() != ERigVMPinDirection::Hidden) { if (TemplateNode->IsA() || TemplateNode->IsA()) { if(!TemplateNode->SupportsType(InPin, InTypeIndex)) { ReportErrorf(TEXT("ChangePinType: %s doesn't support type '%s'."), *InPin->GetPinPath(), *FRigVMRegistry::Get().GetType(InTypeIndex).CPPType.ToString()); return false; } } } } // If changing to wildcard, try to maintain the container type const FRigVMRegistry& Registry = FRigVMRegistry::Get(); if (Registry.IsWildCardType(TypeIndex)) { const bool bIsArrayType = FRigVMRegistry::Get().IsArrayType(TypeIndex); if(InPin->IsRootPin() && bIsArrayType != InPin->IsArray()) { // nothing to do here - leave the type as is } else { const TRigVMTypeIndex BaseTypeIndex = bIsArrayType ? FRigVMRegistry::Get().GetBaseTypeFromArrayTypeIndex(TypeIndex) : TypeIndex; TypeIndex = InPin->IsArray() ? Registry.GetArrayTypeFromBaseTypeIndex(BaseTypeIndex) : BaseTypeIndex; } } } FRigVMControllerCompileBracketScope CompileScope(this); FRigVMBaseAction Action; if (bSetupUndoRedo) { Action.Title = TEXT("Change pin type"); ActionStack->BeginAction(Action); } TArray Links; if (bSetupUndoRedo) { if(!bSetupOrphanPins && bBreakLinks) { BreakAllLinks(InPin, true, true); BreakAllLinks(InPin, false, true); BreakAllLinksRecursive(InPin, true, false, true); BreakAllLinksRecursive(InPin, false, false, true); } } if(bSetupOrphanPins) { Links.Append(InPin->GetSourceLinks(true)); Links.Append(InPin->GetTargetLinks(true)); if (Links.Num() > 0) { DetachLinksFromPinObjects(&Links); const FString OrphanedName = FString::Printf(TEXT("%s%s"), *URigVMPin::OrphanPinPrefix, *InPin->GetName()); if(InPin->GetNode()->FindPin(OrphanedName) == nullptr) { URigVMPin* OrphanedPin = NewObject(InPin->GetNode(), *OrphanedName); ConfigurePinFromPin(OrphanedPin, InPin); OrphanedPin->DisplayName = InPin->GetFName(); if(OrphanedPin->IsStruct()) { AddPinsForStruct(OrphanedPin->GetScriptStruct(), OrphanedPin->GetNode(), OrphanedPin, OrphanedPin->Direction, OrphanedPin->GetDefaultValue(), false); } InPin->GetNode()->OrphanedPins.Add(OrphanedPin); } } } if(bRemoveSubPins || !InPin->IsArray()) { TArray Pins = InPin->SubPins; for (URigVMPin* Pin : Pins) { RemovePin(Pin, bSetupUndoRedo); } InPin->SubPins.Reset(); } if (bSetupUndoRedo) { ActionStack->AddAction(FRigVMChangePinTypeAction(InPin, TypeIndex, bSetupOrphanPins, bBreakLinks, bRemoveSubPins)); } // compute the number of remaining wildcard pins auto WildCardPinCountPredicate = [](const URigVMPin* Pin) { return Pin->IsWildCard(); }; TArray AllPins = InPin->GetNode()->GetAllPinsRecursively(); int32 RemainingWildCardPins = Algo::CountIf(AllPins, WildCardPinCountPredicate); const bool bPinWasWildCard = InPin->IsWildCard(); const FPinState PreviousPinState = GetPinState(InPin); const FString PreviousCPPType = InPin->CPPType; const FRigVMTemplateArgumentType Type = FRigVMRegistry::Get().GetType(TypeIndex); InPin->CPPType = Type.CPPType.ToString(); InPin->CPPTypeObjectPath = Type.GetCPPTypeObjectPath(); InPin->CPPTypeObject = Type.CPPTypeObject; InPin->bIsDynamicArray = FRigVMRegistry::Get().IsArrayType(TypeIndex); if (bInitializeDefaultValue) { InPin->DefaultValue = FString(); if(InPin->IsRootPin() && !InPin->IsWildCard()) { if(URigVMTemplateNode* TemplateNode = Cast(InPin->GetNode())) { InPin->DefaultValue = TemplateNode->GetInitialDefaultValueForPin(InPin->GetFName()); } } } if (InPin->IsExecuteContext() && !InPin->GetNode()->IsA() && !InPin->GetNode()->IsA()) { InPin->Direction = ERigVMPinDirection::IO; } if (InPin->IsStruct() && !InPin->IsArray()) { FString DefaultValue = InPin->DefaultValue; CreateDefaultValueForStructIfRequired(InPin->GetScriptStruct(), DefaultValue); AddPinsForStruct(InPin->GetScriptStruct(), InPin->GetNode(), InPin, InPin->Direction, DefaultValue, false); } if (InPin->IsArray()) { const TRigVMTypeIndex BaseTypeIndex = FRigVMRegistry::Get().GetBaseTypeFromArrayTypeIndex(TypeIndex); for (int32 i=0; iGetSubPins().Num(); ++i) { URigVMPin* SubPin = InPin->GetSubPins()[i]; if (SubPin->GetDirection() == ERigVMPinDirection::Hidden) { continue; } ChangePinType(SubPin, BaseTypeIndex, bSetupUndoRedo, bSetupOrphanPins, bBreakLinks, bRemoveSubPins, bInitializeDefaultValue); } } // if the pin didn't change type - let's maintain the pin state if(PreviousCPPType == InPin->CPPType && !InPin->IsWildCard()) { ApplyPinState(InPin, PreviousPinState, false); } // if this is a template clear its caches if(URigVMTemplateNode* TemplateNode = Cast(InPin->GetNode())) { TemplateNode->InvalidateCache(); } Notify(ERigVMGraphNotifType::PinTypeChanged, InPin); Notify(ERigVMGraphNotifType::PinDefaultValueChanged, InPin); // let's see if this was the last resolved wildcard pin if(RemainingWildCardPins > 0) { // compute the number of current wildcard pins RemainingWildCardPins = 0; if(InPin->GetNode()->IsA()) { AllPins = InPin->GetNode()->GetAllPinsRecursively(); RemainingWildCardPins = Algo::CountIf(AllPins, WildCardPinCountPredicate); } // if this is the first time that there are no wild card pins left if(RemainingWildCardPins == 0) { struct Local { static bool IsPinDefaultEmpty(URigVMPin* InPin) { const FString DefaultValue = InPin->GetDefaultValue(); static const FString EmptyBraces = TEXT("()"); return DefaultValue.IsEmpty() || DefaultValue == EmptyBraces; } static void ApplyResolvedDefaultValue( URigVMController* InController, URigVMPin* InPin, const FString& RemainingPinPath, const FString& InDefaultValue, bool bSetupUndoRedo) { if(InDefaultValue.IsEmpty()) { return; } if(RemainingPinPath.IsEmpty()) { InController->SetPinDefaultValue(InPin, InDefaultValue, true, bSetupUndoRedo, false); return; } FString PinName; FString SubPinPath; if(!URigVMPin::SplitPinPathAtStart(RemainingPinPath, PinName, SubPinPath)) { PinName = RemainingPinPath; SubPinPath.Empty(); } TArray MemberValuePairs = URigVMPin::SplitDefaultValue(InDefaultValue); for (const FString& MemberValuePair : MemberValuePairs) { FString MemberName, MemberValue; if (MemberValuePair.Split(TEXT("="), &MemberName, &MemberValue)) { if(MemberName.Equals(PinName)) { PostProcessDefaultValue(InPin, MemberValue); ApplyResolvedDefaultValue(InController, InPin, SubPinPath, MemberValue, bSetupUndoRedo); break; } } } } }; for(URigVMPin* Pin : AllPins) { // skip struct pins or array pins if(Pin->GetSubPins().Num() > 0) { continue; } if(!Local::IsPinDefaultEmpty(Pin)) { continue; } if(URigVMUnitNode* UnitNode = Cast(Pin->GetNode())) { if(UnitNode->GetScriptStruct()) { TSharedPtr StructOnScope = UnitNode->ConstructStructInstance(true); const FString StructDefaultValue = FRigVMStruct::ExportToFullyQualifiedText(UnitNode->GetScriptStruct(), StructOnScope->GetStructMemory()); Local::ApplyResolvedDefaultValue(this, Pin, Pin->GetSegmentPath(true), StructDefaultValue, bSetupUndoRedo); if(!Local::IsPinDefaultEmpty(Pin)) { continue; } } } // create the default value for the parent struct pin if(Pin->IsStructMember()) { const URigVMPin* ParentPin = Pin->GetParentPin(); TSharedPtr StructOnScope = MakeShareable(new FStructOnScope(ParentPin->GetScriptStruct())); const FString StructDefaultValue = FRigVMStruct::ExportToFullyQualifiedText(ParentPin->GetScriptStruct(), StructOnScope->GetStructMemory()); Local::ApplyResolvedDefaultValue(this, Pin, Pin->GetName(), StructDefaultValue, bSetupUndoRedo); } else { // plain types within an array or at the root FString SimpleTypeDefaultValue; if(Pin->GetCPPType() == RigVMTypeUtils::BoolType) { static const FString BoolDefaultValue = TEXT("False"); SimpleTypeDefaultValue = BoolDefaultValue; } else if(Pin->GetCPPType() == RigVMTypeUtils::FloatType || Pin->GetCPPType() == RigVMTypeUtils::DoubleType) { static const FString FloatingPointDefaultValue = TEXT("0.000000"); SimpleTypeDefaultValue = FloatingPointDefaultValue; } else if(Pin->GetCPPType() == RigVMTypeUtils::Int32Type) { static const FString IntegerDefaultValue = TEXT("0"); SimpleTypeDefaultValue = IntegerDefaultValue; } Local::ApplyResolvedDefaultValue(this, Pin, FString(), SimpleTypeDefaultValue, bSetupUndoRedo); } } if (URigVMTemplateNode* TemplateNode = Cast(InPin->GetNode())) { if (!TemplateNode->IsA() && !TemplateNode->IsA()) { // Figure out the permutation from the pin types. During undo, the filtered permutations are not // reliable as to which permutation we are resolving to. FullyResolveTemplateNode(TemplateNode, INDEX_NONE, bSetupUndoRedo); } } } } // since the resolved pin may affect the node title we need to let // graph views know to invalidate the node title text widget Notify(ERigVMGraphNotifType::NodeDescriptionChanged, InPin->GetNode()); // in cases where we are just changing the type we have to let the // clients know that the links are still there if(!bSetupOrphanPins && !bBreakLinks && !bRemoveSubPins) { const TArray CurrentLinks = InPin->GetLinks(); for(URigVMLink* CurrentLink : CurrentLinks) { Notify(ERigVMGraphNotifType::LinkRemoved, CurrentLink); Notify(ERigVMGraphNotifType::LinkAdded, CurrentLink); } } if (bSetupUndoRedo) { ActionStack->EndAction(Action); } if(Links.Num() > 0) { ReattachLinksToPinObjects(false, &Links, true); RemoveUnusedOrphanedPins(InPin->GetNode()); } return true; } #if WITH_EDITOR void URigVMController::RewireLinks(URigVMPin* InOldPin, URigVMPin* InNewPin, bool bAsInput, bool bSetupUndoRedo, TArray InLinks) { ensure(InOldPin->GetRootPin() == InOldPin); ensure(InNewPin->GetRootPin() == InNewPin); FRigVMControllerCompileBracketScope CompileScope(this); if (bAsInput) { TArray Links = InLinks; if (Links.Num() == 0) { Links = InOldPin->GetSourceLinks(true /* recursive */); } for (URigVMLink* Link : Links) { FString SegmentPath = Link->GetTargetPin()->GetSegmentPath(); URigVMPin* NewPin = SegmentPath.IsEmpty() ? InNewPin : InNewPin->FindSubPin(SegmentPath); check(NewPin); BreakLink(Link->GetSourcePin(), Link->GetTargetPin(), bSetupUndoRedo); AddLink(Link->GetSourcePin(), NewPin, bSetupUndoRedo); } } else { TArray Links = InLinks; if (Links.Num() == 0) { Links = InOldPin->GetTargetLinks(true /* recursive */); } for (URigVMLink* Link : Links) { FString SegmentPath = Link->GetSourcePin()->GetSegmentPath(); URigVMPin* NewPin = SegmentPath.IsEmpty() ? InNewPin : InNewPin->FindSubPin(SegmentPath); check(NewPin); BreakLink(Link->GetSourcePin(), Link->GetTargetPin(), bSetupUndoRedo); AddLink(NewPin, Link->GetTargetPin(), bSetupUndoRedo); } } } #endif bool URigVMController::RenameObject(UObject* InObjectToRename, const TCHAR* InNewName, UObject* InNewOuter) { return InObjectToRename->Rename(InNewName, InNewOuter, REN_ForceNoResetLoaders | REN_DoNotDirty | REN_DontCreateRedirectors | REN_NonTransactional); } void URigVMController::DestroyObject(UObject* InObjectToDestroy) { RenameObject(InObjectToDestroy, nullptr, GetTransientPackage()); InObjectToDestroy->RemoveFromRoot(); InObjectToDestroy->MarkAsGarbage(); } URigVMPin* URigVMController::MakeExecutePin(URigVMNode* InNode, const FName& InName) { URigVMPin* ExecutePin = NewObject(InNode, InName); ExecutePin->DisplayName = FRigVMStruct::ExecuteName; MakeExecutePin(ExecutePin); return ExecutePin; } void URigVMController::MakeExecutePin(URigVMPin* InOutPin) { if(InOutPin->CPPTypeObject != FRigVMExecuteContext::StaticStruct()) { const bool bIsArray = InOutPin->IsArray(); InOutPin->CPPType = FRigVMExecuteContext::StaticStruct()->GetStructCPPName(); InOutPin->CPPTypeObject = FRigVMExecuteContext::StaticStruct(); InOutPin->CPPTypeObjectPath = *InOutPin->CPPTypeObject->GetPathName(); if(bIsArray) { InOutPin->CPPType = RigVMTypeUtils::ArrayTypeFromBaseType(InOutPin->CPPType); InOutPin->LastKnownTypeIndex = FRigVMRegistry::Get().GetArrayTypeFromBaseTypeIndex(RigVMTypeUtils::TypeIndex::Execute); } else { InOutPin->LastKnownTypeIndex = RigVMTypeUtils::TypeIndex::Execute; } InOutPin->LastKnownCPPType = InOutPin->CPPType; } } void URigVMController::AddNodePin(URigVMNode* InNode, URigVMPin* InPin) { ValidatePin(InPin); check(!InNode->Pins.Contains(InPin)); InNode->Pins.Add(InPin); } void URigVMController::AddSubPin(URigVMPin* InParentPin, URigVMPin* InPin) { ValidatePin(InPin); check(!InParentPin->SubPins.Contains(InPin)); InParentPin->SubPins.Add(InPin); } bool URigVMController::EnsurePinValidity(URigVMPin* InPin, bool bRecursive) { check(InPin); // check if the CPPTypeObject is set up correctly. if(RigVMTypeUtils::RequiresCPPTypeObject(InPin->GetCPPType())) { // GetCPPTypeObject attempts to update pin type information to the latest // without testing for redirector if(InPin->GetCPPTypeObject() == nullptr) { FString CPPType = InPin->GetCPPType(); InPin->CPPTypeObject = RigVMTypeUtils::ObjectFromCPPType(CPPType); InPin->CPPType = CPPType; } else { InPin->CPPType = RigVMTypeUtils::PostProcessCPPType(InPin->CPPType, InPin->GetCPPTypeObject()); } } if (InPin->GetCPPType().IsEmpty() || InPin->GetCPPType() == FName().ToString()) { return false; } if(bRecursive) { for(URigVMPin* SubPin : InPin->SubPins) { if(!EnsurePinValidity(SubPin, bRecursive)) { return false; } } } return true; } void URigVMController::ValidatePin(URigVMPin* InPin) { check(InPin); // create a property description from the pin here as a test, // since the compiler needs this FRigVMPropertyDescription(InPin->GetFName(), InPin->GetCPPType(), InPin->GetCPPTypeObject(), InPin->GetDefaultValue()); if(InPin->IsExecuteContext()) { ensure(InPin->GetCPPTypeObject() == FRigVMExecuteContext::StaticStruct()); } } void URigVMController::EnsureLocalVariableValidity() { if (URigVMGraph* Graph = GetGraph()) { for (FRigVMGraphVariableDescription& Variable : Graph->LocalVariables) { // CPPType can become invalid when the type object is defined by // an asset that have changed name or asset path, user defined struct is one possibility Variable.CPPType = RigVMTypeUtils::PostProcessCPPType(Variable.CPPType, Variable.CPPTypeObject); } } } FRigVMExternalVariable URigVMController::GetVariableByName(const FName& InExternalVariableName, const bool bIncludeInputArguments) { TArray Variables = GetAllVariables(bIncludeInputArguments); for (const FRigVMExternalVariable& Variable : Variables) { if (Variable.Name == InExternalVariableName) { return Variable; } } return FRigVMExternalVariable(); } TArray URigVMController::GetAllVariables(const bool bIncludeInputArguments) { TArray ExternalVariables; if(URigVMGraph* Graph = GetGraph()) { for (FRigVMGraphVariableDescription LocalVariable : Graph->GetLocalVariables(bIncludeInputArguments)) { ExternalVariables.Add(LocalVariable.ToExternalVariable()); } } if (GetExternalVariablesDelegate.IsBound()) { ExternalVariables.Append(GetExternalVariablesDelegate.Execute(GetGraph())); } return ExternalVariables; } const FRigVMByteCode* URigVMController::GetCurrentByteCode() const { if (GetCurrentByteCodeDelegate.IsBound()) { return GetCurrentByteCodeDelegate.Execute(); } return nullptr; } void URigVMController::RefreshFunctionReferences(URigVMLibraryNode* InFunctionDefinition, bool bSetupUndoRedo) { check(InFunctionDefinition); if (URigVMFunctionLibrary* FunctionLibrary = Cast(InFunctionDefinition->GetGraph())) { FunctionLibrary->ForEachReference(InFunctionDefinition->GetFName(), [this, bSetupUndoRedo](URigVMFunctionReferenceNode* ReferenceNode) { FRigVMControllerGraphGuard GraphGuard(this, ReferenceNode->GetGraph(), bSetupUndoRedo); RepopulatePinsOnNode(ReferenceNode, false, false, true); }); } } FString URigVMController::GetGraphOuterName() const { check(GetGraph() != nullptr); return GetSanitizedName(GetGraph()->GetRootGraph()->GetOuter()->GetFName().ToString(), true, false); } FString URigVMController::GetSanitizedName(const FString& InName, bool bAllowPeriod, bool bAllowSpace) { FString CopiedName = InName; SanitizeName(CopiedName, bAllowPeriod, bAllowSpace); return CopiedName; } FString URigVMController::GetSanitizedGraphName(const FString& InName) { return GetSanitizedName(InName, true, true); } FString URigVMController::GetSanitizedNodeName(const FString& InName) { return GetSanitizedName(InName, false, true); } FString URigVMController::GetSanitizedVariableName(const FString& InName) { return GetSanitizedName(InName, false, true); } FString URigVMController::GetSanitizedPinName(const FString& InName) { return GetSanitizedName(InName, false, true); } FString URigVMController::GetSanitizedPinPath(const FString& InName) { return GetSanitizedName(InName, true, true); } void URigVMController::SanitizeName(FString& InOutName, bool bAllowPeriod, bool bAllowSpace) { // Sanitize the name for (int32 i = 0; i < InOutName.Len(); ++i) { TCHAR& C = InOutName[i]; const bool bGoodChar = FChar::IsAlpha(C) || // Any letter (upper and lowercase) anytime (C == '_') || (C == '-') || // _ and - anytime (bAllowPeriod && (C == '.')) || (bAllowSpace && (C == ' ')) || ((i > 0) && FChar::IsDigit(C)); // 0-9 after the first character if (!bGoodChar) { C = '_'; } } if (InOutName.Len() > GetMaxNameLength()) { InOutName.LeftChopInline(InOutName.Len() - GetMaxNameLength()); } } TArray> URigVMController::GetLinkedPinPaths(URigVMNode* InNode, bool bIncludeInjectionNodes) { const TArray Nodes = {InNode}; return GetLinkedPinPaths(Nodes, bIncludeInjectionNodes); } TArray> URigVMController::GetLinkedPinPaths(const TArray& InNodes, bool bIncludeInjectionNodes) { TArray> LinkedPaths; for(URigVMNode* Node : InNodes) { TArray Links = Node->GetLinks(); for(URigVMLink* Link : Links) { if(!bIncludeInjectionNodes) { if(Link->GetSourcePin()->GetNode()->IsInjected() || Link->GetTargetPin()->GetNode()->IsInjected()) { continue; } } TPair LinkedPath(Link->GetSourcePin()->GetPinPath(), Link->GetTargetPin()->GetPinPath()); LinkedPaths.AddUnique(LinkedPath); } } return LinkedPaths; } bool URigVMController::BreakLinkedPaths(const TArray>& InLinkedPaths, bool bSetupUndoRedo) { for(const TPair& LinkedPath : InLinkedPaths) { if(!BreakLink(LinkedPath.Key, LinkedPath.Value, bSetupUndoRedo)) { ReportErrorf(TEXT("Couldn't remove link '%s' -> '%s'"), *LinkedPath.Key, *LinkedPath.Value); return false; } } return true; } bool URigVMController::RestoreLinkedPaths( const TArray>& InLinkedPaths, const TMap& InNodeNameMap, const TMap& InRemapDelegates, FRigVMController_CheckPinComatibilityDelegate InCompatibilityDelegate, bool bSetupUndoRedo, ERigVMPinDirection InUserDirection) { bool bSuccess = true; auto RemapNodeName = [InNodeNameMap, InRemapDelegates](const FString& InPinPath, bool bAsInput) -> FString { FString NodeName, SegmentPath; if(!URigVMPin::SplitPinPathAtStart(InPinPath, NodeName, SegmentPath)) { return InPinPath; } FString PinPath = InPinPath; if(const FRigVMController_PinPathRemapDelegate* RemapDelegate = InRemapDelegates.Find(NodeName)) { PinPath = RemapDelegate->Execute(PinPath, bAsInput); } else if(const FString* RemappedNodeName = InNodeNameMap.Find(NodeName)) { PinPath = URigVMPin::JoinPinPath(*RemappedNodeName, SegmentPath); } return PinPath; }; for(const TPair& LinkedPath : InLinkedPaths) { const FString SourcePath = RemapNodeName(LinkedPath.Key, false); const FString TargetPath = RemapNodeName(LinkedPath.Value, true); URigVMPin* SourcePin = GetGraph()->FindPin(SourcePath); URigVMPin* TargetPin = GetGraph()->FindPin(TargetPath); if(SourcePin == nullptr || TargetPin == nullptr) { ReportRemovedLink(SourcePath, TargetPath); bSuccess = false; continue; } if(InCompatibilityDelegate.IsBound()) { if(!InCompatibilityDelegate.Execute(SourcePin, TargetPin)) { bSuccess = false; continue; } } // it's ok if this fails - we want to maintain the minimum set of links if(!AddLink(SourcePin, TargetPin, bSetupUndoRedo, InUserDirection)) { ReportRemovedLink(SourcePath, TargetPath); bSuccess = false; } } return bSuccess; } #if WITH_EDITOR void URigVMController::RegisterUseOfTemplate(const URigVMTemplateNode* InNode) { check(InNode); if(!bRegisterTemplateNodeUsage) { return; } const FRigVMTemplate* Template = InNode->GetTemplate(); if(Template == nullptr) { return; } if(!InNode->IsResolved()) { return; } const int32& ResolvedPermutation = InNode->ResolvedPermutation; if(!ensure(ResolvedPermutation != INDEX_NONE)) { return; } URigVMControllerSettings* Settings = GetMutableDefault(); Settings->Modify(); const FName& Notation = Template->GetNotation(); FRigVMController_CommonTypePerTemplate& TypesForTemplate = Settings->TemplateDefaultTypes.FindOrAdd(Notation); const FString TypesString = FRigVMTemplate::GetStringFromArgumentTypes(Template->GetTypesForPermutation(ResolvedPermutation)); int32& Count = TypesForTemplate.Counts.FindOrAdd(TypesString); Count++; } FRigVMTemplate::FTypeMap URigVMController::GetCommonlyUsedTypesForTemplate( const URigVMTemplateNode* InNode) const { static FRigVMTemplate::FTypeMap EmptyTypes; const URigVMControllerSettings* Settings = GetDefault(); if(!Settings->bAutoResolveTemplateNodesWhenLinkingExecute) { return EmptyTypes; } const FRigVMTemplate* Template = InNode->GetTemplate(); if(Template == nullptr) { return EmptyTypes; } const FName& Notation = Template->GetNotation(); const FRigVMController_CommonTypePerTemplate* TypesForTemplate = Settings->TemplateDefaultTypes.Find(Notation); if(TypesForTemplate == nullptr) { return EmptyTypes; } if(TypesForTemplate->Counts.IsEmpty()) { return EmptyTypes; } TPair MaxPair; for(const TPair& Pair : TypesForTemplate->Counts) { if(Pair.Value > MaxPair.Value) { MaxPair = Pair; } } const FString& TypesString = MaxPair.Key; return Template->GetArgumentTypesFromString(TypesString); } FRigVMClientPatchResult URigVMController::PatchUnitNodesOnLoad() { FRigVMClientPatchResult Result; if (const URigVMGraph* Graph = GetGraph()) { TArray UnitNodesToTurnToDispatches; // check for unit nodes that should be dispatches for(URigVMNode* Node : Graph->GetNodes()) { if(URigVMUnitNode* UnitNode = Cast(Node)) { if(const FRigVMTemplate* Template = UnitNode->GetTemplate()) { if(Template->GetDispatchFactory() != nullptr) { UnitNodesToTurnToDispatches.Add(UnitNode); } } } } // convert unit nodes to dispatches for(URigVMUnitNode* UnitNode : UnitNodesToTurnToDispatches) { TArray> LinkedPaths = GetLinkedPinPaths(UnitNode); const FVector2D NodePosition = UnitNode->GetPosition(); const FString NodeName = UnitNode->GetName(); TMap PinStates = GetPinStates(UnitNode, false); FRigVMTemplate::FTypeMap TypeMap; for(URigVMPin* Pin : UnitNode->GetPins()) { if(Pin->GetDirection() == ERigVMPinDirection::Input || Pin->GetDirection() == ERigVMPinDirection::Visible || Pin->GetDirection() == ERigVMPinDirection::IO || Pin->GetDirection() == ERigVMPinDirection::Output) { TypeMap.Add(Pin->GetFName(), Pin->GetTypeIndex()); } } const FRigVMTemplate* Template = UnitNode->GetTemplate(); Result.RemovedNodes.Add(UnitNode->GetPathName()); Result.bChangedContent = true; RemoveNode(UnitNode, false, false, false, false); URigVMTemplateNode* NewNode = AddTemplateNode( Template->GetNotation(), NodePosition, NodeName, false, false); Result.AddedNodes.Add(NewNode); TArray Permutations; Template->Resolve(TypeMap, Permutations, false); for(URigVMPin* Pin : NewNode->GetPins()) { if(Pin->IsWildCard()) { if(const TRigVMTypeIndex* ResolvedTypeIndex = TypeMap.Find(Pin->GetFName())) { if(!FRigVMRegistry::Get().IsWildCardType(*ResolvedTypeIndex)) { ChangePinType(Pin, *ResolvedTypeIndex, false, false); } } } } ApplyPinStates(NewNode, PinStates, {}, false); RestoreLinkedPaths(LinkedPaths, {}, {}, false); } } return Result; } FRigVMClientPatchResult URigVMController::PatchDispatchNodesOnLoad() { FRigVMClientPatchResult Result; if (const URigVMGraph* Graph = GetGraph()) { for(URigVMNode* Node : Graph->GetNodes()) { if(URigVMDispatchNode* DispatchNode = Cast(Node)) { // find template performs backwards lookup if(const FRigVMTemplate* Template = DispatchNode->GetTemplate()) { if(Template->GetNotation() != DispatchNode->TemplateNotation) { Result.bChangedContent = Result.bChangedContent || DispatchNode->TemplateNotation != Template->GetNotation(); DispatchNode->TemplateNotation = Template->GetNotation(); if(!DispatchNode->ResolvedFunctionName.IsEmpty()) { FString FactoryName, ArgumentsString; if(DispatchNode->ResolvedFunctionName.Split(TEXT("::"), &FactoryName, &ArgumentsString)) { DispatchNode->ResolvedFunctionName.Reset(); DispatchNode->ResolvedPermutation = INDEX_NONE; const FRigVMTemplateTypeMap ArgumentTypes = Template->GetArgumentTypesFromString(ArgumentsString); if(ArgumentTypes.Num() == Template->NumArguments()) { if(const FRigVMDispatchFactory* Factory = Template->GetDispatchFactory()) { const FString ResolvedPermutationName = Factory->GetPermutationName(ArgumentTypes); if(const FRigVMFunction* Function = FRigVMRegistry::Get().FindFunction(*ResolvedPermutationName)) { DispatchNode->ResolvedFunctionName = Function->GetName(); DispatchNode->ResolvedPermutation = Template->FindPermutation(Function); Result.bChangedContent = true; } } } } } } } } } } return Result; } FRigVMClientPatchResult URigVMController::PatchBranchNodesOnLoad() { FRigVMClientPatchResult Result; if (const URigVMGraph* Graph = GetGraph()) { TArray BranchNodes = Graph->GetNodes().FilterByPredicate([](URigVMNode* Node) { return Node->IsA(); }); for(URigVMNode* BranchNode : BranchNodes) { TArray> LinkedPaths = GetLinkedPinPaths(BranchNode); const FVector2D NodePosition = BranchNode->GetPosition(); const FString NodeName = BranchNode->GetName(); const URigVMPin* OldConditionPin = BranchNode->FindPin(GET_MEMBER_NAME_CHECKED(FRigVMFunction_ControlFlowBranch, Condition).ToString()); const FString ConditionDefault = GetPinDefaultValue(OldConditionPin->GetPinPath()); Result.RemovedNodes.Add(BranchNode->GetPathName()); Result.bChangedContent = true; RemoveNode(BranchNode, false, true, false, false); const URigVMNode* NewNode = AddUnitNode(FRigVMFunction_ControlFlowBranch::StaticStruct(), FRigVMStruct::ExecuteName, NodePosition, NodeName, false, false); Result.AddedNodes.Add(NewNode); if(!ConditionDefault.IsEmpty()) { const URigVMPin* ConditionPin = NewNode->FindPin(GET_MEMBER_NAME_CHECKED(FRigVMFunction_ControlFlowBranch, Condition).ToString()); SetPinDefaultValue(ConditionPin->GetPinPath(), ConditionDefault, false, false, false, false); } RestoreLinkedPaths(LinkedPaths, {}, {}, false); } } return Result; } FRigVMClientPatchResult URigVMController::PatchIfSelectNodesOnLoad() { FRigVMClientPatchResult Result; if (const URigVMGraph* Graph = GetGraph()) { TArray IfOrSelectNodes = Graph->GetNodes().FilterByPredicate([](URigVMNode* Node) { return Node->IsA() || Node->IsA(); }); for(URigVMNode* IfOrSelectNode : IfOrSelectNodes) { const bool bIsIfNode = IfOrSelectNode->IsA(); TArray> LinkedPaths = GetLinkedPinPaths(IfOrSelectNode); const FVector2D NodePosition = IfOrSelectNode->GetPosition(); const FString NodeName = IfOrSelectNode->GetName(); const TRigVMTypeIndex TypeIndex = IfOrSelectNode->GetPins().Last()->GetTypeIndex(); TMap PinStates = GetPinStates(IfOrSelectNode, true); Result.RemovedNodes.Add(IfOrSelectNode->GetPathName()); Result.bChangedContent = true; RemoveNode(IfOrSelectNode, false, true, false, false); const FRigVMDispatchFactory* Factory = FRigVMRegistry::Get().FindOrAddDispatchFactory( bIsIfNode ? FRigVMDispatch_If::StaticStruct() : FRigVMDispatch_SelectInt32::StaticStruct()); const FRigVMTemplate* Template = Factory->GetTemplate(); URigVMTemplateNode* NewNode = AddTemplateNode( Template->GetNotation(), NodePosition, NodeName, false, false); Result.AddedNodes.Add(NewNode); if(!FRigVMRegistry::Get().IsWildCardType(TypeIndex)) { TArray Permutations; FRigVMTemplateTypeMap Types; Types.Add(IfOrSelectNode->GetPins().Last()->GetFName(), TypeIndex); Template->Resolve(Types, Permutations, false); for(URigVMPin* Pin : NewNode->GetPins()) { if(Pin->IsWildCard()) { if(const TRigVMTypeIndex* ResolvedTypeIndex = Types.Find(Pin->GetFName())) { if(!FRigVMRegistry::Get().IsWildCardType(*ResolvedTypeIndex)) { ResolveWildCardPin(Pin, *ResolvedTypeIndex, false); } } } } } ApplyPinStates(NewNode, PinStates, {}, false); RestoreLinkedPaths(LinkedPaths, {}, {}, false); } } return Result; } FRigVMClientPatchResult URigVMController::PatchArrayNodesOnLoad() { FRigVMClientPatchResult Result; if (const URigVMGraph* Graph = GetGraph()) { TArray ArrayNodes = Graph->GetNodes().FilterByPredicate([](URigVMNode* Node) { return Node->IsA(); }); for(URigVMNode* ModelNode : ArrayNodes) { UDEPRECATED_RigVMArrayNode* ArrayNode = CastChecked(ModelNode); TArray> LinkedPaths = GetLinkedPinPaths(ArrayNode); const FVector2D NodePosition = ArrayNode->GetPosition(); const FString NodeName = ArrayNode->GetName(); const FString CPPType = ArrayNode->GetCPPType(); UObject* CPPTypeObject = ArrayNode->GetCPPTypeObject(); const ERigVMOpCode OpCode = ArrayNode->GetOpCode(); TMap PinStates = GetPinStates(ArrayNode, true); Result.RemovedNodes.Add(ArrayNode->GetPathName()); Result.bChangedContent = true; RemoveNode(ArrayNode, false, false, false, false); URigVMNode* NewNode = AddArrayNode(OpCode, CPPType, CPPTypeObject, NodePosition, NodeName, false, false, true); Result.AddedNodes.Add(NewNode); ApplyPinStates(NewNode, PinStates, {}, false); RestoreLinkedPaths(LinkedPaths, {}, {}, false); } } return Result; } FRigVMClientPatchResult URigVMController::PatchInvalidLinksOnWildcards() { FRigVMClientPatchResult Result; if (const URigVMGraph* Graph = GetGraph()) { // Remove links between wildcard pins TArray LinksToRemove; for (URigVMLink* Link : Graph->GetLinks()) { bool bRemove = false; if (URigVMPin* SourcePin = Link->GetSourcePin()) { if (SourcePin->IsWildCard()) { bRemove = true; } } if (URigVMPin* TargetPin = Link->GetTargetPin()) { if (TargetPin->IsWildCard()) { bRemove = true; } } if (bRemove) { LinksToRemove.Add(Link); } } if (!LinksToRemove.IsEmpty()) { Result.bChangedContent = true; } for (URigVMLink* Link : LinksToRemove) { if (!BreakLink(Link->GetSourcePin(), Link->GetTargetPin())) { Result.ErrorMessages.Add(FString::Printf(TEXT("Error breaking link %s in PatchInvalidLinksOnWildcards"), *Link->GetPinPathRepresentation())); Result.bSucceeded = false; } } // Remove exposed pins of type wildcard if (URigVMCollapseNode* CollapseNode = Cast(Graph->GetOuter())) { TArray ExposedPins = CollapseNode->GetPins(); for (URigVMPin* ExposedPin : ExposedPins) { if (ExposedPin->IsWildCard()) { FRigVMControllerGraphGuard GraphGuard(this, CollapseNode->GetContainedGraph()); if (!RemoveExposedPin(ExposedPin->GetFName(), false)) { Result.ErrorMessages.Add(FString::Printf(TEXT("Error removing exposed pin %s PatchInvalidLinksOnWildcards"), *ExposedPin->GetPinPath(true))); Result.bSucceeded = false; } } } } } return Result; } void URigVMController::PostDuplicateHost(const FString& InOldPathName, const FString& InNewPathName) { if (const URigVMGraph* Graph = GetGraph()) { TArray FunctionRefNodes = Graph->GetNodes().FilterByPredicate([](URigVMNode* Node) { return Node->IsA(); }); for(URigVMNode* Node : FunctionRefNodes) { URigVMFunctionReferenceNode* FunctionReferenceNode = CastChecked(Node); FunctionReferenceNode->ReferencedFunctionHeader.PostDuplicateHost(InOldPathName, InNewPathName); } } } #endif URigVMControllerSettings::URigVMControllerSettings(const FObjectInitializer& Initializer) : Super(Initializer) { bAutoResolveTemplateNodesWhenLinkingExecute = true; }