|
|
|
@@ -13,6 +13,8 @@
|
|
|
|
|
#include <AzToolsFramework/Prefab/PrefabPublicHandler.h>
|
|
|
|
|
|
|
|
|
|
#include <AzCore/Component/TransformBus.h>
|
|
|
|
|
#include <AzCore/JSON/stringbuffer.h>
|
|
|
|
|
#include <AzCore/JSON/writer.h>
|
|
|
|
|
#include <AzCore/Utils/TypeHash.h>
|
|
|
|
|
|
|
|
|
|
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
|
|
|
|
@@ -31,6 +33,8 @@
|
|
|
|
|
#include <AzToolsFramework/Prefab/PrefabUndoHelpers.h>
|
|
|
|
|
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
|
|
|
|
|
|
|
|
|
|
#include <QString>
|
|
|
|
|
|
|
|
|
|
namespace AzToolsFramework
|
|
|
|
|
{
|
|
|
|
|
namespace Prefab
|
|
|
|
@@ -83,7 +87,9 @@ namespace AzToolsFramework
|
|
|
|
|
commonRootInstanceDomBeforeCreate, commonRootEntityOwningInstance->get());
|
|
|
|
|
|
|
|
|
|
AZStd::vector<AZ::Entity*> entities;
|
|
|
|
|
AZStd::vector<AZStd::unique_ptr<Instance>> instances;
|
|
|
|
|
AZStd::vector<AZStd::unique_ptr<Instance>> instancePtrs;
|
|
|
|
|
AZStd::vector<Instance*> instances;
|
|
|
|
|
AZStd::unordered_map<Instance*, PrefabDom> nestedInstanceLinkPatchesMap;
|
|
|
|
|
|
|
|
|
|
// Retrieve all entities affected and identify Instances
|
|
|
|
|
if (!RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonRootEntityOwningInstance->get(), entities, instances))
|
|
|
|
@@ -92,11 +98,31 @@ namespace AzToolsFramework
|
|
|
|
|
AZStd::string("Could not create a new prefab out of the entities provided - invalid selection."));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Detach the retrieved entities
|
|
|
|
|
for (AZ::Entity* entity : entities)
|
|
|
|
|
{
|
|
|
|
|
commonRootEntityOwningInstance->get().DetachEntity(entity->GetId()).release();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// When we create a prefab with other prefab instances, we have to remove the existing links between the source and
|
|
|
|
|
// target templates of the other instances.
|
|
|
|
|
for (auto& nestedInstance : instances)
|
|
|
|
|
{
|
|
|
|
|
RemoveLink(nestedInstance, commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
|
|
|
|
|
AZStd::unique_ptr<Instance> outInstance = commonRootEntityOwningInstance->get().DetachNestedInstance(nestedInstance->GetInstanceAlias());
|
|
|
|
|
|
|
|
|
|
auto linkRef = m_prefabSystemComponentInterface->FindLink(nestedInstance->GetLinkId());
|
|
|
|
|
|
|
|
|
|
if (linkRef.has_value())
|
|
|
|
|
{
|
|
|
|
|
PrefabDom oldLinkPatches;
|
|
|
|
|
oldLinkPatches.CopyFrom(linkRef->get().GetLinkDom(), oldLinkPatches.GetAllocator());
|
|
|
|
|
|
|
|
|
|
nestedInstanceLinkPatchesMap.emplace(nestedInstance, AZStd::move(oldLinkPatches));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RemoveLink(outInstance, commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
|
|
|
|
|
|
|
|
|
|
instancePtrs.emplace_back(AZStd::move(outInstance));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PrefabUndoHelpers::UpdatePrefabInstance(
|
|
|
|
@@ -112,7 +138,7 @@ namespace AzToolsFramework
|
|
|
|
|
|
|
|
|
|
// Create the Prefab
|
|
|
|
|
instanceToCreate = prefabEditorEntityOwnershipInterface->CreatePrefab(
|
|
|
|
|
entities, AZStd::move(instances), filePath, commonRootEntityOwningInstance);
|
|
|
|
|
entities, AZStd::move(instancePtrs), filePath, commonRootEntityOwningInstance);
|
|
|
|
|
|
|
|
|
|
if (!instanceToCreate)
|
|
|
|
|
{
|
|
|
|
@@ -122,6 +148,9 @@ namespace AzToolsFramework
|
|
|
|
|
|
|
|
|
|
AZ::EntityId containerEntityId = instanceToCreate->get().GetContainerEntityId();
|
|
|
|
|
|
|
|
|
|
// Apply the correct transform to the container for the new instance, and store the patch for use when creating the link.
|
|
|
|
|
PrefabDom patch = ApplyContainerTransformAndGeneratePatch(containerEntityId, commonRootEntityId, topLevelEntities);
|
|
|
|
|
|
|
|
|
|
// Parent the non-container top level entities to the container entity.
|
|
|
|
|
// Parenting the top level container entities will be done during the creation of links.
|
|
|
|
|
for (AZ::Entity* topLevelEntity : topLevelEntities)
|
|
|
|
@@ -141,35 +170,55 @@ namespace AzToolsFramework
|
|
|
|
|
|
|
|
|
|
instanceToCreate->get().GetNestedInstances([&](AZStd::unique_ptr<Instance>& nestedInstance) {
|
|
|
|
|
AZ_Assert(nestedInstance, "Invalid nested instance found in the new prefab created.");
|
|
|
|
|
|
|
|
|
|
EntityOptionalReference nestedInstanceContainerEntity = nestedInstance->GetContainerEntity();
|
|
|
|
|
AZ_Assert(
|
|
|
|
|
nestedInstanceContainerEntity, "Invalid container entity found for the nested instance used in prefab creation.");
|
|
|
|
|
|
|
|
|
|
AZ::EntityId parentId;
|
|
|
|
|
AZ::TransformBus::EventResult(
|
|
|
|
|
parentId, nestedInstanceContainerEntity->get().GetId(), &AZ::TransformBus::Events::GetParentId);
|
|
|
|
|
AZ::EntityId nestedInstanceContainerEntityId = nestedInstanceContainerEntity->get().GetId();
|
|
|
|
|
PrefabDom previousPatch;
|
|
|
|
|
|
|
|
|
|
auto entityIterator = AZStd::find_if(
|
|
|
|
|
entities.begin(), entities.end(), [parentId](AZ::Entity* entity) { return entity->GetId() == parentId; });
|
|
|
|
|
|
|
|
|
|
// If the previous parent entity of the nested instance is not part of the entities of the newly created prefab,
|
|
|
|
|
// then set the parent of the nested prefab as the container entity of the newly created prefab.
|
|
|
|
|
if (entityIterator == entities.end())
|
|
|
|
|
// Retrieve the previous patch if it exists
|
|
|
|
|
if (nestedInstanceLinkPatchesMap.contains(nestedInstance.get()))
|
|
|
|
|
{
|
|
|
|
|
parentId = containerEntityId;
|
|
|
|
|
previousPatch = AZStd::move(nestedInstanceLinkPatchesMap[nestedInstance.get()]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// These link creations shouldn't be undone because that would put the template in a non-usable state if a user
|
|
|
|
|
// chooses to instantiate the template after undoing the creation.
|
|
|
|
|
CreateLink(
|
|
|
|
|
{&nestedInstanceContainerEntity->get()}, *nestedInstance, instanceToCreate->get().GetTemplateId(),
|
|
|
|
|
undoBatch.GetUndoBatch(), parentId, false);
|
|
|
|
|
CreateLink(*nestedInstance, instanceToCreate->get().GetTemplateId(), undoBatch.GetUndoBatch(), AZStd::move(previousPatch), false);
|
|
|
|
|
|
|
|
|
|
// If this nested instance's container is a top level entity in the new prefab, re-parent it and apply the change.
|
|
|
|
|
if (AZStd::find(topLevelEntities.begin(), topLevelEntities.end(), &nestedInstanceContainerEntity->get()) != topLevelEntities.end())
|
|
|
|
|
{
|
|
|
|
|
Prefab::PrefabDom containerEntityDomBefore;
|
|
|
|
|
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, *nestedInstanceContainerEntity);
|
|
|
|
|
|
|
|
|
|
AZ::TransformBus::Event(nestedInstanceContainerEntityId, &AZ::TransformBus::Events::SetParent, containerEntityId);
|
|
|
|
|
|
|
|
|
|
PrefabDom containerEntityDomAfter;
|
|
|
|
|
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomAfter, *nestedInstanceContainerEntity);
|
|
|
|
|
|
|
|
|
|
PrefabDom reparentPatch;
|
|
|
|
|
m_instanceToTemplateInterface->GeneratePatch(reparentPatch, containerEntityDomBefore, containerEntityDomAfter);
|
|
|
|
|
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(reparentPatch, nestedInstanceContainerEntityId);
|
|
|
|
|
|
|
|
|
|
// Update the cache - this prevents these changes from being stored in the regular undo/redo nodes as a separate step
|
|
|
|
|
m_prefabUndoCache.Store(nestedInstanceContainerEntityId, AZStd::move(containerEntityDomAfter));
|
|
|
|
|
|
|
|
|
|
// Save these changes as patches to the link
|
|
|
|
|
PrefabUndoLinkUpdate* linkUpdate = aznew PrefabUndoLinkUpdate(AZStd::to_string(static_cast<AZ::u64>(nestedInstanceContainerEntityId)));
|
|
|
|
|
linkUpdate->SetParent(undoBatch.GetUndoBatch());
|
|
|
|
|
linkUpdate->Capture(reparentPatch, nestedInstance->GetLinkId());
|
|
|
|
|
|
|
|
|
|
linkUpdate->Redo();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Create a link between the templates of the newly created instance and the instance it's being parented under.
|
|
|
|
|
CreateLink(
|
|
|
|
|
topLevelEntities, instanceToCreate->get(), commonRootEntityOwningInstance->get().GetTemplateId(),
|
|
|
|
|
undoBatch.GetUndoBatch(), commonRootEntityId);
|
|
|
|
|
instanceToCreate->get(), commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch(),
|
|
|
|
|
AZStd::move(patch));
|
|
|
|
|
|
|
|
|
|
for (AZ::Entity* topLevelEntity : topLevelEntities)
|
|
|
|
|
{
|
|
|
|
@@ -199,6 +248,40 @@ namespace AzToolsFramework
|
|
|
|
|
return AZ::Success();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PrefabDom PrefabPublicHandler::ApplyContainerTransformAndGeneratePatch(AZ::EntityId containerEntityId, AZ::EntityId parentEntityId, const EntityList& childEntities)
|
|
|
|
|
{
|
|
|
|
|
AZ::Entity* containerEntity = GetEntityById(containerEntityId);
|
|
|
|
|
AZ_Assert(containerEntity, "Invalid container entity passed to ApplyContainerTransformAndGeneratePatch.");
|
|
|
|
|
|
|
|
|
|
// Generate the transform for the container entity out of the top level entities, and set it
|
|
|
|
|
// This step needs to be done before anything is parented to the container, else children position will be wrong
|
|
|
|
|
Prefab::PrefabDom containerEntityDomBefore;
|
|
|
|
|
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, *containerEntity);
|
|
|
|
|
|
|
|
|
|
AZ::Vector3 containerEntityTranslation(AZ::Vector3::CreateZero());
|
|
|
|
|
AZ::Quaternion containerEntityRotation(AZ::Quaternion::CreateZero());
|
|
|
|
|
|
|
|
|
|
// Set container entity to be child of common root
|
|
|
|
|
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, parentEntityId);
|
|
|
|
|
|
|
|
|
|
// Set the transform (translation, rotation) of the container entity
|
|
|
|
|
GenerateContainerEntityTransform(childEntities, containerEntityTranslation, containerEntityRotation);
|
|
|
|
|
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalTranslation, containerEntityTranslation);
|
|
|
|
|
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalRotationQuaternion, containerEntityRotation);
|
|
|
|
|
|
|
|
|
|
PrefabDom containerEntityDomAfter;
|
|
|
|
|
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomAfter, *containerEntity);
|
|
|
|
|
|
|
|
|
|
PrefabDom patch;
|
|
|
|
|
m_instanceToTemplateInterface->GeneratePatch(patch, containerEntityDomBefore, containerEntityDomAfter);
|
|
|
|
|
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, containerEntityId);
|
|
|
|
|
|
|
|
|
|
// Update the cache - this prevents these changes from being stored in the regular undo/redo nodes
|
|
|
|
|
m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter));
|
|
|
|
|
|
|
|
|
|
return AZStd::move(patch);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PrefabOperationResult PrefabPublicHandler::InstantiatePrefab(
|
|
|
|
|
AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position)
|
|
|
|
|
{
|
|
|
|
@@ -249,10 +332,10 @@ namespace AzToolsFramework
|
|
|
|
|
// Initialize Undo Batch object
|
|
|
|
|
ScopedUndoBatch undoBatch("Instantiate Prefab");
|
|
|
|
|
|
|
|
|
|
// Instantiate the Prefab
|
|
|
|
|
PrefabDom instanceToParentUnderDomBeforeCreate;
|
|
|
|
|
m_instanceToTemplateInterface->GenerateDomForInstance(instanceToParentUnderDomBeforeCreate, instanceToParentUnder->get());
|
|
|
|
|
|
|
|
|
|
// Instantiate the Prefab
|
|
|
|
|
auto instanceToCreate = prefabEditorEntityOwnershipInterface->InstantiatePrefab(relativePath, instanceToParentUnder);
|
|
|
|
|
|
|
|
|
|
if (!instanceToCreate)
|
|
|
|
@@ -264,11 +347,32 @@ namespace AzToolsFramework
|
|
|
|
|
PrefabUndoHelpers::UpdatePrefabInstance(
|
|
|
|
|
instanceToParentUnder->get(), "Update prefab instance", instanceToParentUnderDomBeforeCreate, undoBatch.GetUndoBatch());
|
|
|
|
|
|
|
|
|
|
CreateLink({}, instanceToCreate->get(), instanceToParentUnder->get().GetTemplateId(), undoBatch.GetUndoBatch(), parent);
|
|
|
|
|
// Create Link with correct container patches
|
|
|
|
|
AZ::EntityId containerEntityId = instanceToCreate->get().GetContainerEntityId();
|
|
|
|
|
AZ::Entity* containerEntity = GetEntityById(containerEntityId);
|
|
|
|
|
AZ_Assert(containerEntity, "Invalid container entity detected in InstantiatePrefab.");
|
|
|
|
|
|
|
|
|
|
// Apply position
|
|
|
|
|
Prefab::PrefabDom containerEntityDomBefore;
|
|
|
|
|
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, *containerEntity);
|
|
|
|
|
|
|
|
|
|
// Set container entity's parent
|
|
|
|
|
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, parent);
|
|
|
|
|
|
|
|
|
|
// Set the position of the container entity
|
|
|
|
|
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetWorldTranslation, position);
|
|
|
|
|
|
|
|
|
|
PrefabDom containerEntityDomAfter;
|
|
|
|
|
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomAfter, *containerEntity);
|
|
|
|
|
|
|
|
|
|
// Generate patch to be stored in the link
|
|
|
|
|
PrefabDom patch;
|
|
|
|
|
m_instanceToTemplateInterface->GeneratePatch(patch, containerEntityDomBefore, containerEntityDomAfter);
|
|
|
|
|
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, containerEntityId);
|
|
|
|
|
|
|
|
|
|
CreateLink(instanceToCreate->get(), instanceToParentUnder->get().GetTemplateId(), undoBatch.GetUndoBatch(), AZStd::move(patch));
|
|
|
|
|
|
|
|
|
|
// Update the cache - this prevents these changes from being stored in the regular undo/redo nodes
|
|
|
|
|
m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return AZ::Success();
|
|
|
|
@@ -299,7 +403,7 @@ namespace AzToolsFramework
|
|
|
|
|
// Find common root and top level entities
|
|
|
|
|
bool entitiesHaveCommonRoot = false;
|
|
|
|
|
|
|
|
|
|
AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(
|
|
|
|
|
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
|
|
|
|
|
entitiesHaveCommonRoot, &AzToolsFramework::ToolsApplicationRequests::FindCommonRootInactive, inputEntityList,
|
|
|
|
|
commonRootEntityId, &topLevelEntities);
|
|
|
|
|
|
|
|
|
@@ -335,33 +439,9 @@ namespace AzToolsFramework
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PrefabPublicHandler::CreateLink(
|
|
|
|
|
const EntityList& topLevelEntities, Instance& sourceInstance, TemplateId targetTemplateId,
|
|
|
|
|
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId commonRootEntityId, const bool isUndoRedoSupportNeeded)
|
|
|
|
|
Instance& sourceInstance, TemplateId targetTemplateId,
|
|
|
|
|
UndoSystem::URSequencePoint* undoBatch, PrefabDom patch, const bool isUndoRedoSupportNeeded)
|
|
|
|
|
{
|
|
|
|
|
AZ::EntityId containerEntityId = sourceInstance.GetContainerEntityId();
|
|
|
|
|
AZ::Entity* containerEntity = GetEntityById(containerEntityId);
|
|
|
|
|
Prefab::PrefabDom containerEntityDomBefore;
|
|
|
|
|
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, *containerEntity);
|
|
|
|
|
|
|
|
|
|
AZ::Vector3 containerEntityTranslation(AZ::Vector3::CreateZero());
|
|
|
|
|
AZ::Quaternion containerEntityRotation(AZ::Quaternion::CreateZero());
|
|
|
|
|
|
|
|
|
|
// Set the transform (translation, rotation) of the container entity
|
|
|
|
|
GenerateContainerEntityTransform(topLevelEntities, containerEntityTranslation, containerEntityRotation);
|
|
|
|
|
|
|
|
|
|
// Set container entity to be child of common root
|
|
|
|
|
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, commonRootEntityId);
|
|
|
|
|
|
|
|
|
|
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalTranslation, containerEntityTranslation);
|
|
|
|
|
AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalRotationQuaternion, containerEntityRotation);
|
|
|
|
|
|
|
|
|
|
PrefabDom containerEntityDomAfter;
|
|
|
|
|
m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomAfter, *containerEntity);
|
|
|
|
|
|
|
|
|
|
PrefabDom patch;
|
|
|
|
|
m_instanceToTemplateInterface->GeneratePatch(patch, containerEntityDomBefore, containerEntityDomAfter);
|
|
|
|
|
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, containerEntityId);
|
|
|
|
|
|
|
|
|
|
LinkId linkId;
|
|
|
|
|
if (isUndoRedoSupportNeeded)
|
|
|
|
|
{
|
|
|
|
@@ -377,9 +457,6 @@ namespace AzToolsFramework
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sourceInstance.SetLinkId(linkId);
|
|
|
|
|
|
|
|
|
|
// Update the cache - this prevents these changes from being stored in the regular undo/redo nodes
|
|
|
|
|
m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PrefabPublicHandler::RemoveLink(
|
|
|
|
@@ -648,6 +725,151 @@ namespace AzToolsFramework
|
|
|
|
|
return DeleteFromInstance(entityIds, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PrefabOperationResult PrefabPublicHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds)
|
|
|
|
|
{
|
|
|
|
|
if (entityIds.empty())
|
|
|
|
|
{
|
|
|
|
|
return AZ::Failure(AZStd::string("No entities to duplicate."));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!EntitiesBelongToSameInstance(entityIds))
|
|
|
|
|
{
|
|
|
|
|
return AZ::Failure(AZStd::string("Cannot duplicate multiple "
|
|
|
|
|
"entities belonging to different instances with one operation."));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// We've already verified the entities are all owned by the same instance,
|
|
|
|
|
// so we can just retrieve our instance from the first entity in the list.
|
|
|
|
|
InstanceOptionalReference commonEntityOwningInstance = GetOwnerInstanceByEntityId(entityIds[0]);
|
|
|
|
|
AZ_Assert(
|
|
|
|
|
commonEntityOwningInstance.has_value(),
|
|
|
|
|
"Failed to duplicate : Couldn't get a valid owning instance for the common root entity of the entities provided");
|
|
|
|
|
|
|
|
|
|
// This will cull out any entities that have ancestors in the list, since we will end up duplicating
|
|
|
|
|
// the full nested hierarchy with what is returned from RetrieveAndSortPrefabEntitiesAndInstances
|
|
|
|
|
AzToolsFramework::EntityIdSet duplicationSet = AzToolsFramework::GetCulledEntityHierarchy(entityIds);
|
|
|
|
|
|
|
|
|
|
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
|
|
|
|
|
|
|
|
|
|
ScopedUndoBatch undoBatch("Duplicate Entities");
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "DuplicateEntitiesInInstance::UndoCaptureAndDuplicateEntities");
|
|
|
|
|
|
|
|
|
|
// Take a snapshot of the instance DOM before we manipulate it
|
|
|
|
|
Prefab::PrefabDom instanceDomBefore;
|
|
|
|
|
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, commonEntityOwningInstance->get());
|
|
|
|
|
|
|
|
|
|
AZStd::vector<AZ::Entity*> entities;
|
|
|
|
|
AZStd::vector<Instance*> instances;
|
|
|
|
|
|
|
|
|
|
// Gather all entities/instances in the hierarchy, but don't detach them because we are duplicating not deleting.
|
|
|
|
|
EntityList inputEntityList = EntityIdSetToEntityList(duplicationSet);
|
|
|
|
|
bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonEntityOwningInstance->get(), entities, instances);
|
|
|
|
|
|
|
|
|
|
if (!success)
|
|
|
|
|
{
|
|
|
|
|
return AZ::Failure(AZStd::string("Failed to retrieve entities and instances from the given list of entity ids for duplication"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make a copy of our before instance DOM where we will add our duplicated entities
|
|
|
|
|
Prefab::PrefabDom instanceDomAfter;
|
|
|
|
|
instanceDomAfter.CopyFrom(instanceDomBefore, instanceDomAfter.GetAllocator());
|
|
|
|
|
|
|
|
|
|
AZStd::unordered_map<EntityAlias, EntityAlias> oldAliasToNewAliasMap;
|
|
|
|
|
AZStd::unordered_map<EntityAlias, QString> aliasToEntityDomMap;
|
|
|
|
|
|
|
|
|
|
for (AZ::Entity* entity : entities)
|
|
|
|
|
{
|
|
|
|
|
EntityAliasOptionalReference oldAliasRef = commonEntityOwningInstance->get().GetEntityAlias(entity->GetId());
|
|
|
|
|
AZ_Assert(oldAliasRef.has_value(), "No alias found for Entity in the DOM");
|
|
|
|
|
EntityAlias oldAlias = oldAliasRef.value();
|
|
|
|
|
|
|
|
|
|
// Give this the outer allocator so that the memory reference will be valid when
|
|
|
|
|
// it gets used for AddMember
|
|
|
|
|
Prefab::PrefabDom entityDomBefore(&instanceDomAfter.GetAllocator());
|
|
|
|
|
m_instanceToTemplateInterface->GenerateDomForEntity(entityDomBefore, *entity);
|
|
|
|
|
|
|
|
|
|
// Keep track of the old alias <-> new alias mapping for this duplicated entity
|
|
|
|
|
// so we can fixup references later
|
|
|
|
|
EntityAlias newEntityAlias = Instance::GenerateEntityAlias();
|
|
|
|
|
oldAliasToNewAliasMap.insert(AZStd::make_pair(oldAlias, newEntityAlias));
|
|
|
|
|
|
|
|
|
|
rapidjson::StringBuffer buffer;
|
|
|
|
|
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
|
|
|
|
entityDomBefore.Accept(writer);
|
|
|
|
|
|
|
|
|
|
// Store our duplicated Entity DOM with its new alias as a string
|
|
|
|
|
// so that we can fixup entity alias references before adding it
|
|
|
|
|
// to the Entities member of our instance DOM
|
|
|
|
|
QString entityDomString(buffer.GetString());
|
|
|
|
|
aliasToEntityDomMap.insert(AZStd::make_pair(newEntityAlias, entityDomString));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto entitiesIter = instanceDomAfter.FindMember(PrefabDomUtils::EntitiesName);
|
|
|
|
|
AZ_Assert(entitiesIter != instanceDomAfter.MemberEnd(), "Instance DOM missing the Entities member.");
|
|
|
|
|
|
|
|
|
|
// Now that all the duplicated Entity DOMs have been created, we need to iterate
|
|
|
|
|
// through them and replace any previous EntityAlias references with the new ones.
|
|
|
|
|
// These are more than just parent entity references for nested entities, this will
|
|
|
|
|
// also cover any EntityId references that were made in the components between them.
|
|
|
|
|
for (auto aliasEntityPair : aliasToEntityDomMap)
|
|
|
|
|
{
|
|
|
|
|
EntityAlias newEntityAlias = aliasEntityPair.first;
|
|
|
|
|
QString newEntityDomString = aliasEntityPair.second;
|
|
|
|
|
|
|
|
|
|
// Replace all of the old alias references with the new ones
|
|
|
|
|
// We bookend the aliases with \" and also with a / as an extra precaution to prevent
|
|
|
|
|
// inadvertently replacing a matching string vs. where an actual EntityId is expected
|
|
|
|
|
// This will cover both cases where an alias could be used in a normal entity vs. an instance
|
|
|
|
|
for (auto aliasMapIter : oldAliasToNewAliasMap)
|
|
|
|
|
{
|
|
|
|
|
QString oldAliasQuotes = QString("\"%1\"").arg(aliasMapIter.first.c_str());
|
|
|
|
|
QString newAliasQuotes = QString("\"%1\"").arg(aliasMapIter.second.c_str());
|
|
|
|
|
|
|
|
|
|
newEntityDomString.replace(oldAliasQuotes, newAliasQuotes);
|
|
|
|
|
|
|
|
|
|
QString oldAliasPathRef = QString("/%1").arg(aliasMapIter.first.c_str());
|
|
|
|
|
QString newAliasPathRef = QString("/%1").arg(aliasMapIter.second.c_str());
|
|
|
|
|
|
|
|
|
|
newEntityDomString.replace(oldAliasPathRef, newAliasPathRef);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create the new Entity DOM from parsing the JSON string
|
|
|
|
|
Prefab::PrefabDom entityDomAfter(&instanceDomAfter.GetAllocator());
|
|
|
|
|
entityDomAfter.Parse(newEntityDomString.toUtf8().constData());
|
|
|
|
|
|
|
|
|
|
// Add the new Entity DOM to the Entities member of the instance
|
|
|
|
|
rapidjson::Value aliasName(newEntityAlias.c_str(), newEntityAlias.length(), instanceDomAfter.GetAllocator());
|
|
|
|
|
entitiesIter->value.AddMember(AZStd::move(aliasName), entityDomAfter, instanceDomAfter.GetAllocator());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PrefabUndoInstance* command = aznew PrefabUndoInstance("Entity duplication");
|
|
|
|
|
command->SetParent(undoBatch.GetUndoBatch());
|
|
|
|
|
command->Capture(instanceDomBefore, instanceDomAfter, commonEntityOwningInstance->get().GetTemplateId());
|
|
|
|
|
command->RunRedo();
|
|
|
|
|
|
|
|
|
|
EntityIdList duplicatedEntityIds;
|
|
|
|
|
for (auto aliasMapIter : oldAliasToNewAliasMap)
|
|
|
|
|
{
|
|
|
|
|
EntityAlias newEntityAlias = aliasMapIter.second;
|
|
|
|
|
|
|
|
|
|
AliasPath absoluteEntityPath = commonEntityOwningInstance->get().GetAbsoluteInstanceAliasPath();
|
|
|
|
|
absoluteEntityPath.Append(newEntityAlias);
|
|
|
|
|
|
|
|
|
|
AZ::EntityId newEntityId = InstanceEntityIdMapper::GenerateEntityIdForAliasPath(absoluteEntityPath);
|
|
|
|
|
duplicatedEntityIds.push_back(newEntityId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Select the duplicated entities
|
|
|
|
|
auto selectionUndo = aznew SelectionCommand(duplicatedEntityIds, "Select Duplicated Entities");
|
|
|
|
|
selectionUndo->SetParent(undoBatch.GetUndoBatch());
|
|
|
|
|
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::RunRedoSeparately, selectionUndo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return AZ::Success();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PrefabOperationResult PrefabPublicHandler::DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants)
|
|
|
|
|
{
|
|
|
|
|
if (entityIds.empty())
|
|
|
|
@@ -675,17 +897,7 @@ namespace AzToolsFramework
|
|
|
|
|
|
|
|
|
|
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
|
|
|
|
|
|
|
|
|
|
UndoSystem::URSequencePoint* currentUndoBatch = nullptr;
|
|
|
|
|
ToolsApplicationRequests::Bus::BroadcastResult(currentUndoBatch, &ToolsApplicationRequests::Bus::Events::GetCurrentUndoBatch);
|
|
|
|
|
|
|
|
|
|
bool createdUndo = false;
|
|
|
|
|
if (!currentUndoBatch)
|
|
|
|
|
{
|
|
|
|
|
createdUndo = true;
|
|
|
|
|
ToolsApplicationRequests::Bus::BroadcastResult(
|
|
|
|
|
currentUndoBatch, &ToolsApplicationRequests::Bus::Events::BeginUndoBatch, "Delete Selected");
|
|
|
|
|
AZ_Assert(currentUndoBatch, "Failed to create new undo batch.");
|
|
|
|
|
}
|
|
|
|
|
ScopedUndoBatch undoBatch("Delete Selected");
|
|
|
|
|
|
|
|
|
|
// In order to undo DeleteSelected, we have to create a selection command which selects the current selection
|
|
|
|
|
// and then add the deletion as children.
|
|
|
|
@@ -713,7 +925,7 @@ namespace AzToolsFramework
|
|
|
|
|
if (deleteDescendants)
|
|
|
|
|
{
|
|
|
|
|
AZStd::vector<AZ::Entity*> entities;
|
|
|
|
|
AZStd::vector<AZStd::unique_ptr<Instance>> instances;
|
|
|
|
|
AZStd::vector<Instance*> instances;
|
|
|
|
|
|
|
|
|
|
bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
|
|
|
|
|
|
|
|
|
@@ -724,13 +936,15 @@ namespace AzToolsFramework
|
|
|
|
|
|
|
|
|
|
for (AZ::Entity* entity : entities)
|
|
|
|
|
{
|
|
|
|
|
commonOwningInstance->get().DetachEntity(entity->GetId()).release();
|
|
|
|
|
AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::DeleteEntity, entity->GetId());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (auto& nestedInstance : instances)
|
|
|
|
|
{
|
|
|
|
|
RemoveLink(nestedInstance, commonOwningInstance->get().GetTemplateId(), currentUndoBatch);
|
|
|
|
|
nestedInstance.reset();
|
|
|
|
|
AZStd::unique_ptr<Instance> outInstance = commonOwningInstance->get().DetachNestedInstance(nestedInstance->GetInstanceAlias());
|
|
|
|
|
RemoveLink(outInstance, commonOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
|
|
|
|
|
outInstance.reset();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
@@ -742,7 +956,7 @@ namespace AzToolsFramework
|
|
|
|
|
if (owningInstance->get().GetContainerEntityId() == entityId)
|
|
|
|
|
{
|
|
|
|
|
auto instancePtr = commonOwningInstance->get().DetachNestedInstance(owningInstance->get().GetInstanceAlias());
|
|
|
|
|
RemoveLink(instancePtr, commonOwningInstance->get().GetTemplateId(), currentUndoBatch);
|
|
|
|
|
RemoveLink(instancePtr, commonOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
@@ -760,17 +974,12 @@ namespace AzToolsFramework
|
|
|
|
|
command->SetParent(selCommand);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
selCommand->SetParent(currentUndoBatch);
|
|
|
|
|
selCommand->SetParent(undoBatch.GetUndoBatch());
|
|
|
|
|
{
|
|
|
|
|
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DeleteEntities:RunRedo");
|
|
|
|
|
selCommand->RunRedo();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (createdUndo)
|
|
|
|
|
{
|
|
|
|
|
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::EndUndoBatch);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return AZ::Success();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -882,7 +1091,7 @@ namespace AzToolsFramework
|
|
|
|
|
|
|
|
|
|
bool PrefabPublicHandler::RetrieveAndSortPrefabEntitiesAndInstances(
|
|
|
|
|
const EntityList& inputEntities, Instance& commonRootEntityOwningInstance,
|
|
|
|
|
EntityList& outEntities, AZStd::vector<AZStd::unique_ptr<Instance>>& outInstances) const
|
|
|
|
|
EntityList& outEntities, AZStd::vector<Instance*>& outInstances) const
|
|
|
|
|
{
|
|
|
|
|
if (inputEntities.size() == 0)
|
|
|
|
|
{
|
|
|
|
@@ -966,14 +1175,14 @@ namespace AzToolsFramework
|
|
|
|
|
|
|
|
|
|
for (AZ::Entity* entity : entities)
|
|
|
|
|
{
|
|
|
|
|
outEntities.emplace_back(commonRootEntityOwningInstance.DetachEntity(entity->GetId()).release());
|
|
|
|
|
outEntities.emplace_back(entity);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
outInstances.clear();
|
|
|
|
|
outInstances.reserve(instances.size());
|
|
|
|
|
for (Instance* instancePtr : instances)
|
|
|
|
|
{
|
|
|
|
|
outInstances.push_back(AZStd::move(commonRootEntityOwningInstance.DetachNestedInstance(instancePtr->GetInstanceAlias())));
|
|
|
|
|
outInstances.push_back(instancePtr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (outEntities.size() + outInstances.size()) > 0;
|
|
|
|
|