Files
UnrealEngine/Engine/Source/Editor/UnrealEd/Private/EditorObject.cpp
2025-05-18 13:04:45 +08:00

1766 lines
61 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
EditorObject.cpp: Unreal Editor object manipulation code.
=============================================================================*/
#include "CoreMinimal.h"
#include "Misc/CoreMisc.h"
#include "Misc/Paths.h"
#include "Misc/FeedbackContext.h"
#include "UObject/ObjectMacros.h"
#include "UObject/Object.h"
#include "UObject/Class.h"
#include "UObject/UnrealType.h"
#include "UObject/OverridableManager.h"
#include "UObject/PropertyPortFlags.h"
#include "Serialization/ArchiveReplaceObjectRef.h"
#include "Misc/AsciiSet.h"
#include "Misc/PackageName.h"
#include "Components/ActorComponent.h"
#include "GameFramework/Actor.h"
#include "Components/PrimitiveComponent.h"
#include "Model.h"
#include "Engine/Brush.h"
#include "Editor/EditorEngine.h"
#include "Factories/ModelFactory.h"
#include "GameFramework/Volume.h"
#include "Editor.h"
#include "BSPOps.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "FoliageType.h"
#include "InstancedFoliageActor.h"
#include "InstancedFoliage.h"
#include "Components/BrushComponent.h"
#include "Algo/Transform.h"
#include "UObject/OverriddenPropertySet.h"
DEFINE_LOG_CATEGORY_STATIC(LogEditorObject, Log, All);
/*
Subobject Terms -
Much of the confusion in dealing with subobjects and instancing can be traced to the ambiguity of the words used to work with the various concepts.
A standardized method of referring to these terms is highly recommended - it makes the code much more consistent, and well thought-out variable names
make the concepts and especially the relationships between each of the concepts easier to grasp. This will become even more apparent once archetypes
and prefabs are implemented.
Once we've decided on standard terms, we should try to use these words as the name for any variables which refer to the associated concept, in any
code that deals with that concept (where possible).
Here are some terms I came up with for starters. If you're reading this, and you have a more appropriate name for one of these concepts, feel that any
of the descriptions or terms isn't clear enough, or know of a concept that isn't represented here, feel free to modify this comment and update
the appropriate code, if applicable.
Instance:
a UObject that has been instanced from a subobject template
Template (or template object):
the UObject associated with [or created by] an inline subobject definition; stored in the UClass's Defaults array (in the case of a .h subobject).
TemplateName:
the name of the template object
TemplateClass:
the class of the Template object
TemplateOwner:
the UObject that contains the template object; when dealing with templates created via inline subobject
definitions, this corresponds to the class that contains the Begin Object block for the template
SubobjectRoot:
when dealing with nested subobjects, corresponds to the top-most Outer that is not a subobject or template (generally
the same as Outer)
*/
class FDefaultPropertiesContextSupplier : public FContextSupplier
{
public:
/** the current line number */
int32 CurrentLine;
/** the package we're processing */
FString PackageName;
/** the class we're processing */
FString ClassName;
FString GetContext()
{
return FString::Printf
(
TEXT("%sDevelopment/Src/%s/Classes/%s.h(%i)"),
*FPaths::RootDir(),
*PackageName,
*ClassName,
CurrentLine
);
}
FDefaultPropertiesContextSupplier() {}
FDefaultPropertiesContextSupplier( const TCHAR* Package, const TCHAR* Class, int32 StartingLine )
: CurrentLine(StartingLine), PackageName(Package), ClassName(Class)
{
}
};
static FDefaultPropertiesContextSupplier* ContextSupplier = NULL;
void UEditorEngine::RenameObject(UObject* Object,UObject* NewOuter,const TCHAR* NewName, ERenameFlags Flags)
{
Object->Rename(NewName, NewOuter, Flags);
Object->SetFlags(RF_Public | RF_Standalone);
Object->MarkPackageDirty();
}
static void RemapProperty(FProperty* Property, int32 Index, const TMap<FSoftObjectPath, UObject*>& ObjectRemapper, uint8* DestData)
{
if (FObjectPropertyBase* ObjectProperty = CastField<FObjectPropertyBase>(Property))
{
// If there's a concrete index, use that, otherwise iterate all array members (for the case that this property is inside a struct, or there is exactly one element)
const int32 Num = (Index == INDEX_NONE) ? ObjectProperty->ArrayDim : 1;
const int32 StartIndex = (Index == INDEX_NONE) ? 0 : Index;
for (int32 Count = 0; Count < Num; Count++)
{
uint8* PropertyAddr = ObjectProperty->ContainerPtrToValuePtr<uint8>(DestData, StartIndex + Count);
UObject* Object = ObjectProperty->GetObjectPropertyValue(PropertyAddr);
if (Object)
{
UObject* const* RemappedObject = ObjectRemapper.Find(Object);
if (RemappedObject && (*RemappedObject)->GetClass()->IsChildOf(ObjectProperty->PropertyClass))
{
ObjectProperty->SetObjectPropertyValue(PropertyAddr, *RemappedObject);
}
}
else if (FSoftObjectProperty* SoftObjectProperty = CastField<FSoftObjectProperty>(Property))
{
const FSoftObjectPtr& SoftObjectPtr = SoftObjectProperty->GetPropertyValue(PropertyAddr);
UObject* const* RemappedObject = ObjectRemapper.Find(SoftObjectPtr.ToSoftObjectPath());
if (RemappedObject && (*RemappedObject)->GetClass()->IsChildOf(SoftObjectProperty->PropertyClass))
{
SoftObjectProperty->SetObjectPropertyValue(PropertyAddr, *RemappedObject);
}
}
}
}
else if (FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Property))
{
FScriptArrayHelper ArrayHelper(ArrayProperty, ArrayProperty->ContainerPtrToValuePtr<void>(DestData));
if (Index != INDEX_NONE)
{
RemapProperty(ArrayProperty->Inner, INDEX_NONE, ObjectRemapper, ArrayHelper.GetRawPtr(Index));
}
else
{
for (int32 ArrayIndex = 0; ArrayIndex < ArrayHelper.Num(); ArrayIndex++)
{
RemapProperty(ArrayProperty->Inner, INDEX_NONE, ObjectRemapper, ArrayHelper.GetRawPtr(ArrayIndex));
}
}
}
else if (FStructProperty* StructProperty = CastField<FStructProperty>(Property))
{
if (StructProperty->Struct == TBaseStructure<FSoftObjectPath>::Get())
{
// If there's a concrete index, use that, otherwise iterate all array members (for the case that this property is inside a struct, or there is exactly one element)
const int32 Num = (Index == INDEX_NONE) ? StructProperty->ArrayDim : 1;
const int32 StartIndex = (Index == INDEX_NONE) ? 0 : Index;
for (int32 Count = 0; Count < Num; Count++)
{
FSoftObjectPath* SoftObjectPathPtr = StructProperty->ContainerPtrToValuePtr<FSoftObjectPath>(DestData, StartIndex + Count);
if (UObject* const* RemappedObject = ObjectRemapper.Find(*SoftObjectPathPtr))
{
*SoftObjectPathPtr = *RemappedObject;
}
}
}
else if (Index != INDEX_NONE)
{
// If a concrete index was given, remap just that
for (TFieldIterator<FProperty> It(StructProperty->Struct); It; ++It)
{
RemapProperty(*It, INDEX_NONE, ObjectRemapper, StructProperty->ContainerPtrToValuePtr<uint8>(DestData, Index));
}
}
else
{
// If no concrete index was given, either the ArrayDim is 1 (i.e. not a static array), or the struct is within
// a deeper structure (an array or another struct) and we cannot know which element was changed, so iterate through all elements.
for (int32 Count = 0; Count < StructProperty->ArrayDim; Count++)
{
for (TFieldIterator<FProperty> It(StructProperty->Struct); It; ++It)
{
RemapProperty(*It, INDEX_NONE, ObjectRemapper, StructProperty->ContainerPtrToValuePtr<uint8>(DestData, Count));
}
}
}
}
}
//
// ImportProperties
//
bool HandleLineNumber(const TCHAR* Str)
{
int32 NewLineNumber;
if (FParse::Value(Str, TEXT("linenumber="), NewLineNumber))
{
if (ContextSupplier != nullptr)
{
ContextSupplier->CurrentLine = NewLineNumber;
}
return true;
}
return false;
}
static bool IsEndOfProperties(const TCHAR* Str, int32 Depth)
{
return GetEND(&Str, TEXT("Actor")) || GetEND(&Str, TEXT("DefaultProperties")) || GetEND(&Str, TEXT("structdefaultproperties")) || (GetEND(&Str, TEXT("Object")) && Depth);
}
/**
* Parse and import text as property values for the object specified. This function should never be called directly - use ImportObjectProperties instead.
*
* @param ObjectStruct the struct for the data we're importing
* @param DestData the location to import the property values to
* @param SourceText pointer to a buffer containing the values that should be parsed and imported
* @param SubobjectRoot when dealing with nested subobjects, corresponds to the top-most outer that
* is not a subobject/template
* @param SubobjectOuter the outer to use for creating subobjects/components. NULL when importing structdefaultproperties
* @param Warn output device to use for log messages
* @param Depth current nesting level
* @param InstanceGraph contains the mappings of instanced objects and components to their templates
* @param ObjectRemapper a map of existing objects, typically actors, to new instances, used to replace internal references when a number of objects are copy+pasted
*
* @return NULL if the default values couldn't be imported
*/
static const TCHAR* ImportProperties(
uint8* DestData,
const TCHAR* SourceText,
UStruct* ObjectStruct,
UObject* SubobjectRoot,
UObject* SubobjectOuter,
FFeedbackContext* Warn,
int32 Depth,
FObjectInstancingGraph& InstanceGraph,
TMap<FSoftObjectPath, UObject*>* ObjectRemapper
)
{
check(ObjectStruct!=NULL);
check(DestData!=NULL);
if ( SourceText == NULL )
{
return NULL;
}
FOverriddenPropertySet* OverriddenProperties = nullptr;
if (SubobjectOuter && FOverridableSerializationLogic::HasCapabilities(FOverridableSerializationLogic::ECapabilities::T3DSerialization))
{
OverriddenProperties = FOverridableManager::Get().GetOverriddenProperties(SubobjectOuter);
}
FEnableOverridableSerializationScope Scope(OverriddenProperties != nullptr, OverriddenProperties);
// Cannot create subobjects when importing struct defaults, or if SubobjectOuter (used as the Outer for any subobject declarations encountered) is NULL
bool bSubObjectsAllowed = !ObjectStruct->IsA(UScriptStruct::StaticClass()) && SubobjectOuter != NULL;
// true when DestData corresponds to a subobject in a class default object
bool bSubObject = false;
UClass* ComponentOwnerClass = NULL;
if ( bSubObjectsAllowed )
{
bSubObject = SubobjectRoot != NULL && SubobjectRoot->HasAnyFlags(RF_ClassDefaultObject);
if ( SubobjectRoot == NULL )
{
SubobjectRoot = SubobjectOuter;
}
ComponentOwnerClass = SubobjectOuter != NULL
? SubobjectOuter->IsA(UClass::StaticClass())
? CastChecked<UClass>(SubobjectOuter)
: SubobjectOuter->GetClass()
: NULL;
}
// The PortFlags to use for all ImportText calls
uint32 PortFlags = PPF_Delimited | PPF_CheckReferences;
if (GIsImportingT3D)
{
PortFlags |= PPF_AttemptNonQualifiedSearch;
}
FString StrLine;
TArray<FDefinedProperty> DefinedProperties;
// Parse all objects stored in the actor.
// Build list of all text properties.
bool ImportedBrush = 0;
int32 LinesConsumed = 0;
while (FParse::LineExtended(&SourceText, StrLine, LinesConsumed, FParse::ELineExtendedFlags::OldExactMode))
{
// remove extra whitespace and optional semicolon from the end of the line
{
int32 Length = StrLine.Len();
while ( Length > 0 &&
(StrLine[Length - 1] == TCHAR(';') || StrLine[Length - 1] == TCHAR(' ') || StrLine[Length - 1] == 9) )
{
Length--;
}
if (Length != StrLine.Len())
{
StrLine.LeftInline(Length, EAllowShrinking::No);
}
}
if ( ContextSupplier != NULL )
{
ContextSupplier->CurrentLine += LinesConsumed;
}
if (StrLine.Len() == 0)
{
continue;
}
const TCHAR* Str = *StrLine;
if (HandleLineNumber(Str))
{
continue;
}
else if( GetBEGIN(&Str,TEXT("Brush")) && ObjectStruct->IsChildOf(ABrush::StaticClass()) )
{
// If SubobjectOuter is NULL, we are importing defaults for a UScriptStruct's defaultproperties block
if ( !bSubObjectsAllowed )
{
Warn->Logf(ELogVerbosity::Error, TEXT("BEGIN BRUSH: Subobjects are not allowed in this context"));
return NULL;
}
// Parse brush on this line.
TCHAR BrushName[NAME_SIZE];
if( FParse::Value( Str, TEXT("Name="), BrushName, NAME_SIZE ) )
{
// If an initialized brush with this name already exists in the level, rename the existing one.
// It is deemed to be initialized if it has a non-zero poly count.
// If it is uninitialized, the existing object will have been created by a forward reference in the import text,
// and it will now be redefined. This relies on the behavior that NewObject<> will return an existing pointer
// if an object with the same name and outer is passed.
UModel* ExistingBrush = FindObject<UModel>( SubobjectRoot, BrushName );
if (ExistingBrush && ExistingBrush->Polys && ExistingBrush->Polys->Element.Num() > 0)
{
ExistingBrush->Rename();
}
// Create model.
UModelFactory* ModelFactory = NewObject<UModelFactory>();
ModelFactory->FactoryCreateText(UModel::StaticClass(), SubobjectRoot, FName(BrushName, FNAME_Add), RF_NoFlags, NULL, TEXT("t3d"), SourceText, SourceText+FCString::Strlen(SourceText), Warn);
ImportedBrush = 1;
}
}
else if (GetBEGIN(&Str, TEXT("Foliage")))
{
UFoliageType* SourceFoliageType;
FName ComponentName;
if (SubobjectRoot &&
ParseObject<UFoliageType>(Str, TEXT("FoliageType="), SourceFoliageType, nullptr) &&
FParse::Value(Str, TEXT("Component="), ComponentName) )
{
UPrimitiveComponent* ActorComponent = FindObjectFast<UPrimitiveComponent>(SubobjectRoot, ComponentName);
if (ActorComponent && ActorComponent->GetComponentLevel())
{
UWorld* World = ActorComponent->GetWorld();
TMap<AInstancedFoliageActor*, TArray<FFoliageInstance>> FoliageInstances;
const TCHAR* StrPtr;
FString TextLine;
while (FParse::Line(&SourceText, TextLine))
{
StrPtr = *TextLine;
if (GetEND(&StrPtr, TEXT("Foliage")))
{
break;
}
// Parse the instance properties
FFoliageInstance Instance;
FString Temp;
if (FParse::Value(StrPtr, TEXT("Location="), Temp, false))
{
FVector Location;
GetFVECTOR(*Temp, Location);
Instance.Location = Location;
}
if (FParse::Value(StrPtr, TEXT("Rotation="), Temp, false))
{
GetFROTATOR(*Temp, Instance.Rotation, 1);
}
if (FParse::Value(StrPtr, TEXT("PreAlignRotation="), Temp, false))
{
GetFROTATOR(*Temp, Instance.PreAlignRotation, 1);
}
if (FParse::Value(StrPtr, TEXT("DrawScale3D="), Temp, false))
{
FVector DrawScale3D;
GetFVECTOR(*Temp, DrawScale3D);
Instance.DrawScale3D = (FVector3f)DrawScale3D;
}
FParse::Value(StrPtr, TEXT("Flags="), Instance.Flags);
Instance.BaseComponent = ActorComponent;
if (AInstancedFoliageActor* IFA = AInstancedFoliageActor::Get(World, true, ActorComponent->GetComponentLevel(), Instance.Location))
{
FoliageInstances.FindOrAdd(IFA).Add(MoveTemp(Instance));
}
}
for (const auto& Pair : FoliageInstances)
{
AInstancedFoliageActor* IFA = Pair.Key;
FFoliageInfo* MeshInfo = nullptr;
UFoliageType* FoliageType = IFA->AddFoliageType(SourceFoliageType, &MeshInfo);
TArray<const FFoliageInstance*> InstancePtrs;
InstancePtrs.Reserve(Pair.Value.Num());
Algo::Transform(Pair.Value, InstancePtrs, [](const FFoliageInstance& FoliageInstance) { return &FoliageInstance; });
if (MeshInfo)
{
MeshInfo->AddInstances(FoliageType, InstancePtrs);
}
}
}
}
}
else if( GetBEGIN(&Str,TEXT("Object")))
{
// If SubobjectOuter is NULL, we are importing defaults for a UScriptStruct's defaultproperties block
if ( !bSubObjectsAllowed )
{
Warn->Logf(ELogVerbosity::Error, TEXT("BEGIN OBJECT: Subobjects are not allowed in this context"));
return NULL;
}
// Parse subobject default properties.
// Note: default properties subobjects have compiled class as their Outer (used for localization).
UClass* TemplateClass = NULL;
bool bInvalidClass = false;
ParseObject<UClass>(Str, TEXT("Class="), TemplateClass, nullptr, &bInvalidClass);
if (bInvalidClass)
{
Warn->Logf(ELogVerbosity::Error,TEXT("BEGIN OBJECT: Invalid class specified: %s"), *StrLine);
return NULL;
}
// parse the name of the template
FName TemplateName = NAME_None;
FParse::Value(Str,TEXT("Name="),TemplateName);
if(TemplateName == NAME_None)
{
Warn->Logf(ELogVerbosity::Error,TEXT("BEGIN OBJECT: Must specify valid name for subobject/component: %s"), *StrLine);
return NULL;
}
FString ExportedObjectFullName;
const bool bExportPath = FParse::Value(Str, TEXT("ExportPath="), ExportedObjectFullName);
// points to the parent class's template subobject/component, if we are overriding a subobject/component declared in our parent class
UObject* BaseTemplate = NULL;
bool bRedefiningSubobject = false;
if( TemplateClass )
{
}
else
{
// next, verify that a template actually exists in the parent class
UClass* ParentClass = ComponentOwnerClass->GetSuperClass();
check(ParentClass);
UObject* ParentCDO = ParentClass->GetDefaultObject();
check(ParentCDO);
BaseTemplate = StaticFindObjectFast(UObject::StaticClass(), SubobjectOuter, TemplateName);
bRedefiningSubobject = (BaseTemplate != NULL);
if (BaseTemplate == NULL)
{
BaseTemplate = StaticFindObjectFast(UObject::StaticClass(), ParentCDO, TemplateName);
}
if ( BaseTemplate == NULL )
{
// wasn't found
Warn->Logf(ELogVerbosity::Error, TEXT("BEGIN OBJECT: No base template named %s found in parent class %s: %s"), *TemplateName.ToString(), *ParentClass->GetName(), *StrLine);
return NULL;
}
TemplateClass = BaseTemplate->GetClass();
}
// because the outer won't be a default object
checkSlow(TemplateClass != NULL);
if (bRedefiningSubobject)
{
// since we're redefining an object in the same text block, only need to import properties again
SourceText = ImportObjectProperties( (uint8*)BaseTemplate, SourceText, TemplateClass, SubobjectRoot, BaseTemplate,
Warn, Depth + 1, ContextSupplier ? ContextSupplier->CurrentLine : 0, &InstanceGraph, ObjectRemapper );
// If the object import fails, early out and stop parsing.
if (SourceText == nullptr)
{
Warn->Logf(ELogVerbosity::Error, TEXT("BEGIN OBJECT: Could not import properties from sub-object %s."), *SubobjectRoot->GetName());
return nullptr;
}
}
else
{
FName OverriddenOperationName = NAME_None;
const bool bParsed = FParse::Value(Str,TEXT("OverriddenOperation="), OverriddenOperationName);
const bool bOverrideT3DSupported = FOverridableSerializationLogic::HasCapabilities(FOverridableSerializationLogic::ECapabilities::T3DSerialization);
TOptional<EOverriddenPropertyOperation> OverriddenOperation = bParsed && bOverrideT3DSupported ? GetOverriddenOperationFromName(OverriddenOperationName) : TOptional<EOverriddenPropertyOperation>();
UObject* Archetype = NULL;
UObject* ComponentTemplate = NULL;
// Since we are changing the class we can't use the Archetype,
// however that is fine since we will have been editing the CDO anyways
if (!FBlueprintEditorUtils::IsAnonymousBlueprintClass(SubobjectOuter->GetClass()))
{
// if an archetype was specified in the Begin Object block, use that as the template for the ConstructObject call.
FString ArchetypeName;
if (FParse::Value(Str, TEXT("Archetype="), ArchetypeName))
{
// if given a name, break it up along the ' so separate the class from the name
FString ObjectClass;
FString ObjectPath;
if ( FPackageName::ParseExportTextPath(ArchetypeName, &ObjectClass, &ObjectPath) )
{
UClass* ArchetypeClass = nullptr;
// find the class
if (FPackageName::IsValidObjectPath(ObjectClass))
{
ArchetypeClass = (UClass*)StaticFindObject(UClass::StaticClass(), nullptr, *ObjectClass);
}
else
{
// The text might be from before the full path of the class was exported (ensure if the name is ambiguous)
ArchetypeClass = (UClass*)StaticFindFirstObject(UClass::StaticClass(), *ObjectClass, EFindFirstObjectOptions::NativeFirst | EFindFirstObjectOptions::EnsureIfAmbiguous, ELogVerbosity::Warning);
}
if (ArchetypeClass)
{
ObjectPath = ObjectPath.TrimQuotes();
// if we had the class, find the archetype
if (!FPackageName::IsShortPackageName(ObjectPath))
{
Archetype = StaticFindObject(ArchetypeClass, nullptr, *ObjectPath);
}
else
{
Archetype = StaticFindFirstObject(ArchetypeClass, *ObjectPath, EFindFirstObjectOptions::NativeFirst | EFindFirstObjectOptions::EnsureIfAmbiguous);
}
}
}
}
}
if (SubobjectOuter->HasAnyFlags(RF_ClassDefaultObject))
{
if (!Archetype) // if an archetype was specified explicitly, we will stick with that
{
Archetype = ComponentOwnerClass->GetDefaultSubobjectByName(TemplateName);
if(Archetype)
{
if ( BaseTemplate == NULL )
{
// BaseTemplate should only be NULL if the Begin Object line specified a class
Warn->Logf(ELogVerbosity::Error, TEXT("BEGIN OBJECT: The component name %s is already used (if you want to override the component, don't specify a class): %s"), *TemplateName.ToString(), *StrLine);
return NULL;
}
// the component currently in the component template map and the base template should be the same
checkf(Archetype==BaseTemplate,TEXT("OverrideComponent: '%s' BaseTemplate: '%s'"), *Archetype->GetFullName(), *BaseTemplate->GetFullName());
}
}
}
else // handle the non-template case (subobjects and non-template components)
{
ComponentTemplate = FindObject<UObject>(SubobjectOuter, *TemplateName.ToString());
if (ComponentTemplate != NULL)
{
// if we're overriding a subobject declared in a parent class, we should already have an object with that name that
// was instanced when ComponentOwnerClass's CDO was initialized; if so, it's archetype should be the BaseTemplate. If it
// isn't, then there are two unrelated subobject definitions using the same name.
if ( ComponentTemplate->GetArchetype() != BaseTemplate )
{
}
else if ( BaseTemplate == NULL )
{
// BaseTemplate should only be NULL if the Begin Object line specified a class
Warn->Logf(ELogVerbosity::Error, TEXT("BEGIN OBJECT: A subobject named %s is already declared in a parent class. If you intended to override that subobject, don't specify a class in the derived subobject definition: %s"), *TemplateName.ToString(), *StrLine);
return NULL;
}
}
}
// Propagate object flags to the sub object.
EObjectFlags NewFlags = SubobjectOuter->GetMaskedFlags( RF_PropagateToSubObjects );
if (!Archetype) // no override and we didn't find one from the class table, so go with the base
{
Archetype = BaseTemplate;
}
UObject* OldComponent = NULL;
if (ComponentTemplate)
{
bool bIsOkToReuse = ComponentTemplate->GetClass() == TemplateClass
&& ComponentTemplate->GetOuter() == SubobjectOuter
&& ComponentTemplate->GetFName() == TemplateName
&& (ComponentTemplate->GetArchetype() == Archetype || !Archetype);
if (!bIsOkToReuse)
{
UE_LOG(LogEditorObject, Log, TEXT("Could not reuse component instance %s, name clash?"), *ComponentTemplate->GetFullName());
ComponentTemplate->Rename(nullptr, nullptr, REN_DontCreateRedirectors); // just abandon the existing component, we are going to create
OldComponent = ComponentTemplate;
ComponentTemplate = NULL;
}
}
if (!ComponentTemplate)
{
ComponentTemplate = NewObject<UObject>(
SubobjectOuter,
TemplateClass,
TemplateName,
NewFlags,
Archetype,
!!SubobjectOuter,
&InstanceGraph
);
}
else
{
// We do not want to set RF_Transactional for construction script created components, so we have to monkey with things here
if (NewFlags & RF_Transactional)
{
UActorComponent* Component = Cast<UActorComponent>(ComponentTemplate);
if (Component && Component->IsCreatedByConstructionScript())
{
NewFlags &= ~RF_Transactional;
}
}
// Ensure DefaultSubojbect flag persists through the clearing of flags
if (ComponentTemplate->HasAllFlags(RF_DefaultSubObject))
{
NewFlags |= RF_DefaultSubObject;
}
// fix crash when comment below is true as it is now forbidden to clear RF_PendingKill using ClearFlags (see UObjectBaseUtility.h)
ComponentTemplate->ClearGarbage();
// Make sure desired flags are set - existing object could be pending kill
ComponentTemplate->ClearGarbage();
ComponentTemplate->ClearFlags(RF_AllFlags);
ComponentTemplate->ClearInternalFlags(EInternalObjectFlags_AllFlags);
ComponentTemplate->SetFlags(NewFlags);
}
// replace all properties in this subobject outer' class that point to the original subobject with the new subobject
TMap<UObject*, UObject*> ReplacementMap;
if (Archetype)
{
checkSlow(ComponentTemplate->GetArchetype() == Archetype);
ReplacementMap.Add(Archetype, ComponentTemplate);
InstanceGraph.AddNewInstance(ComponentTemplate, Archetype);
}
if (OldComponent)
{
ReplacementMap.Add(OldComponent, ComponentTemplate);
}
FArchiveReplaceObjectRef<UObject> ReplaceAr(SubobjectOuter, ReplacementMap, EArchiveReplaceObjectFlags::IgnoreArchetypeRef);
if (bExportPath && ObjectRemapper)
{
ObjectRemapper->Add(MoveTemp(ExportedObjectFullName), ComponentTemplate);
}
if (bOverrideT3DSupported)
{
if (OverriddenOperation.IsSet())
{
FOverridableManager::Get().RestoreOverrideOperation(ComponentTemplate, OverriddenOperation.GetValue(), /*bNeedsSubObjectTemplateInstantiation*/true);
}
else
{
FOverridableManager::Get().Disable(ComponentTemplate);
}
}
// import the properties for the subobject
SourceText = ImportObjectProperties(
(uint8*)ComponentTemplate,
SourceText,
TemplateClass,
SubobjectRoot,
ComponentTemplate,
Warn,
Depth+1,
ContextSupplier ? ContextSupplier->CurrentLine : 0,
&InstanceGraph,
ObjectRemapper
);
// If the object import fails, early out and stop parsing.
if (SourceText == nullptr)
{
Warn->Logf(ELogVerbosity::Error, TEXT("BEGIN OBJECT: Could not import properties from sub-object %s."), *SubobjectRoot->GetName());
return nullptr;
}
}
}
else if( FParse::Command(&Str,TEXT("CustomProperties")))
{
check(SubobjectOuter);
SubobjectOuter->ImportCustomProperties(Str, Warn);
}
else if (IsEndOfProperties(Str, Depth))
{
// End of properties.
break;
}
else if( GetREMOVE(&Str,TEXT("Component")) )
{
checkf(false, TEXT("Remove component is illegal in pasted text"));
}
else
{
// Property.
FProperty::ImportSingleProperty(Str, DestData, ObjectStruct, SubobjectOuter, PortFlags, Warn, DefinedProperties);
}
}
if (ObjectRemapper)
{
for (const auto& DefinedProperty : DefinedProperties)
{
RemapProperty(DefinedProperty.Property, DefinedProperty.Index, *ObjectRemapper, DestData);
}
}
// Prepare brush.
if( ImportedBrush && ObjectStruct->IsChildOf<ABrush>() && !ObjectStruct->IsChildOf<AVolume>() )
{
check(GIsEditor);
ABrush* Actor = (ABrush*)DestData;
check(Actor->GetBrushComponent());
if( Actor->GetBrushComponent()->Mobility == EComponentMobility::Static )
{
// Prepare static brush.
Actor->SetNotForClientOrServer();
}
else
{
// Prepare moving brush.
FBSPOps::csgPrepMovingBrush( Actor );
}
}
return SourceText;
}
/**
* Parse and import text as property values for the object specified.
*
* @param InParams Parameters for object import; see declaration of FImportObjectParams.
*
* @return NULL if the default values couldn't be imported
*/
const TCHAR* ImportObjectProperties( FImportObjectParams& InParams )
{
FDefaultPropertiesContextSupplier Supplier;
if ( InParams.LineNumber != INDEX_NONE )
{
if ( InParams.SubobjectRoot == NULL )
{
Supplier.PackageName = InParams.ObjectStruct->GetOwnerClass() ? InParams.ObjectStruct->GetOwnerClass()->GetOutermost()->GetName() : InParams.ObjectStruct->GetOutermost()->GetName();
Supplier.ClassName = InParams.ObjectStruct->GetOwnerClass() ? InParams.ObjectStruct->GetOwnerClass()->GetName() : FName(NAME_None).ToString();
Supplier.CurrentLine = InParams.LineNumber;
ContextSupplier = &Supplier; //-V506
}
else
{
if ( ContextSupplier != NULL )
{
ContextSupplier->CurrentLine = InParams.LineNumber;
}
}
InParams.Warn->SetContext(ContextSupplier);
}
if ( InParams.bShouldCallEditChange && InParams.SubobjectOuter != NULL )
{
InParams.SubobjectOuter->PreEditChange(NULL);
}
// When importing hierarchies of objects with instanced sub-objects we don't want to use the
// instancing graph of our outer objects since we need our dedicated instances.
// For example:
//
// A_CDO { SubObj }
// PastedObj { A_1{ SubObj_1 } A_2{ SubObj_1 } }
//
// by sharing the instancing graph, we end up with both SubObj_1 being the same pointer
// by using a dedicated instancing graph, both SubObj_1 are newly created object pointer
//
// Ideally this rule should be applied more widely but for now we limit it to the Prefabs
// which is identified by using overridable serialization.
const bool bShouldUseDedicatedInstancingGraph = FOverridableManager::Get().IsEnabled(InParams.SubobjectRoot);
FObjectInstancingGraph TempGraph;
FObjectInstancingGraph& InstanceGraph = InParams.InInstanceGraph && !bShouldUseDedicatedInstancingGraph ? *InParams.InInstanceGraph : TempGraph;
if (bShouldUseDedicatedInstancingGraph)
{
InstanceGraph.SetDestinationRoot(InParams.SubobjectOuter);
}
else if ( InParams.SubobjectRoot && InParams.SubobjectRoot != UObject::StaticClass()->GetDefaultObject() )
{
InstanceGraph.SetDestinationRoot(InParams.SubobjectRoot);
}
// Parse the object properties.
const TCHAR* NewSourceText =
ImportProperties(
InParams.DestData,
InParams.SourceText,
InParams.ObjectStruct,
InParams.SubobjectRoot,
InParams.SubobjectOuter,
InParams.Warn,
InParams.Depth,
InstanceGraph,
InParams.ObjectRemapper
);
if ( InParams.SubobjectOuter != NULL )
{
check(InParams.SubobjectRoot);
// Update the object properties to point to the newly imported component objects.
// Templates inside classes never need to have components instanced.
if ( !InParams.SubobjectRoot->HasAnyFlags(RF_ClassDefaultObject) )
{
UObject* SubobjectArchetype = InParams.SubobjectOuter->GetArchetype();
InParams.ObjectStruct->InstanceSubobjectTemplates(InParams.DestData, SubobjectArchetype, SubobjectArchetype->GetClass(),
InParams.SubobjectOuter, &InstanceGraph);
}
if ( InParams.bShouldCallEditChange )
{
// notify the object that it has just been imported
InParams.SubobjectOuter->PostEditImport();
// notify the object that it has been edited
InParams.SubobjectOuter->PostEditChange();
}
InParams.SubobjectRoot->CheckDefaultSubobjects();
}
if ( InParams.LineNumber != INDEX_NONE )
{
if ( ContextSupplier == &Supplier )
{
ContextSupplier = NULL;
InParams.Warn->SetContext(NULL);
}
}
return NewSourceText;
}
/**
* Parse and import text as property values for the object specified.
*
* @param DestData the location to import the property values to
* @param SourceText pointer to a buffer containing the values that should be parsed and imported
* @param ObjectStruct the struct for the data we're importing
* @param SubobjectRoot the original object that ImportObjectProperties was called for.
* if SubobjectOuter is a subobject, corresponds to the first object in SubobjectOuter's Outer chain that is not a subobject itself.
* if SubobjectOuter is not a subobject, should normally be the same value as SubobjectOuter
* @param SubobjectOuter the object corresponding to DestData; this is the object that will used as the outer when creating subobjects from definitions contained in SourceText
* @param Warn ouptut device to use for log messages
* @param Depth current nesting level
* @param LineNumber used when importing defaults during script compilation for tracking which line we're currently for the purposes of printing compile errors
* @param InstanceGraph contains the mappings of instanced objects and components to their templates; used when recursively calling ImportObjectProperties; generally
* not necessary to specify a value when calling this function from other code
*
* @return NULL if the default values couldn't be imported
*/
const TCHAR* ImportObjectProperties(
uint8* DestData,
const TCHAR* SourceText,
UStruct* ObjectStruct,
UObject* SubobjectRoot,
UObject* SubobjectOuter,
FFeedbackContext* Warn,
int32 Depth,
int32 LineNumber,
FObjectInstancingGraph* InInstanceGraph,
TMap<FSoftObjectPath, UObject*>* ObjectRemapper
)
{
FImportObjectParams Params;
{
Params.DestData = DestData;
Params.SourceText = SourceText;
Params.ObjectStruct = ObjectStruct;
Params.SubobjectRoot = SubobjectRoot;
Params.SubobjectOuter = SubobjectOuter;
Params.Warn = Warn;
Params.Depth = Depth;
Params.LineNumber = LineNumber;
Params.InInstanceGraph = InInstanceGraph;
Params.ObjectRemapper = ObjectRemapper;
// This implementation always calls PreEditChange/PostEditChange
Params.bShouldCallEditChange = true;
}
return ImportObjectProperties( Params );
}
namespace EditorUtilities
{
// Multi step object import
/**
* Parse and import text as subobjects for the object specified but doesn't serialize the properties.
*
* @param ObjectStruct The struct for the data we're importing
* @param SourceText View on buffer containing the values that should be parsed and imported
* @param SubobjectRoot When dealing with nested subobjects, corresponds to the top-most outer that
* is not a subobject/template
* @param SubobjectOuter The outer to use for creating subobjects/components. NULL when importing structdefaultproperties
* @param Warn Output device to use for log messages
* @param Depth Current nesting level
* @param InstanceGraph Contains the mappings of instanced objects and components to their templates
* @param ObjectRemapper a map of existing Object to new instances, used to replace internal references when a number of actors are copy+pasted
*
* @return NULL if the default values couldn't be imported
*/
static const TCHAR* ImportCreateSubObjectsStep(
FStringView SourceText,
UStruct* ObjectStruct,
UObject* SubobjectRoot,
UObject* SubobjectOuter,
FFeedbackContext* Warn,
int32 Depth,
FObjectInstancingGraph& InstanceGraph,
TMap<FSoftObjectPath, UObject*>* ObjectRemapper
)
{
check(ObjectStruct!=nullptr);
if (SourceText.IsEmpty())
{
return nullptr;
}
// Cannot create subobjects when importing struct defaults, or if SubobjectOuter (used as the Outer for any subobject declarations encountered) is NULL
bool bSubObjectsAllowed = !ObjectStruct->IsA(UScriptStruct::StaticClass()) && SubobjectOuter != nullptr;
// true when DestData corresponds to a subobject in a class default object
bool bSubObject = false;
UClass* ComponentOwnerClass = nullptr;
if ( bSubObjectsAllowed )
{
bSubObject = SubobjectRoot != nullptr && SubobjectRoot->HasAnyFlags(RF_ClassDefaultObject);
if ( SubobjectRoot == nullptr )
{
SubobjectRoot = SubobjectOuter;
}
ComponentOwnerClass = SubobjectOuter != nullptr
? SubobjectOuter->IsA(UClass::StaticClass())
? CastChecked<UClass>(SubobjectOuter)
: SubobjectOuter->GetClass()
: nullptr;
}
const TCHAR* CurrentSourceText = SourceText.GetData();
const TCHAR* EndOfSourceText = SourceText.GetData() + SourceText.Len();
FString StrLine;
// Parse all objects stored in the actor.
// Build list of all text properties.
bool ImportedBrush = 0;
int32 LinesConsumed = 0;
while (CurrentSourceText < EndOfSourceText)
{
if (!FParse::LineExtended(&CurrentSourceText, StrLine, LinesConsumed, FParse::ELineExtendedFlags::OldExactMode))
{
break;
}
// remove extra whitespace and optional semicolon from the end of the line
{
int32 Length = StrLine.Len();
while ( Length > 0 &&
(StrLine[Length - 1] == TCHAR(';') || StrLine[Length - 1] == TCHAR(' ') || StrLine[Length - 1] == TCHAR('\t')) )
{
Length--;
}
if (Length != StrLine.Len())
{
StrLine.LeftInline(Length, EAllowShrinking::No);
}
}
if (ContextSupplier)
{
ContextSupplier->CurrentLine += LinesConsumed;
}
if (StrLine.Len() == 0)
{
continue;
}
const TCHAR* Str = *StrLine;
if (HandleLineNumber(Str))
{
continue;
}
else if(GetBEGIN(&Str,TEXT("Brush")))
{
Warn->Logf(ELogVerbosity::Error, TEXT("BEGIN Brush: Brushes are not supported in the MultiStepsImport use ImportObjectProperties to import these"));
return nullptr;
}
else if (GetBEGIN(&Str, TEXT("Foliage")))
{
Warn->Logf(ELogVerbosity::Error, TEXT("BEGIN Foliage: Foliage is not supported in the MultiStepsImport use ImportObjectProperties to import these"));
return nullptr;
}
else if(GetBEGIN(&Str,TEXT("Object")))
{
// If SubobjectOuter is NULL, we are importing defaults for a UScriptStruct's defaultproperties block
if ( !bSubObjectsAllowed )
{
Warn->Logf(ELogVerbosity::Error, TEXT("BEGIN OBJECT: Subobjects are not allowed in this context"));
return nullptr;
}
// Parse subobject default properties.
// Note: default properties subobjects have compiled class as their Outer (used for localization).
UClass* TemplateClass = nullptr;
bool bInvalidClass = false;
ParseObject<UClass>(Str, TEXT("Class="), TemplateClass, nullptr, &bInvalidClass);
if (bInvalidClass)
{
Warn->Logf(ELogVerbosity::Error,TEXT("BEGIN OBJECT: Invalid class specified: %s"), *StrLine);
return nullptr;
}
// parse the name of the template
FName TemplateName = NAME_None;
FParse::Value(Str,TEXT("Name="),TemplateName);
if(TemplateName == NAME_None)
{
Warn->Logf(ELogVerbosity::Error,TEXT("BEGIN OBJECT: Must specify valid name for subobject/component: %s"), *StrLine);
return nullptr;
}
FString ExportedObjectFullName;
FParse::Value(Str, TEXT("ExportPath="), ExportedObjectFullName);
FName OverriddenOperationName = NAME_None;
const bool bParsed = FParse::Value(Str,TEXT("OverriddenOperation="), OverriddenOperationName);
const bool bOverrideT3DSupported = FOverridableSerializationLogic::HasCapabilities(FOverridableSerializationLogic::ECapabilities::T3DSerialization);
TOptional<EOverriddenPropertyOperation> OverriddenOperation = bParsed && bOverrideT3DSupported ? GetOverriddenOperationFromName(OverriddenOperationName) : TOptional<EOverriddenPropertyOperation>();
// points to the parent class's template subobject/component, if we are overriding a subobject/component declared in our parent class
UObject* BaseTemplate = nullptr;
bool bRedefiningSubobject = false;
if(!TemplateClass)
{
// next, verify that a template actually exists in the parent class
UClass* ParentClass = ComponentOwnerClass->GetSuperClass();
check(ParentClass);
UObject* ParentCDO = ParentClass->GetDefaultObject();
check(ParentCDO);
BaseTemplate = StaticFindObjectFast(UObject::StaticClass(), SubobjectOuter, TemplateName);
bRedefiningSubobject = (BaseTemplate != nullptr);
if (BaseTemplate == nullptr)
{
BaseTemplate = StaticFindObjectFast(UObject::StaticClass(), ParentCDO, TemplateName);
}
if ( BaseTemplate == nullptr )
{
// wasn't found
Warn->Logf(ELogVerbosity::Error, TEXT("BEGIN OBJECT: No base template named %s found in parent class %s: %s"), *TemplateName.ToString(), *ParentClass->GetName(), *StrLine);
return nullptr;
}
TemplateClass = BaseTemplate->GetClass();
}
// because the outer won't be a default object
checkSlow(TemplateClass != nullptr);
if (bRedefiningSubobject)
{
// Simply recreate any missing sub object
FMultiStepsImportObjectParams Params;
Params.SourceText = FStringView(CurrentSourceText, SourceText.Len() - (CurrentSourceText - SourceText.GetData()));
Params.ObjectStruct = TemplateClass;
Params.SubobjectRoot = SubobjectRoot;
Params.SubobjectOuter = BaseTemplate;
Params.Warn = Warn;
Params.Depth = Depth + 1;
Params.LineNumber = ContextSupplier ? ContextSupplier->CurrentLine : 0;
Params.InInstanceGraph = &InstanceGraph;
Params.ObjectRemapper = ObjectRemapper;
CurrentSourceText = ImportCreateObjectsStep(Params);
if (!ExportedObjectFullName.IsEmpty() && ObjectRemapper)
{
ObjectRemapper->Add(MoveTemp(ExportedObjectFullName), BaseTemplate);
}
}
else
{
UObject* Archetype = nullptr;
UObject* ComponentTemplate = nullptr;
// Since we are changing the class we can't use the Archetype,
// however that is fine since we will have been editing the CDO anyways
if (!FBlueprintEditorUtils::IsAnonymousBlueprintClass(SubobjectOuter->GetClass()))
{
// if an archetype was specified in the Begin Object block, use that as the template for the ConstructObject call.
FString ArchetypeName;
if (FParse::Value(Str, TEXT("Archetype="), ArchetypeName))
{
// if given a name, break it up along the ' so separate the class from the name
FString ObjectClass;
FString ArchetypePath;
if ( FPackageName::ParseExportTextPath(ArchetypeName, &ObjectClass, &ArchetypePath) )
{
// find the class
check(FPackageName::IsValidObjectPath(ObjectClass));
UClass* ArchetypeClass = (UClass*)StaticFindObject(UClass::StaticClass(), nullptr, *ObjectClass);
if (ArchetypeClass)
{
ArchetypePath = ArchetypePath.TrimQuotes();
// if we had the class, find the archetype
if (!FPackageName::IsShortPackageName(ArchetypePath))
{
Archetype = StaticFindObject(ArchetypeClass, nullptr, *ArchetypePath);
}
else
{
Archetype = StaticFindFirstObject(ArchetypeClass, *ArchetypePath, EFindFirstObjectOptions::NativeFirst | EFindFirstObjectOptions::EnsureIfAmbiguous);
}
}
}
}
}
if (SubobjectOuter->HasAnyFlags(RF_ClassDefaultObject))
{
if (!Archetype) // if an archetype was specified explicitly, we will stick with that
{
Archetype = ComponentOwnerClass->GetDefaultSubobjectByName(TemplateName);
if(Archetype)
{
if (!BaseTemplate)
{
// BaseTemplate should only be NULL if the Begin Object line specified a class
Warn->Logf(ELogVerbosity::Error, TEXT("BEGIN OBJECT: The component name %s is already used (if you want to override the component, don't specify a class): %s"), *TemplateName.ToString(), *StrLine);
return nullptr;
}
// the component currently in the component template map and the base template should be the same
checkf(Archetype==BaseTemplate,TEXT("OverrideComponent: '%s' BaseTemplate: '%s'"), *Archetype->GetFullName(), *BaseTemplate->GetFullName());
}
}
}
else // handle the non-template case (subobjects and non-template components)
{
ComponentTemplate = FindObject<UObject>(SubobjectOuter, *TemplateName.ToString());
if (ComponentTemplate != nullptr)
{
// if we're overriding a subobject declared in a parent class, we should already have an object with that name that
// was instanced when ComponentOwnerClass's CDO was initialized; if so, it's archetype should be the BaseTemplate. If it
// isn't, then there are two unrelated subobject definitions using the same name.
if (ComponentTemplate->GetArchetype() != BaseTemplate)
{
}
else if (BaseTemplate == nullptr)
{
// BaseTemplate should only be NULL if the Begin Object line specified a class
Warn->Logf(ELogVerbosity::Error, TEXT("BEGIN OBJECT: A subobject named %s is already declared in a parent class. If you intended to override that subobject, don't specify a class in the derived subobject definition: %s"), *TemplateName.ToString(), *StrLine);
return nullptr;
}
}
}
// Propagate object flags to the sub object.
EObjectFlags NewFlags = SubobjectOuter->GetMaskedFlags( RF_PropagateToSubObjects );
if (!Archetype) // no override and we didn't find one from the class table, so go with the base
{
Archetype = BaseTemplate;
}
UObject* OldComponent = nullptr;
if (ComponentTemplate)
{
bool bIsOkToReuse = ComponentTemplate->GetClass() == TemplateClass
&& ComponentTemplate->GetOuter() == SubobjectOuter
&& ComponentTemplate->GetFName() == TemplateName
&& (ComponentTemplate->GetArchetype() == Archetype || !Archetype);
if (!bIsOkToReuse)
{
UE_LOG(LogEditorObject, Log, TEXT("Could not reuse component instance %s, name clash?"), *ComponentTemplate->GetFullName());
ComponentTemplate->Rename(nullptr, nullptr, REN_DontCreateRedirectors); // just abandon the existing component, we are going to create
OldComponent = ComponentTemplate;
ComponentTemplate = nullptr;
}
}
if (!ComponentTemplate)
{
ComponentTemplate = NewObject<UObject>(
SubobjectOuter,
TemplateClass,
TemplateName,
NewFlags,
Archetype,
!!SubobjectOuter,
&InstanceGraph
);
if (!ExportedObjectFullName.IsEmpty() && ObjectRemapper)
{
ObjectRemapper->Add(MoveTemp(ExportedObjectFullName), ComponentTemplate);
}
}
else
{
// We do not want to set RF_Transactional for construction script created components, so we have to monkey with things here
if (NewFlags & RF_Transactional)
{
UActorComponent* Component = Cast<UActorComponent>(ComponentTemplate);
if (Component && Component->IsCreatedByConstructionScript())
{
NewFlags &= ~RF_Transactional;
}
}
// Ensure DefaultSubojbect flag persists through the clearing of flags
if (ComponentTemplate->HasAllFlags(RF_DefaultSubObject))
{
NewFlags |= RF_DefaultSubObject;
}
// Make sure desired flags are set - existing object could be pending kill
ComponentTemplate->ClearGarbage();
ComponentTemplate->ClearFlags(RF_AllFlags);
ComponentTemplate->ClearInternalFlags(EInternalObjectFlags_AllFlags);
ComponentTemplate->SetFlags(NewFlags);
}
// replace all properties in this subobject outer' class that point to the original subobject with the new subobject
TMap<UObject*, UObject*> ReplacementMap;
if (Archetype)
{
checkSlow(ComponentTemplate->GetArchetype() == Archetype);
ReplacementMap.Add(Archetype, ComponentTemplate);
InstanceGraph.AddNewInstance(ComponentTemplate, Archetype);
}
if (OldComponent)
{
ReplacementMap.Add(OldComponent, ComponentTemplate);
}
FArchiveReplaceObjectRef<UObject> ReplaceAr(SubobjectOuter, ReplacementMap, EArchiveReplaceObjectFlags::IgnoreArchetypeRef);
// Simply recreate any missing sub object
FMultiStepsImportObjectParams Params;
Params.SourceText = FStringView(CurrentSourceText, SourceText.Len() - (CurrentSourceText - SourceText.GetData()));
Params.ObjectStruct = TemplateClass;
Params.SubobjectRoot = SubobjectRoot;
Params.SubobjectOuter = ComponentTemplate;
Params.Warn = Warn;
Params.Depth = Depth + 1;
Params.LineNumber = ContextSupplier ? ContextSupplier->CurrentLine : 0;
Params.InInstanceGraph = &InstanceGraph;
Params.ObjectRemapper = ObjectRemapper;
if (!ExportedObjectFullName.IsEmpty() && ObjectRemapper)
{
ObjectRemapper->Add(MoveTemp(ExportedObjectFullName), ComponentTemplate);
}
if (bOverrideT3DSupported)
{
if (OverriddenOperation.IsSet())
{
FOverridableManager::Get().RestoreOverrideOperation(ComponentTemplate, OverriddenOperation.GetValue(), /*bNeedsSubObjectTemplateInstantiation*/true);
}
else
{
FOverridableManager::Get().Disable(ComponentTemplate);
}
}
// Create the subobjects for the subobject
CurrentSourceText = ImportCreateObjectsStep(Params);
}
}
else if(IsEndOfProperties(Str, Depth))
{
// End of properties.
break;
}
}
return CurrentSourceText;
}
/**
* Parse and import text as property values for the object specified. This function should never be called directly - use ImportObjectProperties instead.
*
* @param ObjectStruct The struct for the data we're importing
* @param DestData The location to import the property values to
* @param SourceText Text containing the values that should be parsed and imported
* @param SubobjectRoot When dealing with nested subobjects, corresponds to the top-most outer that
* is not a subobject/template
* @param SubobjectOuter The outer to use for creating subobjects/components. NULL when importing structdefaultproperties
* @param Warn Output device to use for log messages
* @param Depth Current nesting level
* @param InstanceGraph Contains the mappings of instanced objects and components to their templates
* @param ObjectRemapper A map of exported names to new instances, used to replace internal references when a number of object are copy+pasted
* @param PropertiesToSkip A set containing the properties that shouldn't be imported
*
* @return NULL if the default values couldn't be imported
*/
static const TCHAR* ImportPropertiesStep(
uint8* DestData,
FStringView SourceText,
UStruct* ObjectStruct,
UObject* SubobjectRoot,
UObject* SubobjectOuter,
FFeedbackContext* Warn,
int32 Depth,
FObjectInstancingGraph& InstanceGraph,
TMap<FSoftObjectPath, UObject*>* ObjectRemapper,
TSet<FProperty*>* PropertiesToSkip
)
{
check(ObjectStruct!=nullptr);
check(DestData!=nullptr);
if (SourceText == nullptr)
{
return nullptr;
}
FOverriddenPropertySet* OverriddenProperties = nullptr;
if (SubobjectOuter && FOverridableSerializationLogic::HasCapabilities(FOverridableSerializationLogic::ECapabilities::T3DSerialization))
{
OverriddenProperties = FOverridableManager::Get().GetOverriddenProperties(SubobjectOuter);
}
FEnableOverridableSerializationScope Scope(OverriddenProperties != nullptr, OverriddenProperties);
// Cannot create subobjects when importing struct defaults, or if SubobjectOuter (used as the Outer for any subobject declarations encountered) is NULL
bool bSubObjectsAllowed = !ObjectStruct->IsA(UScriptStruct::StaticClass()) && SubobjectOuter != NULL;
// true when DestData corresponds to a subobject in a class default object
bool bSubObject = false;
UClass* ComponentOwnerClass = nullptr;
if ( bSubObjectsAllowed )
{
bSubObject = SubobjectRoot != nullptr && SubobjectRoot->HasAnyFlags(RF_ClassDefaultObject);
if ( SubobjectRoot == nullptr )
{
SubobjectRoot = SubobjectOuter;
}
ComponentOwnerClass = SubobjectOuter != nullptr
? SubobjectOuter->IsA(UClass::StaticClass())
? CastChecked<UClass>(SubobjectOuter)
: SubobjectOuter->GetClass()
: nullptr;
}
// The PortFlags to use for all ImportText calls
uint32 PortFlags = PPF_Delimited | PPF_CheckReferences;
if (GIsImportingT3D)
{
PortFlags |= PPF_AttemptNonQualifiedSearch;
}
const TCHAR* CurrentSourceText = SourceText.GetData();
const TCHAR* EndOfSourceText = SourceText.GetData() + SourceText.Len();
FString StrLine;
TArray<FDefinedProperty> DefinedProperties;
// Parse all objects stored in the actor.
// Build list of all text properties.
bool ImportedBrush = 0;
int32 LinesConsumed = 0;
while (CurrentSourceText < EndOfSourceText)
{
if (FParse::LineExtended(&CurrentSourceText, StrLine, LinesConsumed, FParse::ELineExtendedFlags::OldExactMode))
{
// remove extra whitespace and optional semicolon from the end of the line
{
int32 Length = StrLine.Len();
while ( Length > 0 &&
(StrLine[Length - 1] == TCHAR(';') || StrLine[Length - 1] == TCHAR(' ') || StrLine[Length - 1] == TCHAR('\t')) )
{
Length--;
}
if (Length != StrLine.Len())
{
StrLine.LeftInline(Length, EAllowShrinking::No);
}
}
if ( ContextSupplier != nullptr )
{
ContextSupplier->CurrentLine += LinesConsumed;
}
if (StrLine.Len() == 0)
{
continue;
}
const TCHAR* Str = *StrLine;
if (HandleLineNumber(Str))
{
continue;
}
else if( GetBEGIN(&Str,TEXT("Object")))
{
// If SubobjectOuter is NULL, we are importing defaults for a UScriptStruct's defaultproperties block
if ( !bSubObjectsAllowed )
{
Warn->Logf(ELogVerbosity::Error, TEXT("BEGIN OBJECT: Subobjects are not allowed in this context"));
return NULL;
}
// parse the name of the template
FName TemplateName = NAME_None;
FParse::Value(Str,TEXT("Name="),TemplateName);
if(TemplateName == NAME_None)
{
Warn->Logf(ELogVerbosity::Error,TEXT("BEGIN OBJECT: Must specify valid name for subobject/component: %s"), *StrLine);
return nullptr;
}
if (UObject* BaseTemplate = StaticFindObjectFast(UObject::StaticClass(), SubobjectOuter, TemplateName))
{
FMultiStepsImportObjectParams Params;
Params.DestData = static_cast<uint8*>(static_cast<void*>(BaseTemplate));
Params.SourceText = FStringView(CurrentSourceText, SourceText.Len() - (CurrentSourceText - SourceText.GetData()));
Params.ObjectStruct = BaseTemplate->GetClass();
Params.SubobjectRoot = SubobjectRoot;
Params.SubobjectOuter = BaseTemplate;
Params.Warn = Warn;
Params.Depth = Depth + 1;
Params.LineNumber = ContextSupplier ? ContextSupplier->CurrentLine : 0;
Params.InInstanceGraph = &InstanceGraph;
Params.ObjectRemapper = ObjectRemapper;
Params.PropertiesToSkip = PropertiesToSkip;
CurrentSourceText = ImportObjectsPropertiesStep(Params);
}
}
else if( FParse::Command(&Str,TEXT("CustomProperties")))
{
check(SubobjectOuter);
SubobjectOuter->ImportCustomProperties(Str, Warn);
}
else if (IsEndOfProperties(Str, Depth))
{
// End of properties.
break;
}
else
{
// Property.
const TCHAR* TextToImport = Str;
FString RedirectedProperty;
if (ObjectRemapper || PropertiesToSkip)
{
constexpr FAsciiSet Delimiters("=([.");
FStringView FullPropertyText(StrLine);
FullPropertyText = FullPropertyText.TrimStartAndEnd();
// find first delimiter
if (const TCHAR* EndOfPropertyName = FAsciiSet::FindFirstOrEnd(FullPropertyText.GetData(), Delimiters))
{
if (PropertiesToSkip)
{
FStringView PropertyNameView(FullPropertyText.GetData(), EndOfPropertyName - FullPropertyText.GetData());
if (!PropertyNameView.IsEmpty())
{
PropertyNameView.TrimEndInline();
const FName PropertyName(PropertyNameView);
FProperty* Property = FindFProperty<FProperty>(ObjectStruct, PropertyName);
if (Property == nullptr)
{
// Check for redirects
FName NewPropertyName = FProperty::FindRedirectedPropertyName(ObjectStruct, PropertyName);
if (NewPropertyName != NAME_None)
{
Property = FindFProperty<FProperty>(ObjectStruct, NewPropertyName);
}
if (!Property)
{
Property = ObjectStruct->CustomFindProperty(PropertyName);
}
}
if (PropertiesToSkip->Contains(Property))
{
// Do not import this property
continue;
}
}
}
if (ObjectRemapper)
{
// Get the value
constexpr FAsciiSet Equal("=");
const TCHAR* PtrToEqual = FAsciiSet::FindFirstOrEnd(EndOfPropertyName, Equal);
if (PtrToEqual && PtrToEqual + 1 < FullPropertyText.GetData() + FullPropertyText.Len())
{
// Select the next character
PtrToEqual++;
FStringView PropertyValue(PtrToEqual, FullPropertyText.Len() - (PtrToEqual - FullPropertyText.GetData()));
if (UObject* const* PointerToObject = ObjectRemapper->Find(FSoftObjectPath(PropertyValue)))
{
FString RedirectedFullName = FObjectPropertyBase::GetExportPath(*PointerToObject, nullptr, nullptr, PortFlags | PPF_Delimited);
FStringView TextBeforeValue(FullPropertyText.GetData(), PtrToEqual - FullPropertyText.GetData());
RedirectedProperty.Reserve(RedirectedFullName.Len() + TextBeforeValue.Len());
RedirectedProperty.Append(TextBeforeValue);
RedirectedProperty.Append(RedirectedFullName);
TextToImport = *RedirectedProperty;
}
}
}
}
}
FProperty::ImportSingleProperty(TextToImport, DestData, ObjectStruct, SubobjectOuter, PortFlags, Warn, DefinedProperties);
}
}
}
return CurrentSourceText;
}
const TCHAR* ImportCreateObjectsStep(FMultiStepsImportObjectParams& InParams)
{
FDefaultPropertiesContextSupplier Supplier;
if (InParams.LineNumber != INDEX_NONE)
{
if (InParams.SubobjectRoot == nullptr)
{
Supplier.PackageName = InParams.ObjectStruct->GetOwnerClass() ? InParams.ObjectStruct->GetOwnerClass()->GetOutermost()->GetName() : InParams.ObjectStruct->GetOutermost()->GetName();
Supplier.ClassName = InParams.ObjectStruct->GetOwnerClass() ? InParams.ObjectStruct->GetOwnerClass()->GetName() : FName(NAME_None).ToString();
Supplier.CurrentLine = InParams.LineNumber;
ContextSupplier = &Supplier; //-V506
}
else
{
if (ContextSupplier != nullptr)
{
ContextSupplier->CurrentLine = InParams.LineNumber;
}
}
InParams.Warn->SetContext(ContextSupplier);
}
FObjectInstancingGraph TempGraph;
FObjectInstancingGraph& InstanceGraph = InParams.InInstanceGraph ? *InParams.InInstanceGraph : TempGraph;
if (InParams.SubobjectRoot && InParams.SubobjectRoot != UObject::StaticClass()->GetDefaultObject())
{
InstanceGraph.SetDestinationRoot(InParams.SubobjectRoot);
}
// Parse the object properties.
const TCHAR* NewSourceText =
ImportCreateSubObjectsStep(
InParams.SourceText,
InParams.ObjectStruct,
InParams.SubobjectRoot,
InParams.SubobjectOuter,
InParams.Warn,
InParams.Depth,
InstanceGraph,
InParams.ObjectRemapper
);
if (InParams.LineNumber != INDEX_NONE)
{
if (ContextSupplier == &Supplier)
{
ContextSupplier = nullptr;
InParams.Warn->SetContext(nullptr);
}
}
return NewSourceText;
}
const TCHAR* ImportObjectsPropertiesStep(FMultiStepsImportObjectParams& InParams)
{
FDefaultPropertiesContextSupplier Supplier;
if (InParams.LineNumber != INDEX_NONE)
{
if (InParams.SubobjectRoot == nullptr)
{
Supplier.PackageName = InParams.ObjectStruct->GetOwnerClass() ? InParams.ObjectStruct->GetOwnerClass()->GetOutermost()->GetName() : InParams.ObjectStruct->GetOutermost()->GetName();
Supplier.ClassName = InParams.ObjectStruct->GetOwnerClass() ? InParams.ObjectStruct->GetOwnerClass()->GetName() : FName(NAME_None).ToString();
Supplier.CurrentLine = InParams.LineNumber;
ContextSupplier = &Supplier; //-V506
}
else
{
if (ContextSupplier != nullptr)
{
ContextSupplier->CurrentLine = InParams.LineNumber;
}
}
InParams.Warn->SetContext(ContextSupplier);
}
if (InParams.bShouldCallEditChange && InParams.SubobjectOuter != nullptr)
{
InParams.SubobjectOuter->PreEditChange(nullptr);
}
FObjectInstancingGraph TempGraph;
FObjectInstancingGraph& InstanceGraph = InParams.InInstanceGraph ? *InParams.InInstanceGraph : TempGraph;
if (InParams.SubobjectRoot && InParams.SubobjectRoot != UObject::StaticClass()->GetDefaultObject())
{
InstanceGraph.SetDestinationRoot(InParams.SubobjectRoot);
}
// Parse the object properties.
const TCHAR* NewSourceText =
ImportPropertiesStep(
InParams.DestData,
InParams.SourceText,
InParams.ObjectStruct,
InParams.SubobjectRoot,
InParams.SubobjectOuter,
InParams.Warn,
InParams.Depth,
InstanceGraph,
InParams.ObjectRemapper,
InParams.PropertiesToSkip
);
if (InParams.SubobjectOuter != nullptr)
{
check(InParams.SubobjectRoot);
// Update the object properties to point to the newly imported component objects.
// Templates inside classes never need to have components instanced.
if (!InParams.SubobjectRoot->HasAnyFlags(RF_ClassDefaultObject))
{
UObject* SubobjectArchetype = InParams.SubobjectOuter->GetArchetype();
InParams.ObjectStruct->InstanceSubobjectTemplates(InParams.DestData, SubobjectArchetype, SubobjectArchetype->GetClass(),
InParams.SubobjectOuter, &InstanceGraph);
}
if (InParams.bShouldCallEditChange)
{
// notify the object that it has just been imported
InParams.SubobjectOuter->PostEditImport();
// notify the object that it has been edited
InParams.SubobjectOuter->PostEditChange();
}
InParams.SubobjectRoot->CheckDefaultSubobjects();
}
if (InParams.LineNumber != INDEX_NONE)
{
if (ContextSupplier == &Supplier)
{
ContextSupplier = nullptr;
InParams.Warn->SetContext(nullptr);
}
}
return NewSourceText;
}
} // End namespace EditorUtilities