diff --git a/Plugins/DirectiveUtilities/Config/DefaultDirectiveUtilities.ini b/Plugins/DirectiveUtilities/Config/DefaultDirectiveUtilities.ini
new file mode 100644
index 00000000..3d2b200c
--- /dev/null
+++ b/Plugins/DirectiveUtilities/Config/DefaultDirectiveUtilities.ini
@@ -0,0 +1,31 @@
+[CoreRedirects]
+; Redirects Blueprint assets saved with the 1.x class, struct, enum, and package names.
++PackageRedirects=(OldName="/Script/UDCore",NewName="/Script/DirectiveUtilitiesRuntime")
++PackageRedirects=(OldName="/Script/UDCoreEditor",NewName="/Script/DirectiveUtilitiesEditor")
++ClassRedirects=(OldName="/Script/UDCore.UDCoreArrayFunctionLibrary",NewName="/Script/DirectiveUtilitiesRuntime.DirectiveUtilArrayFunctionLibrary")
++ClassRedirects=(OldName="/Script/UDCore.UDCoreFunctionLibrary",NewName="/Script/DirectiveUtilitiesRuntime.DirectiveUtilFunctionLibrary")
++ClassRedirects=(OldName="/Script/UDCore.UDCoreGameplayTagFunctionLibrary",NewName="/Script/DirectiveUtilitiesRuntime.DirectiveUtilGameplayTagFunctionLibrary")
++ClassRedirects=(OldName="/Script/UDCore.UDCoreInputFunctionLibrary",NewName="/Script/DirectiveUtilitiesRuntime.DirectiveUtilInputFunctionLibrary")
++ClassRedirects=(OldName="/Script/UDCore.UDCoreMapFunctionLibrary",NewName="/Script/DirectiveUtilitiesRuntime.DirectiveUtilMapFunctionLibrary")
++ClassRedirects=(OldName="/Script/UDCore.UDCoreMathFunctionLibrary",NewName="/Script/DirectiveUtilitiesRuntime.DirectiveUtilMathFunctionLibrary")
++ClassRedirects=(OldName="/Script/UDCore.UDCoreRegexFunctionLibrary",NewName="/Script/DirectiveUtilitiesRuntime.DirectiveUtilRegexFunctionLibrary")
++ClassRedirects=(OldName="/Script/UDCore.UDCoreSaveGameFunctionLibrary",NewName="/Script/DirectiveUtilitiesRuntime.DirectiveUtilSaveGameFunctionLibrary")
++ClassRedirects=(OldName="/Script/UDCore.UDCoreStringFunctionLibrary",NewName="/Script/DirectiveUtilitiesRuntime.DirectiveUtilStringFunctionLibrary")
++ClassRedirects=(OldName="/Script/UDCore.UDCoreTextFunctionLibrary",NewName="/Script/DirectiveUtilitiesRuntime.DirectiveUtilTextFunctionLibrary")
++ClassRedirects=(OldName="/Script/UDCore.UDAT_Delay",NewName="/Script/DirectiveUtilitiesRuntime.DirectiveUtilTask_Delay")
++ClassRedirects=(OldName="/Script/UDCore.UDAT_AsyncLoadAsset",NewName="/Script/DirectiveUtilitiesRuntime.DirectiveUtilTask_AsyncLoadAsset")
++ClassRedirects=(OldName="/Script/UDCore.UDAT_AsyncLoadClass",NewName="/Script/DirectiveUtilitiesRuntime.DirectiveUtilTask_AsyncLoadClass")
++ClassRedirects=(OldName="/Script/UDCore.UDAT_AsyncLoadAssets",NewName="/Script/DirectiveUtilitiesRuntime.DirectiveUtilTask_AsyncLoadAssets")
++ClassRedirects=(OldName="/Script/UDCore.UDAT_AsyncTrace",NewName="/Script/DirectiveUtilitiesRuntime.DirectiveUtilTask_AsyncTrace")
++ClassRedirects=(OldName="/Script/UDCore.UDAT_MoveToLocation",NewName="/Script/DirectiveUtilitiesRuntime.DirectiveUtilTask_MoveToLocation")
++ClassRedirects=(OldName="/Script/UDCore.UDAT_MoveToActor",NewName="/Script/DirectiveUtilitiesRuntime.DirectiveUtilTask_MoveToActor")
++ClassRedirects=(OldName="/Script/UDCoreEditor.UDCoreEditorActorSubsystem",NewName="/Script/DirectiveUtilitiesEditor.DirectiveUtilEditorActorSubsystem")
++ClassRedirects=(OldName="/Script/UDCoreEditor.UDCoreEditorAssetLibrary",NewName="/Script/DirectiveUtilitiesEditor.DirectiveUtilEditorAssetLibrary")
++StructRedirects=(OldName="/Script/UDCore.UDCoreEnhancedInputContextData",NewName="/Script/DirectiveUtilitiesRuntime.DirectiveUtilEnhancedInputContextData")
++StructRedirects=(OldName="/Script/UDCoreEditor.UDAssetKey",NewName="/Script/DirectiveUtilitiesEditor.DirectiveUtilAssetKey")
++StructRedirects=(OldName="/Script/UDCoreEditor.UDDuplicateAssetData",NewName="/Script/DirectiveUtilitiesEditor.DirectiveUtilDuplicateAssetData")
++EnumRedirects=(OldName="/Script/UDCore.EUDEaseType",NewName="/Script/DirectiveUtilitiesRuntime.EDirectiveUtilEaseType")
++EnumRedirects=(OldName="/Script/UDCore.EUDSuccessStatus",NewName="/Script/DirectiveUtilitiesRuntime.EDirectiveUtilSuccessStatus")
++EnumRedirects=(OldName="/Script/UDCoreEditor.EUDSelectionMethod",NewName="/Script/DirectiveUtilitiesEditor.EDirectiveUtilSelectionMethod")
++EnumRedirects=(OldName="/Script/UDCoreEditor.EUDInclusivity",NewName="/Script/DirectiveUtilitiesEditor.EDirectiveUtilInclusivity")
++EnumRedirects=(OldName="/Script/UDCoreEditor.EUDSearchLocation",NewName="/Script/DirectiveUtilitiesEditor.EDirectiveUtilSearchLocation")
diff --git a/Plugins/DirectiveUtilities/Config/FilterPlugin.ini b/Plugins/DirectiveUtilities/Config/FilterPlugin.ini
new file mode 100644
index 00000000..a83a8150
--- /dev/null
+++ b/Plugins/DirectiveUtilities/Config/FilterPlugin.ini
@@ -0,0 +1,13 @@
+[FilterPlugin]
+; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and
+; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively.
+;
+; Examples:
+; /README.txt
+; /Extras/...
+; /Binaries/ThirdParty/*.dll
+/README.md
+/LICENSE
+/CHANGELOG.md
+/Documentation/...
+/Config/DefaultDirectiveUtilities.ini
diff --git a/Plugins/DirectiveUtilities/DirectiveUtilities.uplugin b/Plugins/DirectiveUtilities/DirectiveUtilities.uplugin
new file mode 100644
index 00000000..36826517
--- /dev/null
+++ b/Plugins/DirectiveUtilities/DirectiveUtilities.uplugin
@@ -0,0 +1,60 @@
+{
+ "FileVersion": 3,
+ "Version": 3,
+ "VersionName": "2.0",
+ "FriendlyName": "Directive Utilities",
+ "Description": "An open-source Unreal Engine plugin that provides runtime and editor utility nodes for developers.",
+ "Category": "Unreal Directive",
+ "CreatedBy": "Unreal Directive",
+ "CreatedByURL": "https://unrealdirective.com",
+ "DocsURL": "https://udcore.unrealdirective.com/",
+ "MarketplaceURL": "https://unrealdirective.com",
+ "SupportURL": "https://github.com/UnrealDirective/DirectiveUtilities/issues",
+ "CanContainContent": false,
+ "IsBetaVersion": false,
+ "IsExperimentalVersion": false,
+ "Installed": false,
+ "Modules": [
+ {
+ "Name": "DirectiveUtilitiesRuntime",
+ "Type": "Runtime",
+ "LoadingPhase": "Default",
+ "PlatformAllowList": [
+ "Win64",
+ "Mac",
+ "Linux"
+ ]
+ },
+ {
+ "Name": "DirectiveUtilitiesEditor",
+ "Type": "Editor",
+ "LoadingPhase": "Default",
+ "TargetAllowList": [
+ "Editor"
+ ]
+ },
+ {
+ "Name": "DirectiveUtilitiesTests",
+ "Type": "DeveloperTool",
+ "LoadingPhase": "PostEngineInit",
+ "PlatformAllowList": [
+ "Win64",
+ "Mac",
+ "Linux"
+ ],
+ "TargetAllowList": [
+ "Editor"
+ ]
+ }
+ ],
+ "Plugins": [
+ {
+ "Name": "EditorScriptingUtilities",
+ "Enabled": true
+ },
+ {
+ "Name": "EnhancedInput",
+ "Enabled": true
+ }
+ ]
+}
diff --git a/Plugins/DirectiveUtilities/LICENSE b/Plugins/DirectiveUtilities/LICENSE
new file mode 100644
index 00000000..da76d0f4
--- /dev/null
+++ b/Plugins/DirectiveUtilities/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Unreal Directive
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Plugins/DirectiveUtilities/Resources/Icon128.png b/Plugins/DirectiveUtilities/Resources/Icon128.png
new file mode 100644
index 00000000..e05e5bd4
--- /dev/null
+++ b/Plugins/DirectiveUtilities/Resources/Icon128.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3cddcdd354ad7ca3046bc092c54f6ec901faa9645c19641421047b3c93198f07
+size 8676
diff --git a/Plugins/DirectiveUtilities/Resources/UDCoreIcon.svg b/Plugins/DirectiveUtilities/Resources/UDCoreIcon.svg
new file mode 100644
index 00000000..530cb844
--- /dev/null
+++ b/Plugins/DirectiveUtilities/Resources/UDCoreIcon.svg
@@ -0,0 +1,10 @@
+
diff --git a/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesEditor/DirectiveUtilitiesEditor.Build.cs b/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesEditor/DirectiveUtilitiesEditor.Build.cs
new file mode 100644
index 00000000..e1d09503
--- /dev/null
+++ b/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesEditor/DirectiveUtilitiesEditor.Build.cs
@@ -0,0 +1,34 @@
+// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
+
+using UnrealBuildTool;
+
+public class DirectiveUtilitiesEditor : ModuleRules
+{
+ public DirectiveUtilitiesEditor(ReadOnlyTargetRules Target) : base(Target)
+ {
+ PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
+
+ PublicDependencyModuleNames.AddRange(
+ new string[]
+ {
+ "Core",
+ "CoreUObject",
+ "Engine",
+ "EditorSubsystem",
+ "EditorScriptingUtilities",
+ "DirectiveUtilitiesRuntime",
+ }
+ );
+
+ PrivateDependencyModuleNames.AddRange(
+ new string[]
+ {
+ "Slate",
+ "SlateCore",
+ "UnrealEd",
+ "AssetRegistry",
+ "AssetTools",
+ }
+ );
+ }
+}
\ No newline at end of file
diff --git a/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesEditor/Private/DirectiveUtilitiesEditor.cpp b/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesEditor/Private/DirectiveUtilitiesEditor.cpp
new file mode 100644
index 00000000..c7ec7563
--- /dev/null
+++ b/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesEditor/Private/DirectiveUtilitiesEditor.cpp
@@ -0,0 +1,13 @@
+// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
+
+#include "DirectiveUtilitiesEditor.h"
+
+void FDirectiveUtilitiesEditorModule::StartupModule()
+{
+}
+
+void FDirectiveUtilitiesEditorModule::ShutdownModule()
+{
+}
+
+IMPLEMENT_MODULE(FDirectiveUtilitiesEditorModule, DirectiveUtilitiesEditor)
diff --git a/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesEditor/Private/Libraries/DirectiveUtilEditorAssetLibrary.cpp b/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesEditor/Private/Libraries/DirectiveUtilEditorAssetLibrary.cpp
new file mode 100644
index 00000000..41a125c9
--- /dev/null
+++ b/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesEditor/Private/Libraries/DirectiveUtilEditorAssetLibrary.cpp
@@ -0,0 +1,290 @@
+// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
+
+
+#include "Libraries/DirectiveUtilEditorAssetLibrary.h"
+#include "Subsystems/EditorAssetSubsystem.h"
+#include "Algo/Transform.h"
+#include "AssetRegistry/IAssetRegistry.h"
+#include "AssetRegistry/ARFilter.h"
+#include "AssetToolsModule.h"
+#include "IAssetTools.h"
+#include "Misc/AssetRegistryInterface.h"
+#include "Modules/ModuleManager.h"
+#include "UObject/ObjectRedirector.h"
+
+namespace
+{
+ void WaitForAssetRegistry()
+ {
+ if (IAssetRegistry* AssetRegistry = IAssetRegistry::Get())
+ {
+ if (AssetRegistry->IsLoadingAssets())
+ {
+ AssetRegistry->WaitForCompletion();
+ }
+ }
+ }
+}
+
+TArray UDirectiveUtilEditorAssetLibrary::GetAssetDataListFromDirectory(
+ const FString& DirectoryPath,
+ const bool bRecursive)
+{
+ WaitForAssetRegistry();
+
+ TArray AssetDataList;
+
+ UEditorAssetSubsystem* EditorAssetSubsystem = GEditor ? GEditor->GetEditorSubsystem() : nullptr;
+ if (!EditorAssetSubsystem)
+ {
+ return AssetDataList;
+ }
+
+ const TArray AssetPaths = EditorAssetSubsystem->ListAssets(DirectoryPath, bRecursive, false);
+
+ Algo::Transform(AssetPaths, AssetDataList, [EditorAssetSubsystem](const FString& AssetPath) {
+ return EditorAssetSubsystem->FindAssetData(AssetPath);
+ });
+
+ AssetDataList.RemoveAll([](const FAssetData& AssetData) {
+ return !AssetData.IsValid();
+ });
+
+ return AssetDataList;
+}
+
+TMap UDirectiveUtilEditorAssetLibrary::FindDuplicateAssets(
+ const TArray& DirectoryPaths,
+ const bool bRecursive)
+{
+ TArray CombinedAssetDataList;
+
+ for (const FString& DirectoryPath : DirectoryPaths)
+ {
+ const TArray AssetDataList = GetAssetDataListFromDirectory(DirectoryPath, bRecursive);
+ CombinedAssetDataList.Append(AssetDataList);
+ }
+
+ TMap DuplicateAssetsMap;
+ for (const FAssetData& AssetData : CombinedAssetDataList)
+ {
+ if (!AssetData.IsValid()) { continue; }
+
+ const FString AssetName = AssetData.AssetName.ToString();
+ const FString AssetClass = AssetData.AssetClassPath.ToString();
+ const FString AssetPath = AssetData.GetSoftObjectPath().ToString();
+ const FDirectiveUtilAssetKey AssetKey(AssetName, AssetClass);
+
+ if (FDirectiveUtilDuplicateAssetData* ExistingData = DuplicateAssetsMap.Find(AssetKey))
+ {
+ ExistingData->DuplicateAssetPaths.AddUnique(AssetPath);
+ }
+ else
+ {
+ FDirectiveUtilDuplicateAssetData DuplicateAssetData;
+ DuplicateAssetData.AssetName = AssetName;
+ DuplicateAssetData.AssetClass = AssetClass;
+ DuplicateAssetData.DuplicateAssetPaths.Add(AssetPath);
+ DuplicateAssetsMap.Emplace(AssetKey, MoveTemp(DuplicateAssetData));
+ }
+ }
+
+ TArray KeysToRemove;
+ for (const auto& Pair : DuplicateAssetsMap)
+ {
+ if (Pair.Value.DuplicateAssetPaths.Num() <= 1)
+ {
+ KeysToRemove.Add(Pair.Key);
+ }
+ }
+
+ for (const FDirectiveUtilAssetKey& Key : KeysToRemove)
+ {
+ DuplicateAssetsMap.Remove(Key);
+ }
+
+ return DuplicateAssetsMap;
+}
+
+EDirectiveUtilSuccessStatus UDirectiveUtilEditorAssetLibrary::FixUpRedirectorsInPaths(const TArray& DirectoryPaths, int32& OutRedirectorsProcessed)
+{
+ OutRedirectorsProcessed = 0;
+
+ IAssetRegistry* AssetRegistry = IAssetRegistry::Get();
+ if (!AssetRegistry)
+ {
+ return EDirectiveUtilSuccessStatus::Failure;
+ }
+
+ if (AssetRegistry->IsLoadingAssets())
+ {
+ AssetRegistry->WaitForCompletion();
+ }
+
+ FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools");
+ IAssetTools& AssetTools = AssetToolsModule.Get();
+ if (AssetTools.IsFixupReferencersInProgress())
+ {
+ return EDirectiveUtilSuccessStatus::Failure;
+ }
+
+ FARFilter Filter;
+ Filter.bRecursiveClasses = false;
+ Filter.ClassPaths.Add(UObjectRedirector::StaticClass()->GetClassPathName());
+ if (DirectoryPaths.Num() > 0)
+ {
+ Filter.bRecursivePaths = true;
+ for (const FString& DirectoryPath : DirectoryPaths)
+ {
+ Filter.PackagePaths.Add(FName(*DirectoryPath));
+ }
+ }
+
+ TArray RedirectorAssets;
+ if (!AssetRegistry->GetAssets(Filter, RedirectorAssets))
+ {
+ return EDirectiveUtilSuccessStatus::Failure;
+ }
+
+ TArray Redirectors;
+ Redirectors.Reserve(RedirectorAssets.Num());
+ for (const FAssetData& RedirectorData : RedirectorAssets)
+ {
+ if (UObjectRedirector* Redirector = Cast(RedirectorData.GetAsset()))
+ {
+ Redirectors.Add(Redirector);
+ }
+ }
+
+ if (Redirectors.Num() == 0)
+ {
+ return EDirectiveUtilSuccessStatus::Success;
+ }
+
+ AssetTools.FixupReferencers(Redirectors, false, ERedirectFixupMode::DeleteFixedUpRedirectors);
+ OutRedirectorsProcessed = Redirectors.Num();
+ return EDirectiveUtilSuccessStatus::Success;
+}
+
+TArray UDirectiveUtilEditorAssetLibrary::GetAssetsByClass(
+ UClass* AssetClass,
+ const FString& PackagePath,
+ const bool bRecursiveClasses,
+ const bool bRecursivePaths,
+ EDirectiveUtilSuccessStatus& OutStatus)
+{
+ WaitForAssetRegistry();
+
+ TArray Result;
+ OutStatus = EDirectiveUtilSuccessStatus::Failure;
+
+ if (!IsValid(AssetClass))
+ {
+ return Result;
+ }
+
+ IAssetRegistry* AssetRegistry = IAssetRegistry::Get();
+ if (!AssetRegistry)
+ {
+ return Result;
+ }
+
+ FARFilter Filter;
+ Filter.ClassPaths.Add(AssetClass->GetClassPathName());
+ Filter.bRecursiveClasses = bRecursiveClasses;
+ if (!PackagePath.IsEmpty())
+ {
+ Filter.PackagePaths.Add(FName(*PackagePath));
+ Filter.bRecursivePaths = bRecursivePaths;
+ }
+
+ if (AssetRegistry->GetAssets(Filter, Result))
+ {
+ OutStatus = EDirectiveUtilSuccessStatus::Success;
+ }
+ return Result;
+}
+
+TArray UDirectiveUtilEditorAssetLibrary::GetAssetDependencies(const FAssetData& Asset, const bool bHardDependenciesOnly, EDirectiveUtilSuccessStatus& OutStatus)
+{
+ using namespace UE::AssetRegistry;
+
+ WaitForAssetRegistry();
+
+ TArray Result;
+ OutStatus = EDirectiveUtilSuccessStatus::Failure;
+
+ IAssetRegistry* AssetRegistry = IAssetRegistry::Get();
+ if (!AssetRegistry || !Asset.IsValid())
+ {
+ return Result;
+ }
+
+ TArray Dependencies;
+ const FDependencyQuery Query = bHardDependenciesOnly ? FDependencyQuery(EDependencyQuery::Hard) : FDependencyQuery();
+ if (AssetRegistry->GetDependencies(Asset.PackageName, Dependencies, EDependencyCategory::Package, Query))
+ {
+ Result.Reserve(Dependencies.Num());
+ for (const FName& Dependency : Dependencies)
+ {
+ Result.Add(Dependency.ToString());
+ }
+ OutStatus = EDirectiveUtilSuccessStatus::Success;
+ }
+ return Result;
+}
+
+TArray UDirectiveUtilEditorAssetLibrary::GetAssetReferencers(const FAssetData& Asset, const bool bHardReferencesOnly, EDirectiveUtilSuccessStatus& OutStatus)
+{
+ using namespace UE::AssetRegistry;
+
+ WaitForAssetRegistry();
+
+ TArray Result;
+ OutStatus = EDirectiveUtilSuccessStatus::Failure;
+
+ IAssetRegistry* AssetRegistry = IAssetRegistry::Get();
+ if (!AssetRegistry || !Asset.IsValid())
+ {
+ return Result;
+ }
+
+ TArray Referencers;
+ const FDependencyQuery Query = bHardReferencesOnly ? FDependencyQuery(EDependencyQuery::Hard) : FDependencyQuery();
+ if (AssetRegistry->GetReferencers(Asset.PackageName, Referencers, EDependencyCategory::Package, Query))
+ {
+ Result.Reserve(Referencers.Num());
+ for (const FName& Referencer : Referencers)
+ {
+ Result.Add(Referencer.ToString());
+ }
+ OutStatus = EDirectiveUtilSuccessStatus::Success;
+ }
+ return Result;
+}
+
+FString UDirectiveUtilEditorAssetLibrary::GetDefaultAssetNameForClass(UClass* AssetClass, EDirectiveUtilSuccessStatus& OutStatus)
+{
+ OutStatus = EDirectiveUtilSuccessStatus::Failure;
+
+ if (!IsValid(AssetClass))
+ {
+ return FString();
+ }
+
+ FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools");
+ IAssetTools& AssetTools = AssetToolsModule.Get();
+
+#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 8
+ const TOptional ResolvedName = AssetTools.GetDefaultAssetNameForClass(AssetClass, nullptr, nullptr);
+#else
+ const TOptional ResolvedName = AssetTools.GetDefaultAssetNameForClass(AssetClass);
+#endif
+
+ if (ResolvedName.IsSet() && !ResolvedName.GetValue().IsEmpty())
+ {
+ OutStatus = EDirectiveUtilSuccessStatus::Success;
+ return ResolvedName.GetValue();
+ }
+ return FString();
+}
diff --git a/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesEditor/Private/Subsystems/DirectiveUtilEditorActorSubsystem.cpp b/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesEditor/Private/Subsystems/DirectiveUtilEditorActorSubsystem.cpp
new file mode 100644
index 00000000..a112d08e
--- /dev/null
+++ b/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesEditor/Private/Subsystems/DirectiveUtilEditorActorSubsystem.cpp
@@ -0,0 +1,1372 @@
+// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
+
+#include "Subsystems/DirectiveUtilEditorActorSubsystem.h"
+
+#include "DirectiveUtilLogChannels.h"
+#include "Engine/StaticMeshActor.h"
+#include "Materials/MaterialExpressionTextureObject.h"
+#include "EditorViewportClient.h"
+#include "Components/BoxComponent.h"
+#include "Components/CapsuleComponent.h"
+#include "Components/SphereComponent.h"
+#include "Materials/MaterialExpressionTextureSample.h"
+#include "ScopedTransaction.h"
+
+namespace
+{
+ // Adds an actor to OutActors when whether it matches the predicate (evaluated once across all of the
+ // actor's components) equals whether matches are being included. Centralizes the include/exclude
+ // aggregation so a multi-component actor is judged per-actor rather than per-component.
+ template
+ void FilterActorsByPredicate(const TArray& Actors, TArray& OutActors,
+ const EDirectiveUtilInclusivity Inclusivity, TPredicate&& ActorMatches)
+ {
+ const bool bIncludeMatches = Inclusivity == Include;
+
+ // Filtering an array into itself: snapshot the input and rebuild in place.
+ TArray AliasedCopy;
+ const TArray* Source = &Actors;
+ if (static_cast(&Actors) == static_cast(&OutActors))
+ {
+ AliasedCopy = Actors;
+ Source = &AliasedCopy;
+ OutActors.Reset();
+ }
+
+ TSet Seen;
+ Seen.Reserve(OutActors.Num() + Source->Num());
+ Seen.Append(OutActors);
+
+ for (TSourceActor* Actor : *Source)
+ {
+ if (!Actor || Seen.Contains(Actor)) { continue; }
+ if (ActorMatches(Actor) == bIncludeMatches)
+ {
+ Seen.Add(Actor);
+ OutActors.Add(Actor);
+ }
+ }
+ }
+}
+
+void UDirectiveUtilEditorActorSubsystem::FocusActorsInViewport(const TArray Actors, const bool bInstant)
+{
+ if (Actors.Num() == 0) { return; }
+ if (!GEditor) { return; }
+
+ FViewport* ActiveViewport = GEditor->GetActiveViewport();
+ if (!ActiveViewport) { return; }
+
+ FEditorViewportClient* ViewportClient = static_cast(ActiveViewport->GetClient());
+ if (!ViewportClient) { return; }
+
+ FBox BoundingBox = FBox(ForceInit);
+ for (const auto Actor : Actors)
+ {
+ if (!Actor) { continue; }
+ BoundingBox += Actor->GetComponentsBoundingBox(true, true);
+ }
+
+ ViewportClient->FocusViewportOnBox(BoundingBox, bInstant);
+}
+
+TArray UDirectiveUtilEditorActorSubsystem::GetAllLevelClasses()
+{
+ TArray ActorClasses;
+ TArray Actors = GetAllLevelActors();
+
+ for (const AActor* Actor : Actors)
+ {
+ if (!Actor) { continue; }
+ ActorClasses.AddUnique(Actor->GetClass());
+ }
+
+ return ActorClasses;
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterStaticMeshActors(
+ TArray& OutStaticMeshActors,
+ TArray ActorsToFilter) const
+{
+ TSet Seen;
+ Seen.Reserve(OutStaticMeshActors.Num() + ActorsToFilter.Num());
+ for (AStaticMeshActor* ExistingActor : OutStaticMeshActors)
+ {
+ Seen.Add(ExistingActor);
+ }
+
+ for (AActor* Actor : ActorsToFilter)
+ {
+ if (!Actor || Seen.Contains(Actor))
+ {
+ continue;
+ }
+ if (Actor->IsA(AStaticMeshActor::StaticClass()))
+ {
+ Seen.Add(Actor);
+ OutStaticMeshActors.Add(Cast(Actor));
+ }
+ }
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("Filtered %d static mesh actors"), OutStaticMeshActors.Num());
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByName(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const FString& ActorName,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ return Actor->GetActorLabel().Contains(ActorName);
+ });
+
+ UE_LOG(
+ LogDirectiveUtilEditor,
+ Display,
+ TEXT("Actor Filter: Found %i actors that does %s contain the name [%s]"),
+ FilteredActors.Num(),
+ Inclusivity == EDirectiveUtilInclusivity::Include ? TEXT("") : TEXT("not"),
+ *ActorName);
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByClass(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const TSubclassOf ActorClass,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ if (!ActorClass) { return; }
+
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ return Actor->IsA(ActorClass);
+ });
+
+ UE_LOG(
+ LogDirectiveUtilEditor,
+ Display,
+ TEXT("Actor Filter: Found %i actors that does %s contain the class %s"),
+ FilteredActors.Num(),
+ Inclusivity == EDirectiveUtilInclusivity::Include ? TEXT("") : TEXT("not"),
+ *ActorClass->GetName());
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByTag(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const FName Tag,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ return Actor->ActorHasTag(Tag);
+ });
+
+ UE_LOG(
+ LogDirectiveUtilEditor,
+ Display,
+ TEXT("Actor Filter: Found %i actors that does %s contain the tag %s"),
+ FilteredActors.Num(),
+ Inclusivity == EDirectiveUtilInclusivity::Include ? TEXT("") : TEXT("not"),
+ *Tag.ToString());
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByMaterialName(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const FString& MaterialName,
+ const EDirectiveUtilSearchLocation MaterialSource,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ TArray StaticMeshComponents;
+ Actor->GetComponents(StaticMeshComponents, true);
+ for (const UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
+ {
+ if (!StaticMeshComponent) { continue; }
+
+ if (MaterialSource == BaseAndOverride || MaterialSource == OverrideOnly)
+ {
+ for (int32 i = 0; i < StaticMeshComponent->GetNumMaterials(); i++)
+ {
+ const UMaterialInterface* Mat = StaticMeshComponent->GetMaterial(i);
+ if (Mat && Mat->GetName().Contains(MaterialName)) { return true; }
+ }
+ }
+
+ if (MaterialSource == BaseAndOverride || MaterialSource == BaseOnly)
+ {
+ if (const UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh())
+ {
+ for (int32 i = 0; i < Mesh->GetStaticMaterials().Num(); i++)
+ {
+ const UMaterialInterface* Mat = Mesh->GetMaterial(i);
+ if (Mat && Mat->GetName().Contains(MaterialName)) { return true; }
+ }
+ }
+ }
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("Actor Filter: Found %i actors that does %s contain the material %s"),
+ FilteredActors.Num(), Inclusivity == EDirectiveUtilInclusivity::Include ? TEXT("") : TEXT("not"), *MaterialName);
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByMaterial(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const TSoftObjectPtr& Material,
+ const EDirectiveUtilSearchLocation MaterialSource,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ const UMaterialInterface* TargetMaterial = Material.LoadSynchronous();
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ TArray StaticMeshComponents;
+ Actor->GetComponents(StaticMeshComponents, true);
+ for (const UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
+ {
+ if (!StaticMeshComponent) { continue; }
+
+ if (MaterialSource == BaseAndOverride || MaterialSource == OverrideOnly)
+ {
+ for (int32 i = 0; i < StaticMeshComponent->GetNumMaterials(); i++)
+ {
+ if (StaticMeshComponent->GetMaterial(i) == TargetMaterial) { return true; }
+ }
+ }
+
+ if (MaterialSource == BaseAndOverride || MaterialSource == BaseOnly)
+ {
+ if (const UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh())
+ {
+ for (int32 i = 0; i < Mesh->GetStaticMaterials().Num(); i++)
+ {
+ if (Mesh->GetMaterial(i) == TargetMaterial) { return true; }
+ }
+ }
+ }
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("Actor Filter: Found %i actors that does %s contain the material %s"),
+ FilteredActors.Num(), Inclusivity == EDirectiveUtilInclusivity::Include ? TEXT("") : TEXT("not"), *Material.ToString());
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByStaticMeshName(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const FString& StaticMeshName,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ for (const UActorComponent* Component : Actor->GetComponents())
+ {
+ const UStaticMeshComponent* StaticMeshComponent = Cast(Component);
+ if (!StaticMeshComponent) { continue; }
+
+ const UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh();
+ if (!Mesh)
+ {
+ // A null static mesh only counts as a match when filtering for an empty name.
+ if (StaticMeshName.IsEmpty()) { return true; }
+ continue;
+ }
+ if (Mesh->GetName().Contains(StaticMeshName)) { return true; }
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("Actor Filter: Found %i actors that does %s contain the static mesh %s"),
+ FilteredActors.Num(), Inclusivity == EDirectiveUtilInclusivity::Include ? TEXT("") : TEXT("not"), *StaticMeshName);
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByStaticMesh(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const TSoftObjectPtr& StaticMesh,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ const UStaticMesh* TargetMesh = StaticMesh.LoadSynchronous();
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ for (const UActorComponent* Component : Actor->GetComponents())
+ {
+ const UStaticMeshComponent* StaticMeshComponent = Cast(Component);
+ if (!StaticMeshComponent) { continue; }
+
+ const UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh();
+ if (!Mesh)
+ {
+ if (StaticMesh.IsNull()) { return true; }
+ continue;
+ }
+ if (Mesh == TargetMesh) { return true; }
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("Actor Filter: Found %i actors that does %s contain the static mesh %s"),
+ FilteredActors.Num(), Inclusivity == EDirectiveUtilInclusivity::Include ? TEXT("") : TEXT("not"),
+ *StaticMesh.ToString());
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByVertCount(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const int32 MinVertCount,
+ const int32 MaxVertCount,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ TArray StaticMeshComponents;
+ Actor->GetComponents(StaticMeshComponents, true);
+ for (const UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
+ {
+ if (!StaticMeshComponent) { continue; }
+ const UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh();
+ if (!Mesh || !Mesh->HasValidRenderData(true, 0)) { continue; }
+
+ const int32 VertCount = Mesh->GetNumVertices(0);
+ if (VertCount >= MinVertCount && VertCount <= MaxVertCount) { return true; }
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("Actor Filter: Found %i actors that %s between %i and %i vertices"),
+ FilteredActors.Num(), Inclusivity == EDirectiveUtilInclusivity::Include ? TEXT("has") : TEXT("does not have"),
+ MinVertCount, MaxVertCount);
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByTriCount(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const int32 MinTriCount,
+ const int32 MaxTriCount,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ TArray StaticMeshComponents;
+ Actor->GetComponents(StaticMeshComponents, true);
+ for (const UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
+ {
+ if (!StaticMeshComponent) { continue; }
+ const UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh();
+ if (!Mesh || !Mesh->HasValidRenderData(true, 0)) { continue; }
+
+ const int32 TriCount = Mesh->GetNumTriangles(0);
+ if (TriCount >= MinTriCount && TriCount <= MaxTriCount) { return true; }
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("Actor Filter: Found %i actors that %s between %i and %i triangles"),
+ FilteredActors.Num(), Inclusivity == EDirectiveUtilInclusivity::Include ? TEXT("has") : TEXT("does not have"),
+ MinTriCount, MaxTriCount);
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByBounds(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const FVector& MinBounds,
+ const FVector& MaxBounds,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ FVector Origin, Extent;
+ Actor->GetActorBounds(false, Origin, Extent);
+ const FVector ActorSize = Extent * 2;
+ return MinBounds.X <= ActorSize.X && ActorSize.X <= MaxBounds.X &&
+ MinBounds.Y <= ActorSize.Y && ActorSize.Y <= MaxBounds.Y &&
+ MinBounds.Z <= ActorSize.Z && ActorSize.Z <= MaxBounds.Z;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display,
+ TEXT("Actor Filter: Found %i actors that %s within the bounds (%f, %f, %f) and (%f, %f, %f)"),
+ FilteredActors.Num(), Inclusivity == EDirectiveUtilInclusivity::Include ? TEXT("is") : TEXT("is not"), MinBounds.X,
+ MinBounds.Y, MinBounds.Z,
+ MaxBounds.X, MaxBounds.Y, MaxBounds.Z);
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByStaticMeshBounds(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const FVector& MinBounds,
+ const FVector& MaxBounds,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ for (UActorComponent* Component : Actor->GetComponents())
+ {
+ const UStaticMeshComponent* StaticMeshComponent = Cast(Component);
+ if (!StaticMeshComponent || !StaticMeshComponent->GetStaticMesh()) { continue; }
+
+ const FVector StaticMeshSize = StaticMeshComponent->GetStaticMesh()->GetBounds().BoxExtent * 2;
+ if (MinBounds.X <= StaticMeshSize.X && StaticMeshSize.X <= MaxBounds.X &&
+ MinBounds.Y <= StaticMeshSize.Y && StaticMeshSize.Y <= MaxBounds.Y &&
+ MinBounds.Z <= StaticMeshSize.Z && StaticMeshSize.Z <= MaxBounds.Z) { return true; }
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display,
+ TEXT("Actor Filter: Found %i actors that %s within the static mesh bounds (%f, %f, %f) and (%f, %f, %f)"),
+ FilteredActors.Num(), Inclusivity == EDirectiveUtilInclusivity::Include ? TEXT("is") : TEXT("is not"), MinBounds.X,
+ MinBounds.Y, MinBounds.Z,
+ MaxBounds.X, MaxBounds.Y, MaxBounds.Z);
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByWorldLocation(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const FVector& WorldLocation,
+ const float Radius,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ return FVector::Dist(Actor->GetActorLocation(), WorldLocation) <= Radius;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display,
+ TEXT("Actor Filter: Found %i actors that %s within the world location (%f, %f, %f) with the radius of %f"),
+ FilteredActors.Num(), Inclusivity == EDirectiveUtilInclusivity::Include ? TEXT("is") : TEXT("is not"), WorldLocation.X,
+ WorldLocation.Y, WorldLocation.Z, Radius);
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByLODCount(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const int32 MinLODs,
+ const int32 MaxLODs,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ TArray StaticMeshComponents;
+ Actor->GetComponents(StaticMeshComponents, true);
+ for (const UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
+ {
+ if (!StaticMeshComponent) { continue; }
+ const UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
+ if (!StaticMesh) { continue; }
+ if (StaticMesh->GetNumLODs() >= MinLODs && StaticMesh->GetNumLODs() <= MaxLODs) { return true; }
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("Actor Filter: Found %i actors that %s between %i and %i LODs"),
+ FilteredActors.Num(), Inclusivity == EDirectiveUtilInclusivity::Include ? TEXT("has") : TEXT("does not have"), MinLODs,
+ MaxLODs);
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByNaniteState(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const bool bNaniteEnabled,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ TArray StaticMeshComponents;
+ Actor->GetComponents(StaticMeshComponents, true);
+ for (const UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
+ {
+ if (!StaticMeshComponent) { continue; }
+ const UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh();
+ if (!Mesh) { continue; }
+#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 7
+ const bool bMeshNaniteEnabled = Mesh->GetNaniteSettings().bEnabled;
+#else
+ const bool bMeshNaniteEnabled = Mesh->NaniteSettings.bEnabled;
+#endif
+ if (bMeshNaniteEnabled == bNaniteEnabled) { return true; }
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("Actor Filter: Found %i actors that %s Nanite enabled"),
+ FilteredActors.Num(), bNaniteEnabled ? TEXT("has") : TEXT("does not have"));
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByLightmapResolution(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const int32 MinLightmapResolution,
+ const int32 MaxLightmapResolution,
+ const EDirectiveUtilSearchLocation SearchLocation,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ TArray StaticMeshComponents;
+ Actor->GetComponents(StaticMeshComponents, true);
+ for (const UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
+ {
+ if (!StaticMeshComponent) { continue; }
+ const UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh();
+ if (!Mesh) { continue; }
+
+ if ((SearchLocation == BaseAndOverride || SearchLocation == OverrideOnly) && StaticMeshComponent->bOverrideLightMapRes)
+ {
+ const int32 LightmapRes = StaticMeshComponent->OverriddenLightMapRes;
+ if (LightmapRes >= MinLightmapResolution && LightmapRes <= MaxLightmapResolution) { return true; }
+ }
+ if (SearchLocation == BaseAndOverride || SearchLocation == BaseOnly)
+ {
+ const int32 LightmapRes = Mesh->GetLightMapResolution();
+ if (LightmapRes >= MinLightmapResolution && LightmapRes <= MaxLightmapResolution) { return true; }
+ }
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display,
+ TEXT("Actor Filter: Found %i actors that %s between %i and %i lightmap resolution"),
+ FilteredActors.Num(), Inclusivity == EDirectiveUtilInclusivity::Include ? TEXT("has") : TEXT("does not have"),
+ MinLightmapResolution, MaxLightmapResolution);
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByMobility(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const EComponentMobility::Type Mobility,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ const USceneComponent* Root = Actor->GetRootComponent();
+ return Root && Root->Mobility == Mobility;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("Actor Filter: Found %i actors that %s mobility of %s"),
+ FilteredActors.Num(), Inclusivity == EDirectiveUtilInclusivity::Include ? TEXT("has") : TEXT("does not have"),
+ *UEnum::GetValueAsString(Mobility));
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByCollisionChannel(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const ECollisionChannel CollisionChannel,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ TArray StaticMeshComponents;
+ Actor->GetComponents(StaticMeshComponents, true);
+ for (const UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
+ {
+ if (!StaticMeshComponent) { continue; }
+ if (StaticMeshComponent->GetCollisionObjectType() == CollisionChannel) { return true; }
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("Actor Filter: Found %i actors that %s collision channel of %s"),
+ FilteredActors.Num(), Inclusivity == EDirectiveUtilInclusivity::Include ? TEXT("has") : TEXT("does not have"),
+ *UEnum::GetValueAsString(CollisionChannel));
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByCollisionResponse(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const ECollisionChannel CollisionChannel,
+ const ECollisionResponse CollisionResponse,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ TArray StaticMeshComponents;
+ Actor->GetComponents(StaticMeshComponents, true);
+ for (const UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
+ {
+ if (!StaticMeshComponent) { continue; }
+ if (StaticMeshComponent->GetCollisionResponseToChannel(CollisionChannel) == CollisionResponse) { return true; }
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("Actor Filter: Found %i actors that %s collision response of %s"),
+ FilteredActors.Num(), Inclusivity == EDirectiveUtilInclusivity::Include ? TEXT("has") : TEXT("does not have"),
+ *UEnum::GetValueAsString(CollisionResponse));
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByCollisionEnabled(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const ECollisionEnabled::Type CollisionEnabled,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ TArray StaticMeshComponents;
+ Actor->GetComponents(StaticMeshComponents, true);
+ for (const UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
+ {
+ if (!StaticMeshComponent) { continue; }
+ if (StaticMeshComponent->GetCollisionEnabled() == CollisionEnabled) { return true; }
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("Actor Filter: Found %i actors that %s collision enabled of %s"),
+ FilteredActors.Num(), Inclusivity == EDirectiveUtilInclusivity::Include ? TEXT("has") : TEXT("does not have"),
+ *UEnum::GetValueAsString(CollisionEnabled));
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByCollisionProfile(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const FName CollisionProfile,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ TArray StaticMeshComponents;
+ Actor->GetComponents(StaticMeshComponents, true);
+ for (const UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
+ {
+ if (!StaticMeshComponent) { continue; }
+ if (StaticMeshComponent->GetCollisionProfileName() == CollisionProfile) { return true; }
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("Actor Filter: Found %i actors that %s collision profile of %s"),
+ FilteredActors.Num(), Inclusivity == EDirectiveUtilInclusivity::Include ? TEXT("has") : TEXT("does not have"),
+ *CollisionProfile.ToString());
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByTextureName(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const FString TextureName,
+ const EDirectiveUtilSearchLocation Source,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ TArray StaticMeshComponents;
+ Actor->GetComponents(StaticMeshComponents, true);
+ for (const UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
+ {
+ if (!StaticMeshComponent) { continue; }
+
+ if (Source == BaseAndOverride || Source == OverrideOnly)
+ {
+ for (UMaterialInterface* Material : StaticMeshComponent->GetMaterials())
+ {
+ if (!Material || !Material->GetMaterial()) { continue; }
+ for (const auto& Expression : Material->GetMaterial()->GetExpressions())
+ {
+ if (const UMaterialExpressionTextureSample* TextureSample = Cast(Expression))
+ {
+ if (TextureSample->Texture.GetName().Contains(TextureName)) { return true; }
+ }
+ if (const UMaterialExpressionTextureObject* TextureObject = Cast(Expression))
+ {
+ if (TextureObject->Texture.GetName().Contains(TextureName)) { return true; }
+ }
+ }
+ }
+ }
+
+ if (Source == BaseAndOverride || Source == BaseOnly)
+ {
+ const UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh();
+ if (!Mesh) { continue; }
+ for (int32 i = 0; i < Mesh->GetStaticMaterials().Num(); i++)
+ {
+ UMaterialInterface* Material = Mesh->GetMaterial(i);
+ if (!Material || !Material->GetMaterial()) { continue; }
+ for (const auto& Expression : Material->GetMaterial()->GetExpressions())
+ {
+ if (const UMaterialExpressionTextureSample* TextureSample = Cast(Expression))
+ {
+ if (TextureSample->Texture.GetName().Contains(TextureName)) { return true; }
+ }
+ if (const UMaterialExpressionTextureObject* TextureObject = Cast(Expression))
+ {
+ if (TextureObject->Texture.GetName().Contains(TextureName)) { return true; }
+ }
+ }
+ }
+ }
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("Actor Filter: Found %i actors that %s texture name of %s"),
+ FilteredActors.Num(), Inclusivity == EDirectiveUtilInclusivity::Include ? TEXT("has") : TEXT("does not have"),
+ *TextureName);
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByTexture(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ TSoftObjectPtr TextureReference,
+ const EDirectiveUtilSearchLocation Source,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ TArray StaticMeshComponents;
+ Actor->GetComponents(StaticMeshComponents, true);
+ for (const UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
+ {
+ if (!StaticMeshComponent) { continue; }
+
+ if (Source == BaseAndOverride || Source == OverrideOnly)
+ {
+ for (UMaterialInterface* Material : StaticMeshComponent->GetMaterials())
+ {
+ if (!Material || !Material->GetMaterial()) { continue; }
+ for (const auto& Expression : Material->GetMaterial()->GetExpressions())
+ {
+ if (const UMaterialExpressionTextureSample* TextureSample = Cast(Expression))
+ {
+ if (TextureSample->Texture == TextureReference) { return true; }
+ }
+ if (const UMaterialExpressionTextureObject* TextureObject = Cast(Expression))
+ {
+ if (TextureObject->Texture == TextureReference) { return true; }
+ }
+ }
+ }
+ }
+
+ if (Source == BaseAndOverride || Source == BaseOnly)
+ {
+ const UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh();
+ if (!Mesh) { continue; }
+ for (int32 i = 0; i < Mesh->GetStaticMaterials().Num(); i++)
+ {
+ UMaterialInterface* Material = Mesh->GetMaterial(i);
+ if (!Material || !Material->GetMaterial()) { continue; }
+ for (const auto& Expression : Material->GetMaterial()->GetExpressions())
+ {
+ if (const UMaterialExpressionTextureSample* TextureSample = Cast(Expression))
+ {
+ if (TextureSample->Texture == TextureReference) { return true; }
+ }
+ if (const UMaterialExpressionTextureObject* TextureObject = Cast(Expression))
+ {
+ if (TextureObject->Texture == TextureReference) { return true; }
+ }
+ }
+ }
+ }
+ }
+ return false;
+ });
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterEmptyActors(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByPredicate(Actors, FilteredActors, Inclusivity, [](AActor* Actor) -> bool
+ {
+ TArray ActorComponents;
+ Actor->GetComponents(ActorComponents);
+ if (ActorComponents.IsEmpty()) { return true; }
+ if (ActorComponents.Num() == 1)
+ {
+ // ExactCast: a subclass component (mesh, light, camera...) means the actor does something.
+ const USceneComponent* SceneComponent = ExactCast(ActorComponents[0]);
+ return SceneComponent && SceneComponent->GetNumChildrenComponents() == 0;
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("Actor Filter: Found %i actors that %s empty"),
+ FilteredActors.Num(), Inclusivity == EDirectiveUtilInclusivity::Include ? TEXT("are") : TEXT("are not"));
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByMissingMaterials(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const EDirectiveUtilSearchLocation Location,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByMaterial(Actors, FilteredActors, nullptr, Location, Inclusivity);
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByMissingStaticMeshes(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByStaticMesh(Actors, FilteredActors, nullptr, Inclusivity);
+}
+
+void UDirectiveUtilEditorActorSubsystem::FilterActorsByMissingTextures(
+ const TArray& Actors,
+ TArray& FilteredActors,
+ const EDirectiveUtilSearchLocation Location,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ FilterActorsByTexture(Actors, FilteredActors, nullptr, Location, Inclusivity);
+}
+
+bool UDirectiveUtilEditorActorSubsystem::IsActorWithinBoxBounds(AActor* Actor, UBoxComponent* BoxComponent)
+{
+ if (!Actor || !BoxComponent)
+ {
+ return false;
+ }
+
+ // Transform the actor's location into the box's local space so the box's rotation and its own
+ // scale are respected, then test against the unscaled extent (a proper oriented-box check).
+ const FVector LocalLocation = BoxComponent->GetComponentTransform().InverseTransformPosition(Actor->GetActorLocation());
+ const FVector Extent = BoxComponent->GetUnscaledBoxExtent();
+ return FMath::Abs(LocalLocation.X) <= Extent.X
+ && FMath::Abs(LocalLocation.Y) <= Extent.Y
+ && FMath::Abs(LocalLocation.Z) <= Extent.Z;
+}
+
+bool UDirectiveUtilEditorActorSubsystem::IsActorWithinSphereBounds(AActor* Actor, USphereComponent* SphereComponent)
+{
+ if (!Actor || !SphereComponent)
+ {
+ return false;
+ }
+ const float DeltaLoc = FVector::Dist(Actor->GetActorLocation(), SphereComponent->GetComponentLocation());
+ return DeltaLoc <= SphereComponent->GetScaledSphereRadius();
+}
+
+bool UDirectiveUtilEditorActorSubsystem::IsActorWithinCapsuleBounds(AActor* Actor, UCapsuleComponent* CapsuleComponent)
+{
+ if (!Actor || !CapsuleComponent)
+ {
+ return false;
+ }
+ // Proper capsule containment: distance from the actor to the capsule's central segment (its local
+ // Z axis, between the cap centres) must be within the radius. Local space respects orientation.
+ const FVector LocalLocation = CapsuleComponent->GetComponentTransform().InverseTransformPosition(Actor->GetActorLocation());
+ const float Radius = CapsuleComponent->GetUnscaledCapsuleRadius();
+ const float SegmentHalfHeight = FMath::Max(0.0f, CapsuleComponent->GetUnscaledCapsuleHalfHeight() - Radius);
+ const FVector ClosestOnSegment(0.0f, 0.0f, FMath::Clamp(LocalLocation.Z, -SegmentHalfHeight, SegmentHalfHeight));
+ return FVector::Dist(LocalLocation, ClosestOnSegment) <= Radius;
+}
+
+void UDirectiveUtilEditorActorSubsystem::GetActorsByClass(
+ TArray& FoundActors,
+ const TSubclassOf ActorClass,
+ const EDirectiveUtilSelectionMethod SelectionMethod,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ const TArray ActorsToFilter = SelectionMethod == Selection ? GetSelectedLevelActors() : GetAllLevelActors();
+ FilterActorsByClass(ActorsToFilter, FoundActors, ActorClass, Inclusivity);
+}
+
+void UDirectiveUtilEditorActorSubsystem::GetActorsByName(
+ TArray& FoundActors,
+ const FString ActorName,
+ const EDirectiveUtilSelectionMethod SelectionMethod,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ const TArray ActorsToFilter = SelectionMethod == Selection ? GetSelectedLevelActors() : GetAllLevelActors();
+ FilterActorsByName(ActorsToFilter, FoundActors, ActorName, Inclusivity);
+}
+
+void UDirectiveUtilEditorActorSubsystem::GetActorsByMaterial(
+ TArray& FoundActors,
+ const UMaterialInterface* Material,
+ const EDirectiveUtilSearchLocation MaterialSource,
+ const EDirectiveUtilSelectionMethod SelectionMethod,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ const TSoftObjectPtr MaterialSoftReference{FSoftObjectPath(Material)};
+ GetActorsByMaterialSoftReference(FoundActors, MaterialSoftReference, MaterialSource, SelectionMethod, Inclusivity);
+}
+
+
+void UDirectiveUtilEditorActorSubsystem::GetActorsByMaterialSoftReference(
+ TArray& FoundActors,
+ const TSoftObjectPtr Material,
+ const EDirectiveUtilSearchLocation MaterialSource,
+ const EDirectiveUtilSelectionMethod SelectionMethod,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ const TArray SourceActors = SelectionMethod == Selection ? GetSelectedLevelActors() : GetAllLevelActors();
+
+ TArray StaticMeshActors;
+ FilterStaticMeshActors(StaticMeshActors, SourceActors);
+
+ FilterActorsByPredicate(StaticMeshActors, FoundActors, Inclusivity, [&](AStaticMeshActor* StaticMeshActor) -> bool
+ {
+ const UStaticMeshComponent* StaticMeshComp = StaticMeshActor->GetStaticMeshComponent();
+ if (!StaticMeshComp) { return false; }
+ const UStaticMesh* Mesh = StaticMeshComp->GetStaticMesh();
+ if (!Mesh) { return false; }
+
+ if (MaterialSource == BaseAndOverride || MaterialSource == OverrideOnly)
+ {
+ for (int32 i = 0; i < StaticMeshComp->GetNumMaterials(); i++)
+ {
+ if (StaticMeshComp->GetMaterial(i) == Material) { return true; }
+ }
+ }
+ if (MaterialSource == BaseAndOverride || MaterialSource == BaseOnly)
+ {
+ for (int32 i = 0; i < Mesh->GetStaticMaterials().Num(); i++)
+ {
+ if (Mesh->GetMaterial(i) == Material) { return true; }
+ }
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("%i actors with the material reference were found."),
+ FoundActors.Num());
+}
+
+void UDirectiveUtilEditorActorSubsystem::GetActorsByMaterialName(
+ TArray& FoundActors,
+ const FString MaterialName,
+ const EDirectiveUtilSearchLocation MaterialSource,
+ const EDirectiveUtilSelectionMethod SelectionMethod,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ const TArray SourceActors = SelectionMethod == Selection ? GetSelectedLevelActors() : GetAllLevelActors();
+
+ TArray StaticMeshActors;
+ FilterStaticMeshActors(StaticMeshActors, SourceActors);
+
+ FilterActorsByPredicate(StaticMeshActors, FoundActors, Inclusivity, [&](AStaticMeshActor* StaticMeshActor) -> bool
+ {
+ const UStaticMeshComponent* StaticMeshComp = StaticMeshActor->GetStaticMeshComponent();
+ if (!StaticMeshComp) { return false; }
+ const UStaticMesh* Mesh = StaticMeshComp->GetStaticMesh();
+ if (!Mesh) { return false; }
+
+ if (MaterialSource == BaseAndOverride || MaterialSource == OverrideOnly)
+ {
+ for (int32 i = 0; i < StaticMeshComp->GetNumMaterials(); i++)
+ {
+ const UMaterialInterface* Mat = StaticMeshComp->GetMaterial(i);
+ if (Mat && Mat->GetName().Contains(MaterialName)) { return true; }
+ }
+ }
+ if (MaterialSource == BaseAndOverride || MaterialSource == BaseOnly)
+ {
+ for (int32 i = 0; i < Mesh->GetStaticMaterials().Num(); i++)
+ {
+ const UMaterialInterface* Mat = Mesh->GetMaterial(i);
+ if (Mat && Mat->GetName().Contains(MaterialName)) { return true; }
+ }
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("%i actors with material %s were found."), FoundActors.Num(),
+ *MaterialName);
+}
+
+void UDirectiveUtilEditorActorSubsystem::GetActorsByVertexCount(
+ TArray& FoundActors,
+ const int32 From,
+ const int32 To,
+ const EDirectiveUtilSelectionMethod SelectionMethod,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ const TArray SourceActors = SelectionMethod == Selection ? GetSelectedLevelActors() : GetAllLevelActors();
+ FilterActorsByPredicate(SourceActors, FoundActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ TArray StaticMeshComponents;
+ Actor->GetComponents(StaticMeshComponents, true);
+ for (const UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
+ {
+ if (!StaticMeshComponent) { continue; }
+ const UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
+ if (!StaticMesh || !StaticMesh->HasValidRenderData(true, 0)) { continue; }
+ const int32 VertexCount = StaticMesh->GetNumVertices(0);
+ if (VertexCount >= From && VertexCount <= To) { return true; }
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("%i actors with vertex count between %i and %i were found."),
+ FoundActors.Num(), From, To);
+}
+
+void UDirectiveUtilEditorActorSubsystem::GetActorsByTriCount(
+ TArray& FoundActors,
+ const int32 From,
+ const int32 To,
+ const EDirectiveUtilSelectionMethod SelectionMethod,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ const TArray SourceActors = SelectionMethod == Selection ? GetSelectedLevelActors() : GetAllLevelActors();
+ FilterActorsByPredicate(SourceActors, FoundActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ TArray StaticMeshComponents;
+ Actor->GetComponents(StaticMeshComponents, true);
+ for (const UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
+ {
+ if (!StaticMeshComponent) { continue; }
+ const UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
+ if (!StaticMesh || !StaticMesh->HasValidRenderData(true, 0)) { continue; }
+ const int32 TriCount = StaticMesh->GetNumTriangles(0);
+ if (TriCount >= From && TriCount <= To) { return true; }
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor,
+ Display,
+ TEXT("%i actors with triangle count between %i and %i were found."),
+ FoundActors.Num(),
+ From,
+ To);
+}
+
+void UDirectiveUtilEditorActorSubsystem::GetActorsByBoundingBox(
+ TArray& FoundActors,
+ const FVector Min,
+ const FVector Max,
+ const EDirectiveUtilSelectionMethod SelectionMethod,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ const TArray SourceActors = SelectionMethod == Selection ? GetSelectedLevelActors() : GetAllLevelActors();
+ FilterActorsByPredicate(SourceActors, FoundActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ const FBox QueryBox(Min, Max);
+ const FBox Box = Actor->GetComponentsBoundingBox();
+ return Box.IsValid && QueryBox.IsInsideOrOn(Box.Min) && QueryBox.IsInsideOrOn(Box.Max);
+ });
+
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("%i actors with bounding box between %s and %s were found."),
+ FoundActors.Num(), *Min.ToString(), *Max.ToString());
+}
+
+void UDirectiveUtilEditorActorSubsystem::GetActorsByMeshSize(
+ TArray& FoundActors,
+ const float From,
+ const float To,
+ const EDirectiveUtilSelectionMethod SelectionMethod,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ const TArray SourceActors = SelectionMethod == Selection ? GetSelectedLevelActors() : GetAllLevelActors();
+ FilterActorsByPredicate(SourceActors, FoundActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ TArray StaticMeshComponents;
+ Actor->GetComponents(StaticMeshComponents, true);
+ for (const UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
+ {
+ if (!StaticMeshComponent || !StaticMeshComponent->GetStaticMesh()) { continue; }
+ const double BoundBoxSize = StaticMeshComponent->GetStaticMesh()->GetBoundingBox().GetSize().Size();
+ if (BoundBoxSize >= From && BoundBoxSize <= To) { return true; }
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("%i actors with mesh size between %f and %f were found."),
+ FoundActors.Num(), From, To);
+}
+
+void UDirectiveUtilEditorActorSubsystem::GetActorsByWorldLocation(
+ TArray& FoundActors,
+ const FVector WorldLocation,
+ const float Radius,
+ const EDirectiveUtilSelectionMethod SelectionMethod,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ const TArray SourceActors = SelectionMethod == Selection ? GetSelectedLevelActors() : GetAllLevelActors();
+ FilterActorsByPredicate(SourceActors, FoundActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ return FVector::Dist(Actor->GetActorLocation(), WorldLocation) <= Radius;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("%i actors with world location %s and radius %f were found."),
+ FoundActors.Num(), *WorldLocation.ToString(), Radius);
+}
+
+void UDirectiveUtilEditorActorSubsystem::GetActorsByLODCount(
+ TArray& FoundActors,
+ const int32 LODCountFrom,
+ const int32 LODCountTo,
+ const EDirectiveUtilSelectionMethod SelectionMethod,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ const TArray SourceActors = SelectionMethod == Selection ? GetSelectedLevelActors() : GetAllLevelActors();
+ FilterActorsByPredicate(SourceActors, FoundActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ TArray StaticMeshComponents;
+ Actor->GetComponents(StaticMeshComponents, true);
+ for (const UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
+ {
+ if (!StaticMeshComponent) { continue; }
+ const UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
+ if (!StaticMesh) { continue; }
+ if (StaticMesh->GetNumLODs() >= LODCountFrom && StaticMesh->GetNumLODs() <= LODCountTo) { return true; }
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("%i actors with LOD count between %i and %i were found."),
+ FoundActors.Num(), LODCountFrom, LODCountTo);
+}
+
+void UDirectiveUtilEditorActorSubsystem::GetActorsByNaniteEnabled(
+ TArray& FoundActors,
+ const bool bNaniteEnabled,
+ const EDirectiveUtilSelectionMethod SelectionMethod,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ const TArray SourceActors = SelectionMethod == Selection ? GetSelectedLevelActors() : GetAllLevelActors();
+ FilterActorsByPredicate(SourceActors, FoundActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ TArray StaticMeshComponents;
+ Actor->GetComponents(StaticMeshComponents, true);
+ for (const UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
+ {
+ if (!StaticMeshComponent) { continue; }
+ const UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh();
+ if (!Mesh) { continue; }
+#if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 7
+ const bool bMeshNaniteEnabled = Mesh->GetNaniteSettings().bEnabled;
+#else
+ const bool bMeshNaniteEnabled = Mesh->NaniteSettings.bEnabled;
+#endif
+ if (bMeshNaniteEnabled == bNaniteEnabled) { return true; }
+ }
+ return false;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("%i actors with nanite enabled %s were found."),
+ FoundActors.Num(), bNaniteEnabled ? TEXT("true") : TEXT("false"));
+}
+
+void UDirectiveUtilEditorActorSubsystem::GetActorsByLightmapResolution(
+ TArray& FoundActors,
+ const int32 From,
+ const int32 To,
+ const EDirectiveUtilSelectionMethod SelectionMethod,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ const TArray SourceActors = SelectionMethod == Selection ? GetSelectedLevelActors() : GetAllLevelActors();
+ FilterActorsByPredicate(SourceActors, FoundActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ TArray StaticMeshComponents;
+ Actor->GetComponents(StaticMeshComponents, true);
+ for (const UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
+ {
+ if (!StaticMeshComponent || !StaticMeshComponent->GetStaticMesh()) { continue; }
+ const int32 LightmapRes = StaticMeshComponent->GetStaticMesh()->GetLightMapResolution();
+ if (LightmapRes >= From && LightmapRes <= To) { return true; }
+ }
+ return false;
+ });
+
+ UE_LOG(
+ LogDirectiveUtilEditor,
+ Display,
+ TEXT("%i actors with lightmap resolution between %i and %i were found."),
+ FoundActors.Num(),
+ From,
+ To);
+}
+
+void UDirectiveUtilEditorActorSubsystem::GetActorsByMobility(
+ TArray& FoundActors,
+ const EComponentMobility::Type Mobility,
+ const EDirectiveUtilSelectionMethod SelectionMethod,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ const TArray SourceActors = SelectionMethod == Selection ? GetSelectedLevelActors() : GetAllLevelActors();
+ FilterActorsByPredicate(SourceActors, FoundActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ const USceneComponent* Root = Actor->GetRootComponent();
+ return Root && Root->Mobility == Mobility;
+ });
+
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("%i actors with mobility %s were found."),
+ FoundActors.Num(), *UEnum::GetValueAsName(Mobility).ToString());
+}
+
+void UDirectiveUtilEditorActorSubsystem::GetActorsByStaticMesh(
+ TArray& FoundActors,
+ UStaticMesh* StaticMesh,
+ const EDirectiveUtilSelectionMethod SelectionMethod,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ GetActorsByStaticMeshSoftReference(FoundActors, StaticMesh, SelectionMethod, Inclusivity);
+}
+
+void UDirectiveUtilEditorActorSubsystem::GetActorsByStaticMeshSoftReference(
+ TArray& FoundActors,
+ TSoftObjectPtr StaticMesh,
+ const EDirectiveUtilSelectionMethod SelectionMethod,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ const TArray SourceActors = SelectionMethod == Selection ? GetSelectedLevelActors() : GetAllLevelActors();
+
+ FilterActorsByPredicate(SourceActors, FoundActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ for (const auto Component : Actor->GetComponents())
+ {
+ const UStaticMeshComponent* StaticMeshComponent = Cast(Component);
+ if (!StaticMeshComponent) { continue; }
+ if (StaticMeshComponent->GetStaticMesh() == StaticMesh) { return true; }
+ }
+ return false;
+ });
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("%i actors with static mesh %s were found."),
+ FoundActors.Num(), *StaticMesh.ToString());
+}
+
+void UDirectiveUtilEditorActorSubsystem::GetActorsByStaticMeshName(
+ TArray& FoundActors,
+ const FString StaticMeshName,
+ const EDirectiveUtilSelectionMethod SelectionMethod,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ const TArray SourceActors = SelectionMethod == Selection ? GetSelectedLevelActors() : GetAllLevelActors();
+
+ FilterActorsByPredicate(SourceActors, FoundActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ for (const auto Component : Actor->GetComponents())
+ {
+ const UStaticMeshComponent* StaticMeshComponent = Cast(Component);
+ if (!StaticMeshComponent) { continue; }
+ const UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh();
+ if (!Mesh)
+ {
+ // A null static mesh only counts as a match when filtering for an empty name.
+ if (StaticMeshName.IsEmpty()) { return true; }
+ continue;
+ }
+ if (Mesh->GetName().Contains(StaticMeshName)) { return true; }
+ }
+ return false;
+ });
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("%i actors with static mesh %s were found."),
+ FoundActors.Num(), *StaticMeshName);
+}
+
+void UDirectiveUtilEditorActorSubsystem::GetActorsByTexture(
+ TArray& FoundActors,
+ UTexture2D* Texture,
+ const EDirectiveUtilSelectionMethod SelectionMethod,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ GetActorsByTextureSoftReference(FoundActors, Texture, SelectionMethod, Inclusivity);
+}
+
+void UDirectiveUtilEditorActorSubsystem::GetActorsByTextureSoftReference(
+ TArray& FoundActors,
+ const TSoftObjectPtr Texture,
+ const EDirectiveUtilSelectionMethod SelectionMethod,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ const TArray SourceActors = SelectionMethod == Selection ? GetSelectedLevelActors() : GetAllLevelActors();
+
+ FilterActorsByPredicate(SourceActors, FoundActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ for (const auto Component : Actor->GetComponents())
+ {
+ const UStaticMeshComponent* StaticMeshComponent = Cast(Component);
+ if (!StaticMeshComponent) { continue; }
+ for (UMaterialInterface* Material : StaticMeshComponent->GetMaterials())
+ {
+ if (!Material || !Material->GetMaterial()) { continue; }
+ for (const auto& Expression : Material->GetMaterial()->GetExpressions())
+ {
+ if (const UMaterialExpressionTextureSample* TextureSample = Cast(Expression))
+ {
+ if (TextureSample->Texture == Texture) { return true; }
+ }
+ if (const UMaterialExpressionTextureObject* TextureObject = Cast(Expression))
+ {
+ if (TextureObject->Texture == Texture) { return true; }
+ }
+ }
+ }
+ }
+ return false;
+ });
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("%i actors with texture %s were found."),
+ FoundActors.Num(), *Texture.ToString());
+}
+
+void UDirectiveUtilEditorActorSubsystem::GetActorsByTextureName(
+ TArray& FoundActors,
+ const FString TextureName,
+ const EDirectiveUtilSelectionMethod SelectionMethod,
+ const EDirectiveUtilInclusivity Inclusivity)
+{
+ const TArray SourceActors = SelectionMethod == Selection ? GetSelectedLevelActors() : GetAllLevelActors();
+
+ FilterActorsByPredicate(SourceActors, FoundActors, Inclusivity, [&](AActor* Actor) -> bool
+ {
+ for (const auto Component : Actor->GetComponents())
+ {
+ const UStaticMeshComponent* StaticMeshComponent = Cast(Component);
+ if (!StaticMeshComponent) { continue; }
+ for (UMaterialInterface* Material : StaticMeshComponent->GetMaterials())
+ {
+ if (!Material || !Material->GetMaterial()) { continue; }
+ for (const auto& Expression : Material->GetMaterial()->GetExpressions())
+ {
+ if (const UMaterialExpressionTextureSample* TextureSample = Cast(Expression))
+ {
+ if (TextureSample->Texture.GetName().Contains(TextureName)) { return true; }
+ }
+ if (const UMaterialExpressionTextureObject* TextureObject = Cast(Expression))
+ {
+ if (TextureObject->Texture.GetName().Contains(TextureName)) { return true; }
+ }
+ }
+ }
+ }
+ return false;
+ });
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("%i actors with texture %s were found."),
+ FoundActors.Num(), *TextureName);
+}
+
+void UDirectiveUtilEditorActorSubsystem::GetInvalidActors(TArray& FoundActors)
+{
+ for (AActor* Actor : GetAllLevelActors()) { if (!IsValid(Actor)) { FoundActors.AddUnique(Actor); } }
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("%i invalid actors were found."), FoundActors.Num());
+}
+
+void UDirectiveUtilEditorActorSubsystem::PushOverrideMaterialsToSource(UStaticMeshComponent* StaticMeshComponent)
+{
+ if (!IsValid(StaticMeshComponent))
+ {
+ UE_LOG(LogDirectiveUtilEditor, Error, TEXT("Static Mesh Component is invalid."));
+ return;
+ }
+
+ UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
+ if (!IsValid(StaticMesh))
+ {
+ UE_LOG(LogDirectiveUtilEditor, Error, TEXT("Static Mesh Component has no valid static mesh."));
+ return;
+ }
+
+ const FScopedTransaction Transaction(NSLOCTEXT("DirectiveUtilities", "PushOverrideMaterialsToSource", "Push Override Materials To Source"));
+ for (int32 i = 0; i < StaticMeshComponent->GetNumMaterials(); i++)
+ {
+ if (UMaterialInterface* Material = StaticMeshComponent->GetMaterial(i))
+ {
+ StaticMesh->SetMaterial(i, Material);
+ }
+ }
+ UE_LOG(LogDirectiveUtilEditor, Display, TEXT("Materials were pushed to source for %s."), *StaticMeshComponent->GetName());
+}
diff --git a/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesEditor/Public/DirectiveUtilitiesEditor.h b/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesEditor/Public/DirectiveUtilitiesEditor.h
new file mode 100644
index 00000000..a21d5bc3
--- /dev/null
+++ b/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesEditor/Public/DirectiveUtilitiesEditor.h
@@ -0,0 +1,13 @@
+// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Modules/ModuleManager.h"
+
+class FDirectiveUtilitiesEditorModule : public IModuleInterface
+{
+public:
+ virtual void StartupModule() override;
+ virtual void ShutdownModule() override;
+};
diff --git a/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesEditor/Public/Libraries/DirectiveUtilEditorAssetLibrary.h b/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesEditor/Public/Libraries/DirectiveUtilEditorAssetLibrary.h
new file mode 100644
index 00000000..d9b14738
--- /dev/null
+++ b/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesEditor/Public/Libraries/DirectiveUtilEditorAssetLibrary.h
@@ -0,0 +1,102 @@
+// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "EditorAssetLibrary.h"
+#include "Types/DirectiveUtilEditorAssetTypes.h"
+#include "Types/DirectiveUtilTypes.h"
+#include "DirectiveUtilEditorAssetLibrary.generated.h"
+
+/**
+ * UDirectiveUtilEditorAssetLibrary
+ *
+ * Blueprint helpers for querying and managing editor assets.
+ */
+UCLASS()
+class DIRECTIVEUTILITIESEDITOR_API UDirectiveUtilEditorAssetLibrary : public UEditorAssetLibrary
+{
+ GENERATED_BODY()
+
+public:
+
+ /**
+ * Retrieve a list of asset data for the given directory.
+ * @param DirectoryPath Directory path of the asset we want the list from. (e.g., /Game/MyFolder or /MyPluginName/MyFolder)
+ * @param bRecursive The search will be recursive and will look in subfolders. Defaults to true.
+ * @return TArray List of asset data.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Directive Utilities | Editor Scripting | Asset")
+ static TArray GetAssetDataListFromDirectory(const FString& DirectoryPath, bool bRecursive = true);
+
+ /**
+ * Find and return a list of duplicate assets within the given directories.
+ * The criteria for duplication is based on the asset name and class.
+ * @param DirectoryPaths List of directory paths to search for duplicate assets.
+ * (e.g., /Game/MyFolder or /MyPluginName/MyFolder)
+ * @param bRecursive The search will be recursive and will look in subfolders. Defaults to true.
+ * @return TMap Mapped list of duplicate assets.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Directive Utilities | Editor Scripting | Asset")
+ static TMap FindDuplicateAssets(
+ const TArray& DirectoryPaths,
+ bool bRecursive = true);
+
+ /**
+ * Fixes up (and deletes) object redirectors found under the given directories, without loading every asset.
+ * Equivalent to the Content Browser's "Fix Up Redirectors in Folder", but scriptable and headless-friendly.
+ * @param DirectoryPaths Directories to scan for redirectors. If empty, the entire registry is scanned.
+ * @param OutRedirectorsProcessed [out] The number of redirectors submitted for fix-up (the engine does not report per-redirector success).
+ * @return Success if the operation ran (even if nothing needed fixing), Failure otherwise (e.g. a fixup is already in progress).
+ */
+ UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "ReturnValue"), Category = "Directive Utilities | Editor Scripting | Asset")
+ static EDirectiveUtilSuccessStatus FixUpRedirectorsInPaths(const TArray& DirectoryPaths, int32& OutRedirectorsProcessed);
+
+ /**
+ * Finds all assets of the given class using the Asset Registry (no asset loading).
+ * @param AssetClass The class to search for.
+ * @param PackagePath An optional package path to scope the search (e.g. /Game/MyFolder). Empty searches everywhere.
+ * @param bRecursiveClasses If true, also matches subclasses of AssetClass.
+ * @param bRecursivePaths If true, also searches subfolders of PackagePath.
+ * @param OutStatus [out] Success if the registry was queried successfully.
+ * @return The matching asset data.
+ */
+ UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "OutStatus"), Category = "Directive Utilities | Editor Scripting | Asset")
+ static TArray GetAssetsByClass(
+ UClass* AssetClass,
+ const FString& PackagePath,
+ bool bRecursiveClasses,
+ bool bRecursivePaths,
+ EDirectiveUtilSuccessStatus& OutStatus);
+
+ /**
+ * Returns the package paths of the assets that the given asset depends on, using the Asset Registry dependency graph.
+ * @param Asset The asset whose dependencies to retrieve.
+ * @param bHardDependenciesOnly If true, only hard (always-loaded) dependencies are returned.
+ * @param OutStatus [out] Success if the registry was queried successfully.
+ * @return The dependency package paths.
+ */
+ UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "OutStatus"), Category = "Directive Utilities | Editor Scripting | Asset")
+ static TArray GetAssetDependencies(const FAssetData& Asset, bool bHardDependenciesOnly, EDirectiveUtilSuccessStatus& OutStatus);
+
+ /**
+ * Returns the package paths of the assets that reference the given asset, using the Asset Registry dependency graph.
+ * @param Asset The asset whose referencers to retrieve.
+ * @param bHardReferencesOnly If true, only hard (always-loaded) referencers are returned.
+ * @param OutStatus [out] Success if the registry was queried successfully.
+ * @return The referencer package paths.
+ */
+ UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "OutStatus"), Category = "Directive Utilities | Editor Scripting | Asset")
+ static TArray GetAssetReferencers(const FAssetData& Asset, bool bHardReferencesOnly, EDirectiveUtilSuccessStatus& OutStatus);
+
+ /**
+ * Returns the default name a new asset of the given class would receive, honoring the project's asset naming
+ * convention overrides where the engine supports them.
+ * @note Naming-convention overrides are only consulted on UE 5.8+; on 5.6/5.7 the engine's plain default name is returned.
+ * @param AssetClass The class to resolve a default asset name for.
+ * @param OutStatus [out] Success if a non-empty name was resolved.
+ * @return The default asset name, or an empty string on failure.
+ */
+ UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "OutStatus"), Category = "Directive Utilities | Editor Scripting | Asset")
+ static FString GetDefaultAssetNameForClass(UClass* AssetClass, EDirectiveUtilSuccessStatus& OutStatus);
+};
diff --git a/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesEditor/Public/Subsystems/DirectiveUtilEditorActorSubsystem.h b/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesEditor/Public/Subsystems/DirectiveUtilEditorActorSubsystem.h
new file mode 100644
index 00000000..0623ec4f
--- /dev/null
+++ b/Plugins/DirectiveUtilities/Source/DirectiveUtilitiesEditor/Public/Subsystems/DirectiveUtilEditorActorSubsystem.h
@@ -0,0 +1,695 @@
+// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Subsystems/EditorActorSubsystem.h"
+#include "Engine/EngineTypes.h"
+#include "Types/DirectiveUtilEditorTypes.h"
+#include "DirectiveUtilEditorActorSubsystem.generated.h"
+
+class UCapsuleComponent;
+class UBoxComponent;
+class USphereComponent;
+class UStaticMeshActor;
+
+/**
+ * DirectiveUtilEditorActorSubsystem
+ *
+ * Blueprint helpers for querying and filtering actors in the editor world.
+ */
+UCLASS()
+class DIRECTIVEUTILITIESEDITOR_API UDirectiveUtilEditorActorSubsystem : public UEditorActorSubsystem
+{
+ GENERATED_BODY()
+
+public:
+
+ //-----------------------------
+ // Utilities
+ //-----------------------------
+
+ /**
+ * Focus actors in viewport.
+ * @param Actors The actors to focus.
+ * @param bInstant Enable to focus the actors instantly instead of smoothly animating.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Editor")
+ static void FocusActorsInViewport(const TArray Actors, bool bInstant = false);
+
+ /**
+ * Get all unique classes used in the level.
+ * @result Classes The list of classes.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Editor")
+ TArray GetAllLevelClasses();
+
+ //-----------------------------
+ // Filters
+ //-----------------------------
+ // Output arrays are appended to and deduplicated (existing entries are preserved).
+ // Passing the same array as input and output filters it in place.
+
+ /**
+ * Returns only the Static Mesh Actors from the provided Actor List.
+ * @param ActorsToFilter The list of Actors to filter.
+ * @param OutStaticMeshActors The list of Actors that are Static Mesh Actors.
+ */
+ virtual void FilterStaticMeshActors(TArray& OutStaticMeshActors, TArray ActorsToFilter) const;
+
+ /**
+ * Filters the provided actors based on the provided name.
+ * @param Actors The list of actors to filter.
+ * @param FilteredActors The list of actors that have been filtered.
+ * @param ActorName The name to filter by.
+ * @param Inclusivity Whether to include or exclude actors with the provided name.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Filters|Actor")
+ static void FilterActorsByName(const TArray& Actors, TArray& FilteredActors, const FString& ActorName, EDirectiveUtilInclusivity Inclusivity = Include);
+
+ /**
+ * Filter the provided actors based on the provided class.
+ * @param Actors The list of actors to filter.
+ * @param FilteredActors The list of actors that have been filtered.
+ * @param ActorClass The class to filter by.
+ * @param Inclusivity Whether to include or exclude actors with the provided class.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Filters|Actor")
+ static void FilterActorsByClass(const TArray& Actors, TArray& FilteredActors, TSubclassOf ActorClass, EDirectiveUtilInclusivity Inclusivity = Include);
+
+ /**
+ * Filter the provided actors based on the provided tags.
+ * @param Actors The list of actors to filter.
+ * @param FilteredActors The list of actors that have been filtered.
+ * @param Tag The tag to filter by.
+ * @param Inclusivity Whether to include or exclude actors with the provided tags.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Filters|Actor")
+ static void FilterActorsByTag(const TArray& Actors, TArray& FilteredActors, const FName Tag, EDirectiveUtilInclusivity Inclusivity = Include);
+
+ /**
+ * Filter the provided actors based on the provided material name.
+ * @param Actors The list of actors to filter.
+ * @param FilteredActors The list of actors that have been filtered.
+ * @param MaterialName The material name to filter by.
+ * @param MaterialSource The location to check for the material.
+ * @param Inclusivity Whether to include or exclude actors with the provided material name.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Filters|Actor")
+ static void FilterActorsByMaterialName(const TArray& Actors, TArray& FilteredActors, const FString& MaterialName, EDirectiveUtilSearchLocation MaterialSource, EDirectiveUtilInclusivity Inclusivity = Include);
+
+ /**
+ * Filter the provided actors based on the provided material reference.
+ * @param Actors The list of actors to filter.
+ * @param FilteredActors The list of actors that have been filtered.
+ * @param Material The material reference to filter by.
+ * @param MaterialSource The location to check for the material.
+ * @param Inclusivity Whether to include or exclude actors with the provided material reference.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Filters|Actor")
+ static void FilterActorsByMaterial(const TArray& Actors, TArray& FilteredActors, const TSoftObjectPtr& Material, EDirectiveUtilSearchLocation MaterialSource, EDirectiveUtilInclusivity Inclusivity = Include);
+
+ /**
+ * Filter the provided actors based on the provided static mesh name.
+ * Uses a case-insensitive substring match against the mesh asset name.
+ * @param Actors The list of actors to filter.
+ * @param FilteredActors The list of actors that have been filtered.
+ * @param StaticMeshName The static mesh name to filter by.
+ * @param Inclusivity Whether to include or exclude actors with the provided static mesh name.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Filters|Actor")
+ static void FilterActorsByStaticMeshName(const TArray& Actors, TArray& FilteredActors, const FString& StaticMeshName, EDirectiveUtilInclusivity Inclusivity = Include);
+
+ /**
+ * Filter the provided actors based on the provided static mesh reference.
+ * @param Actors The list of actors to filter.
+ * @param FilteredActors The list of actors that have been filtered.
+ * @param StaticMesh The static mesh reference to filter by.
+ * @param Inclusivity Whether to include or exclude actors with the provided static mesh reference.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Filters|Actor")
+ static void FilterActorsByStaticMesh(const TArray& Actors, TArray& FilteredActors, const TSoftObjectPtr& StaticMesh, EDirectiveUtilInclusivity Inclusivity = Include);
+
+ /**
+ * Filter the provided actors based on the provided vert count range.
+ * @param Actors The list of actors to filter.
+ * @param FilteredActors The list of actors that have been filtered.
+ * @param MinVertCount The minimum vert count to filter by.
+ * @param MaxVertCount The maximum vert count to filter by.
+ * @param Inclusivity Whether to include or exclude actors with the provided vert count range.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Filters|Actor")
+ static void FilterActorsByVertCount(const TArray& Actors, TArray& FilteredActors, int32 MinVertCount, int32 MaxVertCount, EDirectiveUtilInclusivity Inclusivity = Include);
+
+ /**
+ * Filter the provided actors based on the provided triangle count range.
+ * @param Actors The list of actors to filter.
+ * @param FilteredActors The list of actors that have been filtered.
+ * @param MinTriCount The minimum triangle count to filter by.
+ * @param MaxTriCount The maximum triangle count to filter by.
+ * @param Inclusivity Whether to include or exclude actors with the provided triangle count range.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Filters|Actor")
+ static void FilterActorsByTriCount(const TArray& Actors, TArray& FilteredActors, int32 MinTriCount, int32 MaxTriCount, EDirectiveUtilInclusivity Inclusivity = Include);
+
+ /**
+ * Filter the provided actors based on the provided actor bounds.
+ * @param Actors The list of actors to filter.
+ * @param FilteredActors The list of actors that have been filtered.
+ * @param MinBounds The minimum bounds to filter by.
+ * @param MaxBounds The maximum bounds to filter by.
+ * @param Inclusivity Whether to include or exclude actors with the provided bounds.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Filters|Actor")
+ static void FilterActorsByBounds(const TArray& Actors, TArray& FilteredActors, const FVector& MinBounds, const FVector& MaxBounds, EDirectiveUtilInclusivity Inclusivity = Include);
+
+ /**
+ * Filter the provided actors based on the provided static mesh bounds.
+ * @param Actors The list of actors to filter.
+ * @param FilteredActors The list of actors that have been filtered.
+ * @param MinBounds The minimum bounds to filter by.
+ * @param MaxBounds The maximum bounds to filter by.
+ * @param Inclusivity Whether to include or exclude actors with the provided bounds.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Filters|Actor")
+ static void FilterActorsByStaticMeshBounds(const TArray& Actors, TArray& FilteredActors, const FVector& MinBounds, const FVector& MaxBounds, EDirectiveUtilInclusivity Inclusivity = Include);
+
+ /**
+ * Filter the provided actors based on the provided world location and radius.
+ * @param Actors The list of actors to filter.
+ * @param FilteredActors The list of actors that have been filtered.
+ * @param WorldLocation The world location to filter by.
+ * @param Radius The radius to filter by.
+ * @param Inclusivity Whether to include or exclude actors with the provided world location and radius.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Filters|Actor")
+ static void FilterActorsByWorldLocation(const TArray& Actors, TArray& FilteredActors, const FVector& WorldLocation, float Radius, EDirectiveUtilInclusivity Inclusivity = Include);
+
+ /**
+ * Filter the provided actors based on the provided LOD (Level of Detail) count.
+ * @param Actors The list of actors to filter.
+ * @param FilteredActors The list of actors that have been filtered.
+ * @param MinLODs The minimum LOD count to filter by.
+ * @param MaxLODs The maximum LOD count to filter by.
+ * @param Inclusivity Whether to include or exclude actors with the provided LOD count.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Filters|Actor")
+ static void FilterActorsByLODCount(const TArray& Actors, TArray& FilteredActors, int32 MinLODs, int32 MaxLODs, EDirectiveUtilInclusivity Inclusivity = Include);
+
+ /**
+ * Filter the provided actors based on the provided Nanite state.
+ * @param Actors The list of actors to filter.
+ * @param FilteredActors The list of actors that have been filtered.
+ * @param bNaniteEnabled Whether to filter by Nanite enabled or disabled.
+ * @param Inclusivity Whether to include or exclude actors with the provided Nanite state.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Filters|Actor")
+ static void FilterActorsByNaniteState(const TArray& Actors, TArray& FilteredActors, bool bNaniteEnabled, EDirectiveUtilInclusivity Inclusivity = Include);
+
+ /**
+ * Filter the provided actors based on the provided Lightmap Resolution.
+ * @param Actors The list of actors to filter.
+ * @param FilteredActors The list of actors that have been filtered.
+ * @param MinLightmapResolution The minimum lightmap resolution to filter by.
+ * @param MaxLightmapResolution The maximum lightmap resolution to filter by.
+ * @param SearchLocation The location to search from (Actor Override and/or Static Mesh).
+ * @param Inclusivity Whether to include or exclude actors with the provided lightmap resolution.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Filters|Actor")
+ static void FilterActorsByLightmapResolution(const TArray& Actors, TArray& FilteredActors, int32 MinLightmapResolution, int32 MaxLightmapResolution, EDirectiveUtilSearchLocation SearchLocation, EDirectiveUtilInclusivity Inclusivity = Include);
+
+ /**
+ * Filter the provided actors based on the provided mobility.
+ * Tests the root component's mobility, matching the actor mobility shown in the editor UI.
+ * @param Actors The list of actors to filter.
+ * @param FilteredActors The list of actors that have been filtered.
+ * @param Mobility The mobility to filter by.
+ * @param Inclusivity Whether to include or exclude actors with the provided mobility.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Filters|Actor")
+ static void FilterActorsByMobility(const TArray& Actors, TArray& FilteredActors, EComponentMobility::Type Mobility, EDirectiveUtilInclusivity Inclusivity = Include);
+
+ /**
+ * Filter the provided actors based on the provided collision channel.
+ * @param Actors The list of actors to filter.
+ * @param FilteredActors The list of actors that have been filtered.
+ * @param CollisionChannel The collision type to filter by.
+ * @param Inclusivity Whether to include or exclude actors with the provided collision type.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Filters|Actor")
+ static void FilterActorsByCollisionChannel(const TArray& Actors, TArray& FilteredActors, ECollisionChannel CollisionChannel, EDirectiveUtilInclusivity Inclusivity = Include);
+
+ /**
+ * Filter the provided actors based on the provided collision response.
+ * @param Actors The list of actors to filter.
+ * @param FilteredActors The list of actors that have been filtered.
+ * @param CollisionChannel The collision channel to filter by.
+ * @param CollisionResponse The collision response to filter by.
+ * @param Inclusivity Whether to include or exclude actors with the provided collision response.
+ */
+ UFUNCTION(BlueprintCallable, Category = "Directive Utilities|Filters|Actor")
+ static void FilterActorsByCollisionResponse(const TArray& Actors, TArray