Files
UnrealEngineUWP/Engine/Source/Editor/UnrealEd/Private/EditorScriptingHelpers.cpp
Matt Peters 7ad238a806 AssetRegistry includes (Engine/Source): change #include "AssetData.h" -> #include "AssetRegistry/AssetData.h", and similar for the other moved AssetRegistry headers.
#rb Zousar.Shaker
#rnx
#preflight 6270509a220f89f0ad573030

[CL 20016982 by Matt Peters in ue5-main branch]
2022-05-02 18:06:48 -04:00

349 lines
11 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "EditorScriptingHelpers.h"
#include "Utils.h"
#include "Algo/Count.h"
#include "AssetRegistry/AssetData.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "Editor.h"
namespace EditorScriptingHelpersInternal
{
// Like !FName::IsValidGroupName(Path)), but with another list and no conversion to from FName
// InvalidChar may be INVALID_OBJECTPATH_CHARACTERS or INVALID_LONGPACKAGE_CHARACTERS or ...
FString RemoveFullName(const FString& AnyAssetPath, FString& OutFailureReason)
{
FString Result = AnyAssetPath.TrimStartAndEnd();
int32 NumberOfSpace = Algo::Count(AnyAssetPath, TEXT(' '));
if (NumberOfSpace == 0)
{
return MoveTemp(Result);
}
else if (NumberOfSpace > 1)
{
OutFailureReason = FString::Printf(TEXT("Can't convert path '%s' because there are too many spaces."), *AnyAssetPath);
return FString();
}
else// if (NumberOfSpace == 1)
{
int32 FoundIndex = 0;
AnyAssetPath.FindChar(TEXT(' '), FoundIndex);
check(FoundIndex > INDEX_NONE && FoundIndex < AnyAssetPath.Len()); // because of TrimStartAndEnd
// Confirm that it's a valid Class
FString ClassName = AnyAssetPath.Left(FoundIndex);
// Convert \ to /
ClassName.ReplaceInline(TEXT("\\"), TEXT("/"), ESearchCase::CaseSensitive);
// Test ClassName for invalid Char
const int32 StrLen = FCString::Strlen(INVALID_OBJECTNAME_CHARACTERS);
for (int32 Index = 0; Index < StrLen; ++Index)
{
int32 InvalidFoundIndex = 0;
if (ClassName.FindChar(INVALID_OBJECTNAME_CHARACTERS[Index], InvalidFoundIndex))
{
OutFailureReason = FString::Printf(TEXT("Can't convert the path %s because it contains invalid characters (probably spaces)."), *AnyAssetPath);
return FString();
}
}
// Return the path without the Class name
return AnyAssetPath.Mid(FoundIndex + 1);
}
}
}
bool EditorScriptingHelpers::CheckIfInEditorAndPIE()
{
if (!IsInGameThread())
{
UE_LOG(LogUtils, Error, TEXT("You are not on the main thread."));
return false;
}
if (!GIsEditor)
{
UE_LOG(LogUtils, Error, TEXT("You are not in the Editor."));
return false;
}
if (GEditor->PlayWorld || GIsPlayInEditorWorld)
{
UE_LOG(LogUtils, Error, TEXT("The Editor is currently in a play mode."));
return false;
}
return true;
}
FString EditorScriptingHelpers::ConvertAnyPathToLongPackagePath(const FString& AnyPath, FString& OutFailureReason)
{
if (AnyPath.Len() < 2) // minimal length to have /G
{
OutFailureReason = FString::Printf(TEXT("Can't convert the path '%s' because the Root path need to be specified. ie /Game/"), *AnyPath);
return FString();
}
// Prepare for TryConvertFilenameToLongPackageName
// Remove class name from Reference Path
FString TextPath = FPackageName::ExportTextPathToObjectPath(AnyPath);
// Remove class name Fullname
TextPath = EditorScriptingHelpersInternal::RemoveFullName(TextPath, OutFailureReason);
if (TextPath.IsEmpty())
{
return FString();
}
// Convert \ to /
TextPath.ReplaceInline(TEXT("\\"), TEXT("/"), ESearchCase::CaseSensitive);
FPaths::RemoveDuplicateSlashes(TextPath);
{
// Remove .
int32 ObjectDelimiterIdx;
if (TextPath.FindChar(TEXT('.'), ObjectDelimiterIdx))
{
TextPath.LeftInline(ObjectDelimiterIdx);
}
// Remove :
if (TextPath.FindChar(TEXT(':'), ObjectDelimiterIdx))
{
TextPath.LeftInline(ObjectDelimiterIdx);
}
}
// Test for invalid characters
if (!IsAValidPath(TextPath, INVALID_LONGPACKAGE_CHARACTERS, OutFailureReason))
{
return FString();
}
// Confirm that we have a valid Root Package and get the valid PackagePath /Game/MyFolder
FString PackagePath;
if (!FPackageName::TryConvertFilenameToLongPackageName(TextPath, PackagePath, &OutFailureReason))
{
return FString();
}
if (PackagePath.Len() == 0)
{
OutFailureReason = FString::Printf(TEXT("Can't convert the path '%s' because of an internal error. TryConvertFilenameToLongPackageName should have return false."), *AnyPath);
return FString();
}
if (PackagePath[0] != TEXT('/'))
{
OutFailureReason = FString::Printf(TEXT("Can't convert path '%s' because the PackagePath '%s' doesn't start with a '/'."), *AnyPath, *PackagePath);
return FString();
}
if (PackagePath[PackagePath.Len() - 1] == TEXT('/'))
{
PackagePath.RemoveAt(PackagePath.Len() - 1);
}
if (FPackageName::IsScriptPackage(PackagePath))
{
OutFailureReason = FString::Printf(TEXT("Can't convert the path '%s' because it starts with /Script/"), *AnyPath);
return FString();
}
if (FPackageName::IsMemoryPackage(PackagePath))
{
OutFailureReason = FString::Printf(TEXT("Can't convert the path '%s' because it starts with /Memory/"), *AnyPath);
return FString();
}
// Confirm that the PackagePath start with a valid root
if (!HasValidRoot(PackagePath))
{
OutFailureReason = FString::Printf(TEXT("Can't convert the path '%s' because it does not map to a root."), *AnyPath);
return FString();
}
return PackagePath;
}
bool EditorScriptingHelpers::HasValidRoot(const FString& ObjectPath)
{
FString Filename;
bool bValidRoot = true;
if (!ObjectPath.IsEmpty() && ObjectPath[ObjectPath.Len() - 1] == TEXT('/'))
{
bValidRoot = FPackageName::TryConvertLongPackageNameToFilename(ObjectPath, Filename);
}
else
{
FString ObjectPathWithSlash = ObjectPath;
ObjectPathWithSlash.AppendChar(TEXT('/'));
bValidRoot = FPackageName::TryConvertLongPackageNameToFilename(ObjectPathWithSlash, Filename);
}
return bValidRoot;
}
// Test for invalid characters
bool EditorScriptingHelpers::IsAValidPath(const FString& Path, const TCHAR* InvalidChar, FString& OutFailureReason)
{
const int32 StrLen = FCString::Strlen(InvalidChar);
for (int32 Index = 0; Index < StrLen; ++Index)
{
int32 FoundIndex = 0;
if (Path.FindChar(InvalidChar[Index], FoundIndex))
{
OutFailureReason = FString::Printf(TEXT("Can't convert the path %s because it contains invalid characters."), *Path);
return false;
}
}
if (Path.Len() > FPlatformMisc::GetMaxPathLength())
{
OutFailureReason = FString::Printf(TEXT("Can't convert the path because it is too long (%d characters). This may interfere with cooking for consoles. Unreal filenames should be no longer than %d characters. Full path value: %s"), Path.Len(), FPlatformMisc::GetMaxPathLength(), *Path);
return false;
}
return true;
}
bool EditorScriptingHelpers::IsAValidPathForCreateNewAsset(const FString& ObjectPath, FString& OutFailureReason)
{
const FString ObjectName = FPackageName::ObjectPathToObjectName(ObjectPath);
// Make sure the name is not already a class or otherwise invalid for saving
FText FailureReason;
if (!FFileHelper::IsFilenameValidForSaving(ObjectName, FailureReason))
{
OutFailureReason = FailureReason.ToString();
return false;
}
// Make sure the new name only contains valid characters
if (!FName::IsValidXName(ObjectName, INVALID_OBJECTNAME_CHARACTERS INVALID_LONGPACKAGE_CHARACTERS, &FailureReason))
{
OutFailureReason = FailureReason.ToString();
return false;
}
// Make sure we are not creating an FName that is too large
if (ObjectPath.Len() >= NAME_SIZE)
{
OutFailureReason = TEXT("This asset name is too long (") + FString::FromInt(ObjectPath.Len()) + TEXT(" characters), the maximum is ") + FString::FromInt(NAME_SIZE - 1) + TEXT(". Please choose a shorter name.");
return false;
}
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
FAssetData AssetData = AssetRegistryModule.Get().GetAssetByObjectPath(*ObjectPath);
if (AssetData.IsValid())
{
OutFailureReason = TEXT("An asset already exists at this location.");
return false;
}
return true;
}
FString EditorScriptingHelpers::ConvertAnyPathToObjectPath(const FString& AnyAssetPath, FString& OutFailureReason)
{
if (AnyAssetPath.Len() < 2) // minimal length to have /G
{
OutFailureReason = FString::Printf(TEXT("Can't convert the path '%s' because the Root path need to be specified. ie /Game/"), *AnyAssetPath);
return FString();
}
// Remove class name from Reference Path
FString TextPath = FPackageName::ExportTextPathToObjectPath(AnyAssetPath);
// Remove class name Fullname
TextPath = EditorScriptingHelpersInternal::RemoveFullName(TextPath, OutFailureReason);
if (TextPath.IsEmpty())
{
return FString();
}
// Extract the subobject path if any
FString SubObjectPath;
int32 SubObjectDelimiterIdx;
if (TextPath.FindChar(SUBOBJECT_DELIMITER_CHAR, SubObjectDelimiterIdx))
{
SubObjectPath = TextPath.Mid(SubObjectDelimiterIdx + 1);
TextPath.LeftInline(SubObjectDelimiterIdx);
}
// Convert \ to /
TextPath.ReplaceInline(TEXT("\\"), TEXT("/"), ESearchCase::CaseSensitive);
FPaths::RemoveDuplicateSlashes(TextPath);
// Get asset full name, i.e."PackageName.ObjectName:InnerAssetName.2ndInnerAssetName" from "/Game/Folder/PackageName.ObjectName:InnerAssetName.2ndInnerAssetName"
FString AssetFullName;
{
// Get everything after the last slash
int32 IndexOfLastSlash = INDEX_NONE;
TextPath.FindLastChar('/', IndexOfLastSlash);
FString Folders = TextPath.Left(IndexOfLastSlash);
// Test for invalid characters
if (!IsAValidPath(Folders, INVALID_LONGPACKAGE_CHARACTERS, OutFailureReason))
{
return FString();
}
AssetFullName = TextPath.Mid(IndexOfLastSlash + 1);
}
// Get the object name
FString ObjectName = FPackageName::ObjectPathToObjectName(AssetFullName);
if (ObjectName.IsEmpty())
{
OutFailureReason = FString::Printf(TEXT("Can't convert the path '%s' because it doesn't contain an asset name."), *AnyAssetPath);
return FString();
}
// Test for invalid characters
if (!IsAValidPath(ObjectName, INVALID_OBJECTNAME_CHARACTERS, OutFailureReason))
{
return FString();
}
// Confirm that we have a valid Root Package and get the valid PackagePath /Game/MyFolder/MyAsset
FString PackagePath;
if (!FPackageName::TryConvertFilenameToLongPackageName(TextPath, PackagePath, &OutFailureReason))
{
return FString();
}
if (PackagePath.Len() == 0)
{
OutFailureReason = FString::Printf(TEXT("Can't convert path '%s' because the PackagePath is empty."), *AnyAssetPath);
return FString();
}
if (PackagePath[0] != TEXT('/'))
{
OutFailureReason = FString::Printf(TEXT("Can't convert path '%s' because the PackagePath '%s' doesn't start with a '/'."), *AnyAssetPath, *PackagePath);
return FString();
}
FString ObjectPath = FString::Printf(TEXT("%s.%s"), *PackagePath, *ObjectName);
if (FPackageName::IsScriptPackage(ObjectPath))
{
OutFailureReason = FString::Printf(TEXT("Can't convert the path '%s' because it start with /Script/"), *AnyAssetPath);
return FString();
}
if (FPackageName::IsMemoryPackage(ObjectPath))
{
OutFailureReason = FString::Printf(TEXT("Can't convert the path '%s' because it start with /Memory/"), *AnyAssetPath);
return FString();
}
// Confirm that the PackagePath starts with a valid root
if (!HasValidRoot(PackagePath))
{
OutFailureReason = FString::Printf(TEXT("Can't convert the path '%s' because it does not map to a root."), *AnyAssetPath);
return FString();
}
return ObjectPath;
}