// Copyright (c) 2026 Unreal Directive. Licensed under the MIT License. #include "Libraries/DirectiveUtilEditorBlueprintLibrary.h" #include "AssetRegistry/DirectiveUtilAssetRegistry.h" #include "AssetRegistry/ARFilter.h" #include "AssetRegistry/IAssetRegistry.h" #include "Blueprint/BlueprintSupport.h" #include "Components/ActorComponent.h" #include "Engine/Blueprint.h" #include "Engine/BlueprintGeneratedClass.h" #include "Engine/SCS_Node.h" #include "Engine/SimpleConstructionScript.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Misc/PackageName.h" #include "UObject/Package.h" namespace { bool IsPackageInsideBlueprintSearchPath(const FName PackageName, const FName Path) { const FString Package = PackageName.ToString(); FString Parent = Path.ToString(); Parent.RemoveFromEnd(TEXT("/")); return Package == Parent || Package.StartsWith(Parent + TEXT("/")); } TArray GetBlueprintAssets(const FDirectiveUtilBlueprintSearchOptions& Options) { IAssetRegistry* AssetRegistry = IAssetRegistry::Get(); if (!AssetRegistry) { return {}; } DirectiveUtilitiesEditor::EnsureAssetRegistryScan(*AssetRegistry); FARFilter Filter; Filter.ClassPaths.Add(UBlueprint::StaticClass()->GetClassPathName()); Filter.bRecursiveClasses = true; Filter.bRecursivePaths = true; if (Options.PackagePaths.IsEmpty()) { Filter.PackagePaths.Add(TEXT("/Game")); } else { Filter.PackagePaths.Append(Options.PackagePaths); } TArray Assets; AssetRegistry->GetAssets(Filter, Assets); Assets.RemoveAll([&Options](const FAssetData& Asset) { for (const FName Path : Options.ExcludedPackagePaths) { if (IsPackageInsideBlueprintSearchPath(Asset.PackageName, Path)) { return true; } } return false; }); Assets.Sort([](const FAssetData& Left, const FAssetData& Right) { return Left.GetSoftObjectPath().LexicalLess(Right.GetSoftObjectPath()); }); return Assets; } UBlueprint* LoadBlueprint(const FAssetData& Asset) { return Cast(Asset.GetAsset()); } FString GetClassObjectPath(const UClass* Class) { return Class ? Class->GetPathName() : FString(); } bool IsDirectParent(const FAssetData& Asset, const UClass* ParentClass) { const FString ParentTag = Asset.GetTagValueRef(FBlueprintTags::ParentClassPath); return FPackageName::ExportTextPathToObjectPath(ParentTag) == GetClassObjectPath(ParentClass); } bool HasComponentClass(const UBlueprint* Blueprint, const UClass* ComponentClass, const bool bIncludeDerivedComponents) { TSet Visited; for (const UBlueprint* Current = Blueprint; Current && !Visited.Contains(Current);) { Visited.Add(Current); if (Current->SimpleConstructionScript) { for (const USCS_Node* Node : Current->SimpleConstructionScript->GetAllNodes()) { if (!Node || !Node->ComponentClass) { continue; } const bool bMatches = bIncludeDerivedComponents ? Node->ComponentClass->IsChildOf(ComponentClass) : Node->ComponentClass == ComponentClass; if (bMatches) { return true; } } } Current = Current->ParentClass ? UBlueprint::GetBlueprintFromClass(Current->ParentClass) : nullptr; } return false; } } EDirectiveUtilBlueprintCompileStatus UDirectiveUtilEditorBlueprintLibrary::GetBlueprintCompileStatus( const UBlueprint* Blueprint) { if (!IsValid(Blueprint)) { return EDirectiveUtilBlueprintCompileStatus::Unknown; } switch (Blueprint->Status) { case BS_Dirty: return EDirectiveUtilBlueprintCompileStatus::Dirty; case BS_Error: return EDirectiveUtilBlueprintCompileStatus::Error; case BS_UpToDate: return EDirectiveUtilBlueprintCompileStatus::UpToDate; case BS_BeingCreated: return EDirectiveUtilBlueprintCompileStatus::BeingCreated; case BS_UpToDateWithWarnings: return EDirectiveUtilBlueprintCompileStatus::UpToDateWithWarnings; case BS_Unknown: default: return EDirectiveUtilBlueprintCompileStatus::Unknown; } } TArray UDirectiveUtilEditorBlueprintLibrary::FindBlueprintsByCompileStatus( const EDirectiveUtilBlueprintCompileStatus CompileStatus, const FDirectiveUtilBlueprintSearchOptions& Options) { TArray Matches; for (const FAssetData& Asset : GetBlueprintAssets(Options)) { if (const UBlueprint* Blueprint = LoadBlueprint(Asset)) { if (GetBlueprintCompileStatus(Blueprint) == CompileStatus) { Matches.Add(Asset); } } } return Matches; } TArray UDirectiveUtilEditorBlueprintLibrary::FindBlueprintsByParentClass( UClass* ParentClass, const FDirectiveUtilBlueprintSearchOptions& Options, const bool bIncludeDescendants) { TArray Matches; if (!IsValid(ParentClass)) { return Matches; } for (const FAssetData& Asset : GetBlueprintAssets(Options)) { if (!bIncludeDescendants) { if (IsDirectParent(Asset, ParentClass)) { Matches.Add(Asset); } continue; } const UBlueprint* Blueprint = LoadBlueprint(Asset); if (Blueprint && Blueprint->ParentClass && Blueprint->ParentClass->IsChildOf(ParentClass)) { Matches.Add(Asset); } } return Matches; } TArray UDirectiveUtilEditorBlueprintLibrary::FindBlueprintsImplementingInterface( UClass* InterfaceClass, const FDirectiveUtilBlueprintSearchOptions& Options) { TArray Matches; if (!IsValid(InterfaceClass) || !InterfaceClass->HasAnyClassFlags(CLASS_Interface)) { return Matches; } const FString InterfacePath = GetClassObjectPath(InterfaceClass); for (const FAssetData& Asset : GetBlueprintAssets(Options)) { const FString Interfaces = Asset.GetTagValueRef(FBlueprintTags::ImplementedInterfaces); const FString ParentTag = Asset.GetTagValueRef(FBlueprintTags::ParentClassPath); UClass* TaggedParent = FindObject(nullptr, *FPackageName::ExportTextPathToObjectPath(ParentTag)); if (!Interfaces.Contains(InterfacePath) && TaggedParent && !TaggedParent->ImplementsInterface(InterfaceClass)) { continue; } const UBlueprint* Blueprint = LoadBlueprint(Asset); if (Blueprint && Blueprint->GeneratedClass && Blueprint->GeneratedClass->ImplementsInterface(InterfaceClass)) { Matches.Add(Asset); } } return Matches; } TArray UDirectiveUtilEditorBlueprintLibrary::FindBlueprintsContainingComponentClass( UClass* ComponentClass, const FDirectiveUtilBlueprintSearchOptions& Options, const bool bIncludeDerivedComponents) { TArray Matches; if (!IsValid(ComponentClass) || !ComponentClass->IsChildOf(UActorComponent::StaticClass())) { return Matches; } for (const FAssetData& Asset : GetBlueprintAssets(Options)) { if (const UBlueprint* Blueprint = LoadBlueprint(Asset)) { if (HasComponentClass(Blueprint, ComponentClass, bIncludeDerivedComponents)) { Matches.Add(Asset); } } } return Matches; } TArray UDirectiveUtilEditorBlueprintLibrary::GetUnusedBlueprintVariables( UBlueprint* Blueprint, const bool bIncludeExternallyAccessibleVariables) { TArray Names; if (!IsValid(Blueprint)) { return Names; } TArray UsedVariables; TArray UnusedVariables; FBlueprintEditorUtils::GetUsedAndUnusedVariables(Blueprint, UsedVariables, UnusedVariables); Names.Reserve(UnusedVariables.Num()); for (const FProperty* Variable : UnusedVariables) { if (Variable && (bIncludeExternallyAccessibleVariables || FBlueprintEditorUtils::IsPropertyPrivate(Variable))) { Names.Add(Variable->GetFName()); } } Names.Sort(FNameLexicalLess()); return Names; }