/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace AzToolsFramework { namespace Prefab { void PrefabPublicHandler::RegisterPrefabPublicHandlerInterface() { m_instanceEntityMapperInterface = AZ::Interface::Get(); AZ_Assert(m_instanceEntityMapperInterface, "PrefabPublicHandler - Could not retrieve instance of InstanceEntityMapperInterface"); m_instanceToTemplateInterface = AZ::Interface::Get(); AZ_Assert(m_instanceToTemplateInterface, "PrefabPublicHandler - Could not retrieve instance of InstanceToTemplateInterface"); m_prefabFocusInterface = AZ::Interface::Get(); AZ_Assert(m_prefabFocusInterface, "Could not get PrefabFocusInterface on PrefabPublicHandler construction."); m_prefabFocusPublicInterface = AZ::Interface::Get(); AZ_Assert(m_prefabFocusPublicInterface, "Could not get PrefabFocusPublicInterface on PrefabPublicHandler construction."); m_prefabLoaderInterface = AZ::Interface::Get(); AZ_Assert(m_prefabLoaderInterface, "Could not get PrefabLoaderInterface on PrefabPublicHandler construction."); m_prefabSystemComponentInterface = AZ::Interface::Get(); AZ_Assert(m_prefabSystemComponentInterface, "Could not get PrefabSystemComponentInterface on PrefabPublicHandler construction."); m_prefabUndoCache.Initialize(); AZ::Interface::Register(this); } void PrefabPublicHandler::UnregisterPrefabPublicHandlerInterface() { AZ::Interface::Unregister(this); m_prefabUndoCache.Destroy(); } CreatePrefabResult PrefabPublicHandler::CreatePrefabInMemory(const EntityIdList& entityIds, AZ::IO::PathView filePath) { EntityList inputEntityList, topLevelEntities; AZ::EntityId commonRootEntityId; InstanceOptionalReference commonRootEntityOwningInstance; PrefabOperationResult findCommonRootOutcome = FindCommonRootOwningInstance( entityIds, inputEntityList, topLevelEntities, commonRootEntityId, commonRootEntityOwningInstance); if (!findCommonRootOutcome.IsSuccess()) { return AZ::Failure(findCommonRootOutcome.TakeError()); } AZ::EntityId containerEntityId; InstanceOptionalReference instanceToCreate; { // Initialize Undo Batch object ScopedUndoBatch undoBatch("Create Prefab"); PrefabDom commonRootInstanceDomBeforeCreate; m_instanceToTemplateInterface->GenerateDomForInstance( commonRootInstanceDomBeforeCreate, commonRootEntityOwningInstance->get()); AZStd::vector entities; AZStd::vector> instancePtrs; AZStd::vector instances; AZStd::unordered_map nestedInstanceLinkPatchesMap; // Retrieve all entities affected and identify Instances PrefabOperationResult retrieveEntitiesAndInstancesOutcome = RetrieveAndSortPrefabEntitiesAndInstances( inputEntityList, commonRootEntityOwningInstance->get(), entities, instances); if (!retrieveEntitiesAndInstancesOutcome.IsSuccess()) { return AZ::Failure(retrieveEntitiesAndInstancesOutcome.TakeError()); } AZStd::unordered_map oldEntityAliases; // Detach the retrieved entities for (AZ::Entity* entity : entities) { AZ::EntityId entityId = entity->GetId(); oldEntityAliases.emplace(entityId, commonRootEntityOwningInstance->get().GetEntityAlias(entityId)->get()); 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) { AZStd::unique_ptr outInstance = commonRootEntityOwningInstance->get().DetachNestedInstance(nestedInstance->GetInstanceAlias()); LinkId detachingInstanceLinkId = nestedInstance->GetLinkId(); auto linkRef = m_prefabSystemComponentInterface->FindLink(detachingInstanceLinkId); AZ_Assert(linkRef.has_value(), "Unable to find link with id '%llu' during prefab creation.", detachingInstanceLinkId); PrefabDomValueReference linkPatches = linkRef->get().GetLinkPatches(); AZ_Assert( linkPatches.has_value(), "Unable to get patches on link with id '%llu' during prefab creation.", detachingInstanceLinkId); PrefabDom linkPatchesCopy; linkPatchesCopy.CopyFrom(linkPatches->get(), linkPatchesCopy.GetAllocator()); nestedInstanceLinkPatchesMap.emplace(nestedInstance, AZStd::move(linkPatchesCopy)); RemoveLink(outInstance, commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch()); instancePtrs.emplace_back(AZStd::move(outInstance)); } PrefabUndoHelpers::UpdatePrefabInstance( commonRootEntityOwningInstance->get(), "Update prefab instance", commonRootInstanceDomBeforeCreate, undoBatch.GetUndoBatch()); auto prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); if (!prefabEditorEntityOwnershipInterface) { return AZ::Failure(AZStd::string("Could not create a new prefab out of the entities provided - internal error " "(PrefabEditorEntityOwnershipInterface unavailable).")); } // Create the Prefab AZ_Assert(filePath.IsAbsolute(), "CreatePrefabInMemory requires an absolute file path."); instanceToCreate = prefabEditorEntityOwnershipInterface->CreatePrefab( entities, AZStd::move(instancePtrs), m_prefabLoaderInterface->GenerateRelativePath(filePath), commonRootEntityOwningInstance); if (!instanceToCreate) { return AZ::Failure(AZStd::string("Could not create a new prefab out of the entities provided - internal error " "(A null instance is returned).")); } 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) { if (!IsInstanceContainerEntity(topLevelEntity->GetId())) { AZ::TransformBus::Event(topLevelEntity->GetId(), &AZ::TransformBus::Events::SetParent, containerEntityId); } } // Update the template of the instance since the entities are modified since the template creation. Prefab::PrefabDom serializedInstance; if (Prefab::PrefabDomUtils::StoreInstanceInPrefabDom(instanceToCreate->get(), serializedInstance)) { m_prefabSystemComponentInterface->UpdatePrefabTemplate(instanceToCreate->get().GetTemplateId(), serializedInstance); } instanceToCreate->get().GetNestedInstances([&](AZStd::unique_ptr& 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 nestedInstanceContainerEntityId = nestedInstanceContainerEntity->get().GetId(); PrefabDom previousPatch; // Retrieve the previous patch if it exists if (nestedInstanceLinkPatchesMap.contains(nestedInstance.get())) { previousPatch = AZStd::move(nestedInstanceLinkPatchesMap[nestedInstance.get()]); UpdateLinkPatchesWithNewEntityAliases(previousPatch, oldEntityAliases, instanceToCreate->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(*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); // We won't parent this undo node to the undo batch so that the newly created template and link will remain // unaffected by undo actions. This is needed so that any future instantiations of the template will work. PrefabUndoLinkUpdate linkUpdate = PrefabUndoLinkUpdate(AZStd::to_string(static_cast(nestedInstanceContainerEntityId))); 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( instanceToCreate->get(), commonRootEntityOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch(), AZStd::move(patch)); // Reset the transform of the container entity so that the new values aren't saved in the new prefab's dom. // The new values were saved in the link, so propagation will apply them correctly. { AZ::Entity* containerEntity = GetEntityById(containerEntityId); PrefabDom containerBeforeReset; m_instanceToTemplateInterface->GenerateDomForEntity(containerBeforeReset, *containerEntity); AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetParent, AZ::EntityId()); AZ::TransformBus::Event(containerEntityId, &AZ::TransformBus::Events::SetLocalTM, AZ::Transform::CreateIdentity()); PrefabDom containerAfterReset; m_instanceToTemplateInterface->GenerateDomForEntity(containerAfterReset, *containerEntity); // Update the state of the entity auto templateId = instanceToCreate->get().GetTemplateId(); PrefabDom transformPatch; m_instanceToTemplateInterface->GeneratePatch(transformPatch, containerBeforeReset, containerAfterReset); m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(transformPatch, containerEntityId); m_instanceToTemplateInterface->PatchTemplate(transformPatch, templateId); } // This clears any entities marked as dirty due to reparenting of entities during the process of creating a prefab. // We are doing this so that the changes in those entities are not queued up twice for propagation. AzToolsFramework::ToolsApplicationRequestBus::Broadcast( &AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities); // Select Container Entity { const EntityIdList selectedEntities = EntityIdList{ containerEntityId }; auto selectionUndo = aznew SelectionCommand(selectedEntities, "Select Prefab Container Entity"); selectionUndo->SetParent(undoBatch.GetUndoBatch()); ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::SetSelectedEntities, selectedEntities); } } return AZ::Success(containerEntityId); } CreatePrefabResult PrefabPublicHandler::CreatePrefabInDisk(const EntityIdList& entityIds, AZ::IO::PathView filePath) { auto result = CreatePrefabInMemory(entityIds, filePath); if (result.IsSuccess()) { // Save Template to file auto relativePath = m_prefabLoaderInterface->GenerateRelativePath(filePath); Prefab::TemplateId templateId = m_prefabSystemComponentInterface->GetTemplateIdFromFilePath(relativePath); if (!m_prefabLoaderInterface->SaveTemplateToFile(templateId, filePath)) { AZStd::string_view filePathString(filePath); return AZ::Failure(AZStd::string::format( "Could not save the newly created prefab to file path %.*s - internal error ", AZ_STRING_ARG(filePathString))); } } return result; } 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), parentEntityId); return AZStd::move(patch); } InstantiatePrefabResult PrefabPublicHandler::InstantiatePrefab( AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position) { auto prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); if (!prefabEditorEntityOwnershipInterface) { return AZ::Failure(AZStd::string("Could not instantiate prefab - internal error " "(PrefabEditorEntityOwnershipInterface unavailable).")); } if (!prefabEditorEntityOwnershipInterface->IsRootPrefabAssigned()) { return AZ::Failure(AZStd::string("Could not instantiate prefab - no root prefab assigned. " "Currently, prefabs can only be instantiated inside a level")); } InstanceOptionalReference instanceToParentUnder; // Get parent entity and owning instance if (parent.IsValid()) { instanceToParentUnder = m_instanceEntityMapperInterface->FindOwningInstance(parent); } if (!instanceToParentUnder.has_value()) { instanceToParentUnder = prefabEditorEntityOwnershipInterface->GetRootPrefabInstance(); parent = instanceToParentUnder->get().GetContainerEntityId(); } //Detect whether this instantiation would produce a cyclical dependency auto relativePath = m_prefabLoaderInterface->GenerateRelativePath(filePath); Prefab::TemplateId templateId = m_prefabSystemComponentInterface->GetTemplateIdFromFilePath(relativePath); if (templateId == InvalidTemplateId) { // Load the template from the file templateId = m_prefabLoaderInterface->LoadTemplateFromFile(filePath); AZ_Assert(templateId != InvalidTemplateId, "Template with source path %.*s couldn't be loaded correctly.", AZ_STRING_ARG(filePath)); } const PrefabDom& templateDom = m_prefabSystemComponentInterface->FindTemplateDom(templateId); AZStd::unordered_set templatePaths; PrefabDomUtils::GetTemplateSourcePaths(templateDom, templatePaths); if (IsCyclicalDependencyFound(instanceToParentUnder->get(), templatePaths)) { return AZ::Failure(AZStd::string::format( "Instantiate Prefab operation aborted - Cyclical dependency detected\n(%s depends on %s).", relativePath.Native().c_str(), instanceToParentUnder->get().GetTemplateSourcePath().Native().c_str())); } AZ::EntityId containerEntityId; { // Initialize Undo Batch object ScopedUndoBatch undoBatch("Instantiate Prefab"); // Instantiate the Prefab PrefabDom instanceToParentUnderDomBeforeCreate; m_instanceToTemplateInterface->GenerateDomForInstance(instanceToParentUnderDomBeforeCreate, instanceToParentUnder->get()); auto instanceToCreate = prefabEditorEntityOwnershipInterface->InstantiatePrefab(relativePath, instanceToParentUnder); if (!instanceToCreate) { return AZ::Failure(AZStd::string("Could not instantiate the prefab provided - internal error " "(A null instance is returned).")); } PrefabUndoHelpers::UpdatePrefabInstance( instanceToParentUnder->get(), "Update prefab instance", instanceToParentUnderDomBeforeCreate, undoBatch.GetUndoBatch()); // Create Link with correct container patches containerEntityId = instanceToCreate->get().GetContainerEntityId(); AZ::Entity* containerEntity = GetEntityById(containerEntityId); AZ_Assert(containerEntity, "Invalid container entity detected in InstantiatePrefab."); 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)); AzToolsFramework::ToolsApplicationRequestBus::Broadcast( &AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities); } return AZ::Success(containerEntityId); } PrefabOperationResult PrefabPublicHandler::FindCommonRootOwningInstance( const AZStd::vector& entityIds, EntityList& inputEntityList, EntityList& topLevelEntities, AZ::EntityId& commonRootEntityId, InstanceOptionalReference& commonRootEntityOwningInstance) { // Retrieve entityList from entityIds inputEntityList = EntityIdListToEntityList(entityIds); // Remove Level Container Entity if it's part of the list AZ::EntityId levelEntityId = GetLevelInstanceContainerEntityId(); if (levelEntityId.IsValid()) { AZ::Entity* levelEntity = GetEntityById(levelEntityId); if (levelEntity) { auto levelEntityIter = AZStd::find(inputEntityList.begin(), inputEntityList.end(), levelEntity); if (levelEntityIter != inputEntityList.end()) { inputEntityList.erase(levelEntityIter); } } } // Find common root and top level entities bool entitiesHaveCommonRoot = false; AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult( entitiesHaveCommonRoot, &AzToolsFramework::ToolsApplicationRequests::FindCommonRootInactive, inputEntityList, commonRootEntityId, &topLevelEntities); // Bail if entities don't share a common root if (!entitiesHaveCommonRoot) { return AZ::Failure(AZStd::string("Failed to create a prefab: Provided entities do not share a common root.")); } // Retrieve the owning instance of the common root entity, which will be our new instance's parent instance. commonRootEntityOwningInstance = GetOwnerInstanceByEntityId(commonRootEntityId); AZ_Assert( commonRootEntityOwningInstance.has_value(), "Failed to create prefab : Couldn't get a valid owning instance for the common root entity of the enities provided"); return AZ::Success(); } bool PrefabPublicHandler::IsCyclicalDependencyFound( InstanceOptionalConstReference instance, const AZStd::unordered_set& templateSourcePaths) { InstanceOptionalConstReference currentInstance = instance; while (currentInstance.has_value()) { if (templateSourcePaths.contains(currentInstance->get().GetTemplateSourcePath())) { return true; } currentInstance = currentInstance->get().GetParentInstance(); } return false; } void PrefabPublicHandler::CreateLink( Instance& sourceInstance, TemplateId targetTemplateId, UndoSystem::URSequencePoint* undoBatch, PrefabDom patch, const bool isUndoRedoSupportNeeded) { LinkId linkId; if (isUndoRedoSupportNeeded) { linkId = PrefabUndoHelpers::CreateLink( sourceInstance.GetTemplateId(), targetTemplateId, AZStd::move(patch), sourceInstance.GetInstanceAlias(), undoBatch); } else { linkId = m_prefabSystemComponentInterface->CreateLink( targetTemplateId, sourceInstance.GetTemplateId(), sourceInstance.GetInstanceAlias(), patch, InvalidLinkId); m_prefabSystemComponentInterface->PropagateTemplateChanges(targetTemplateId); } sourceInstance.SetLinkId(linkId); } void PrefabPublicHandler::RemoveLink( AZStd::unique_ptr& sourceInstance, TemplateId targetTemplateId, UndoSystem::URSequencePoint* undoBatch) { LinkReference nestedInstanceLink = m_prefabSystemComponentInterface->FindLink(sourceInstance->GetLinkId()); AZ_Assert( nestedInstanceLink.has_value(), "A valid link was not found for one of the instances provided as input for the CreatePrefab operation."); PrefabDomReference nestedInstanceLinkDom = nestedInstanceLink->get().GetLinkDom(); AZ_Assert( nestedInstanceLinkDom.has_value(), "A valid DOM was not found for the link corresponding to one of the instances provided as input for the " "CreatePrefab operation."); PrefabDomValueReference nestedInstanceLinkPatches = PrefabDomUtils::FindPrefabDomValue(nestedInstanceLinkDom->get(), PrefabDomUtils::PatchesName); AZ_Assert( nestedInstanceLinkPatches.has_value(), "A valid DOM for patches was not found for the link corresponding to one of the instances provided as input for the " "CreatePrefab operation."); PrefabDom patchesCopyForUndoSupport; patchesCopyForUndoSupport.CopyFrom(nestedInstanceLinkPatches->get(), patchesCopyForUndoSupport.GetAllocator()); PrefabUndoHelpers::RemoveLink( sourceInstance->GetTemplateId(), targetTemplateId, sourceInstance->GetInstanceAlias(), sourceInstance->GetLinkId(), AZStd::move(patchesCopyForUndoSupport), undoBatch); } PrefabOperationResult PrefabPublicHandler::SavePrefab(AZ::IO::Path filePath) { auto templateId = m_prefabSystemComponentInterface->GetTemplateIdFromFilePath(filePath.c_str()); if (templateId == InvalidTemplateId) { return AZ::Failure( AZStd::string("SavePrefab - Path error. Path could be invalid, or the prefab may not be loaded in this level.")); } if (!m_prefabLoaderInterface->SaveTemplate(templateId)) { return AZ::Failure(AZStd::string("Could not save prefab - internal error (Json write operation failure).")); } return AZ::Success(); } PrefabEntityResult PrefabPublicHandler::CreateEntity(AZ::EntityId parentId, const AZ::Vector3& position) { // If the parent is invalid, parent to the container of the currently focused prefab. if (!parentId.IsValid()) { AzFramework::EntityContextId editorEntityContextId = AzToolsFramework::GetEntityContextId(); parentId = m_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId); } InstanceOptionalReference owningInstanceOfParentEntity = GetOwnerInstanceByEntityId(parentId); if (!owningInstanceOfParentEntity) { return AZ::Failure(AZStd::string::format( "Cannot add entity because the owning instance of parent entity with id '%llu' could not be found.", static_cast(parentId))); } EntityAlias entityAlias = Instance::GenerateEntityAlias(); AliasPath absoluteEntityPath = owningInstanceOfParentEntity->get().GetAbsoluteInstanceAliasPath(); absoluteEntityPath.Append(entityAlias); AZ::EntityId entityId = InstanceEntityIdMapper::GenerateEntityIdForAliasPath(absoluteEntityPath); AZStd::string entityName = AZStd::string::format("Entity%llu", static_cast(m_newEntityCounter++)); AZ::Entity* entity = aznew AZ::Entity(entityId, entityName.c_str()); Instance& entityOwningInstance = owningInstanceOfParentEntity->get(); PrefabDom instanceDomBeforeUpdate; m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBeforeUpdate, entityOwningInstance); ScopedUndoBatch undoBatch("Add Entity"); entityOwningInstance.AddEntity(*entity, entityAlias); EditorEntityContextRequestBus::Broadcast(&EditorEntityContextRequestBus::Events::HandleEntitiesAdded, EntityList{entity}); AZ::Transform transform = AZ::Transform::CreateIdentity(); transform.SetTranslation(position); EntityOptionalReference owningInstanceContainerEntity = entityOwningInstance.GetContainerEntity(); if (owningInstanceContainerEntity && !parentId.IsValid()) { parentId = owningInstanceContainerEntity->get().GetId(); } if (parentId.IsValid()) { AZ::TransformBus::Event(entityId, &AZ::TransformInterface::SetParent, parentId); AZ::TransformBus::Event(entityId, &AZ::TransformInterface::SetLocalTM, transform); } else { AZ::TransformBus::Event(entityId, &AZ::TransformInterface::SetWorldTM, transform); } // Select the new entity (and deselect others). AzToolsFramework::EntityIdList selection = {entityId}; SelectionCommand* selectionCommand = aznew SelectionCommand(selection, ""); selectionCommand->SetParent(undoBatch.GetUndoBatch()); ToolsApplicationRequests::Bus::Broadcast(&ToolsApplicationRequests::SetSelectedEntities, selection); PrefabUndoHelpers::UpdatePrefabInstance( entityOwningInstance, "Undo adding entity", instanceDomBeforeUpdate, undoBatch.GetUndoBatch()); return AZ::Success(entityId); } PrefabOperationResult PrefabPublicHandler::GenerateUndoNodesForEntityChangeAndUpdateCache( AZ::EntityId entityId, UndoSystem::URSequencePoint* parentUndoBatch) { // Create Undo node on entities if they belong to an instance InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId); if (!owningInstance.has_value()) { return AZ::Success(); } AZ::Entity* entity = GetEntityById(entityId); if (!entity) { m_prefabUndoCache.PurgeCache(entityId); return AZ::Success(); } PrefabDom beforeState; AZ::EntityId beforeParentId; m_prefabUndoCache.Retrieve(entityId, beforeState, beforeParentId); PrefabDom afterState; AZ::EntityId afterParentId; AZ::TransformBus::EventResult(afterParentId, entityId, &AZ::TransformBus::Events::GetParentId); m_instanceToTemplateInterface->GenerateDomForEntity(afterState, *entity); PrefabDom patch; m_instanceToTemplateInterface->GeneratePatch(patch, beforeState, afterState); m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, entityId); if (patch.IsArray() && !patch.Empty() && beforeState.IsObject()) { bool isInstanceContainerEntity = IsInstanceContainerEntity(entityId) && !IsLevelInstanceContainerEntity(entityId); bool isNewParentOwnedByDifferentInstance = false; if (beforeParentId != afterParentId) { // If the entity parent changed, verify if the owning instance changed too InstanceOptionalReference beforeOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(beforeParentId); InstanceOptionalReference afterOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(afterParentId); if (beforeOwningInstance.has_value() && afterOwningInstance.has_value() && (&beforeOwningInstance->get() != &afterOwningInstance->get())) { isNewParentOwnedByDifferentInstance = true; // Detect loops. Assert if an instance has been reparented in such a way to generate circular dependencies. AZStd::vector instancesInvolved; if (isInstanceContainerEntity) { instancesInvolved.push_back(&owningInstance->get()); } else { // Retrieve all nested instances that are part of the subtree under the current entity. EntityList entities; PrefabOperationResult retrieveEntitiesAndInstancesOutcome = RetrieveAndSortPrefabEntitiesAndInstances( { entity }, beforeOwningInstance->get(), entities, instancesInvolved); if (!retrieveEntitiesAndInstancesOutcome.IsSuccess()) { return retrieveEntitiesAndInstancesOutcome; } } for (Instance* instance : instancesInvolved) { const PrefabDom& templateDom = m_prefabSystemComponentInterface->FindTemplateDom(instance->GetTemplateId()); AZStd::unordered_set templatePaths; PrefabDomUtils::GetTemplateSourcePaths(templateDom, templatePaths); if (IsCyclicalDependencyFound(afterOwningInstance->get(), templatePaths)) { // Cancel the operation by restoring the previous parent AZ::TransformBus::Event(entityId, &AZ::TransformBus::Events::SetParent, beforeParentId); m_prefabUndoCache.UpdateCache(entityId); // Skip the creation of an undo node return AZ::Failure(AZStd::string::format( "Reparent Prefab operation aborted - Cyclical dependency detected\n(%s depends on %s).", instance->GetTemplateSourcePath().Native().c_str(), afterOwningInstance->get().GetTemplateSourcePath().Native().c_str())); } } } } if (isInstanceContainerEntity) { if (isNewParentOwnedByDifferentInstance) { Internal_HandleInstanceChange(parentUndoBatch, entity, beforeParentId, afterParentId); PrefabDom afterStateafterReparenting; m_instanceToTemplateInterface->GenerateDomForEntity(afterStateafterReparenting, *entity); PrefabDom newPatch; m_instanceToTemplateInterface->GeneratePatch(newPatch, afterState, afterStateafterReparenting); m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(newPatch, entityId); InstanceOptionalReference owningInstanceAfterReparenting = m_instanceEntityMapperInterface->FindOwningInstance(entityId); Internal_HandleContainerOverride( parentUndoBatch, entityId, newPatch, owningInstanceAfterReparenting->get().GetLinkId()); } else { Internal_HandleContainerOverride( parentUndoBatch, entityId, patch, owningInstance->get().GetLinkId(), owningInstance->get().GetParentInstance()); } } else { Internal_HandleEntityChange(parentUndoBatch, entityId, beforeState, afterState, owningInstance); if (isNewParentOwnedByDifferentInstance) { Internal_HandleInstanceChange(parentUndoBatch, entity, beforeParentId, afterParentId); } } } m_prefabUndoCache.UpdateCache(entityId); return AZ::Success(); } void PrefabPublicHandler::Internal_HandleContainerOverride( UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, const PrefabDom& patch, const LinkId linkId, InstanceOptionalReference parentInstance) { // Save these changes as patches to the link PrefabUndoLinkUpdate* linkUpdate = aznew PrefabUndoLinkUpdate(AZStd::to_string(static_cast(entityId))); linkUpdate->SetParent(undoBatch); linkUpdate->Capture(patch, linkId); linkUpdate->Redo(parentInstance); } void PrefabPublicHandler::Internal_HandleEntityChange( UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, PrefabDom& beforeState, PrefabDom& afterState, InstanceOptionalReference instance) { // Update the state of the entity PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast(entityId))); state->SetParent(undoBatch); state->Capture(beforeState, afterState, entityId); state->Redo(instance); } void PrefabPublicHandler::Internal_HandleInstanceChange( UndoSystem::URSequencePoint* undoBatch, AZ::Entity* entity, AZ::EntityId beforeParentId, AZ::EntityId afterParentId) { // If the entity parent changed, verify if the owning instance changed too InstanceOptionalReference beforeOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(beforeParentId); InstanceOptionalReference afterOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(afterParentId); EntityList entities; AZStd::vector instances; // Retrieve all descendant entities and instances of this entity that belonged to the same owning instance. PrefabOperationResult retrieveEntitiesAndInstancesOutcome = RetrieveAndSortPrefabEntitiesAndInstances( { entity }, beforeOwningInstance->get(), entities, instances); AZ_Error("Prefab", retrieveEntitiesAndInstancesOutcome.IsSuccess(), retrieveEntitiesAndInstancesOutcome.GetError().data()); AZStd::vector> instanceUniquePtrs; AZStd::vector> instancePatches; // Remove Entities and Instances from the prior instance { // Remove Instances for (Instance* nestedInstance : instances) { auto linkRef = m_prefabSystemComponentInterface->FindLink(nestedInstance->GetLinkId()); PrefabDom oldLinkPatches; if (linkRef.has_value()) { auto patches = linkRef->get().GetLinkPatches(); if (patches.has_value()) { oldLinkPatches.CopyFrom(patches->get(), oldLinkPatches.GetAllocator()); } } auto nestedInstanceUniquePtr = beforeOwningInstance->get().DetachNestedInstance(nestedInstance->GetInstanceAlias()); RemoveLink(nestedInstanceUniquePtr, beforeOwningInstance->get().GetTemplateId(), undoBatch); instancePatches.emplace_back(AZStd::make_pair(nestedInstanceUniquePtr.get(), AZStd::move(oldLinkPatches))); instanceUniquePtrs.emplace_back(AZStd::move(nestedInstanceUniquePtr)); } // Get the previous state of the prior instance for undo/redo purposes PrefabDom beforeInstanceDomBeforeRemoval; m_instanceToTemplateInterface->GenerateDomForInstance(beforeInstanceDomBeforeRemoval, beforeOwningInstance->get()); // Remove Entities for (AZ::Entity* nestedEntity : entities) { beforeOwningInstance->get().DetachEntity(nestedEntity->GetId()).release(); } // Create the Update node for the prior owning instance // Instance removal will be taken care of from the RemoveLink function for undo/redo purposes PrefabUndoHelpers::UpdatePrefabInstance( beforeOwningInstance->get(), "Update prior prefab instance", beforeInstanceDomBeforeRemoval, undoBatch); } // Add Entities and Instances to new instance { // Add Instances for (auto& instanceUniquePtr : instanceUniquePtrs) { afterOwningInstance->get().AddInstance(AZStd::move(instanceUniquePtr)); } // Create Links for (auto& instanceInfo : instancePatches) { // Add a new link with the old dom CreateLink( *instanceInfo.first, afterOwningInstance->get().GetTemplateId(), undoBatch, AZStd::move(instanceInfo.second)); } // Get the previous state of the new instance for undo/redo purposes PrefabDom afterInstanceDomBeforeAdd; m_instanceToTemplateInterface->GenerateDomForInstance(afterInstanceDomBeforeAdd, afterOwningInstance->get()); // Add Entities for (AZ::Entity* nestedEntity : entities) { afterOwningInstance->get().AddEntity(*nestedEntity); } // Create the Update node for the new owning instance PrefabUndoHelpers::UpdatePrefabInstance( afterOwningInstance->get(), "Update new prefab instance", afterInstanceDomBeforeAdd, undoBatch); } } bool PrefabPublicHandler::IsInstanceContainerEntity(AZ::EntityId entityId) const { InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId); return owningInstance && (owningInstance->get().GetContainerEntityId() == entityId); } bool PrefabPublicHandler::IsLevelInstanceContainerEntity(AZ::EntityId entityId) const { // Get owning instance InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId); // Get level root instance auto prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); if (!prefabEditorEntityOwnershipInterface) { AZ_Assert( false, "Could not get owning instance of common root entity :" "PrefabEditorEntityOwnershipInterface unavailable."); } InstanceOptionalReference levelInstance = prefabEditorEntityOwnershipInterface->GetRootPrefabInstance(); return owningInstance && levelInstance && (&owningInstance->get() == &levelInstance->get()) && (owningInstance->get().GetContainerEntityId() == entityId); } AZ::EntityId PrefabPublicHandler::GetInstanceContainerEntityId(AZ::EntityId entityId) const { AZ::Entity* entity = GetEntityById(entityId); if (entity) { InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entity->GetId()); if (owningInstance) { return owningInstance->get().GetContainerEntityId(); } } return AZ::EntityId(); } AZ::EntityId PrefabPublicHandler::GetLevelInstanceContainerEntityId() const { auto prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); if (!prefabEditorEntityOwnershipInterface) { AZ_Assert( false, "Could not get owning instance of common root entity :" "PrefabEditorEntityOwnershipInterface unavailable."); return AZ::EntityId(); } auto rootInstance = prefabEditorEntityOwnershipInterface->GetRootPrefabInstance(); if (!rootInstance.has_value()) { return AZ::EntityId(); } return rootInstance->get().GetContainerEntityId(); } AZ::IO::Path PrefabPublicHandler::GetOwningInstancePrefabPath(AZ::EntityId entityId) const { AZ::IO::Path path; InstanceOptionalReference instance = GetOwnerInstanceByEntityId(entityId); if (instance.has_value()) { path = instance->get().GetTemplateSourcePath(); } return path; } PrefabRequestResult PrefabPublicHandler::HasUnsavedChanges(AZ::IO::Path prefabFilePath) const { auto templateId = m_prefabSystemComponentInterface->GetTemplateIdFromFilePath(prefabFilePath.c_str()); if (templateId == InvalidTemplateId) { return AZ::Failure(AZStd::string("HasUnsavedChanges - Path error. Path could be invalid, or the prefab may not be loaded in this level.")); } return AZ::Success(m_prefabSystemComponentInterface->IsTemplateDirty(templateId)); } PrefabOperationResult PrefabPublicHandler::DeleteEntitiesInInstance(const EntityIdList& entityIds) { return DeleteFromInstance(entityIds, false); } PrefabOperationResult PrefabPublicHandler::DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) { return DeleteFromInstance(entityIds, true); } PrefabOperationResult PrefabPublicHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds) { if (entityIds.empty()) { return AZ::Failure(AZStd::string("No entities to duplicate.")); } const EntityIdList entityIdsNoFocusContainer = GenerateEntityIdListWithoutFocusedInstanceContainer(entityIds); if (entityIdsNoFocusContainer.empty()) { return AZ::Failure(AZStd::string("No entities to duplicate because only instance selected is the container entity of the focused instance.")); } if (!EntitiesBelongToSameInstance(entityIdsNoFocusContainer)) { return AZ::Failure(AZStd::string("Cannot duplicate multiple entities belonging to different instances with one operation." "Change your selection to contain entities in the same instance.")); } // 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. AZ::EntityId firstEntityIdToDuplicate = entityIdsNoFocusContainer[0]; InstanceOptionalReference commonOwningInstance = GetOwnerInstanceByEntityId(firstEntityIdToDuplicate); if (!commonOwningInstance.has_value()) { return AZ::Failure(AZStd::string("Failed to duplicate : Couldn't get a valid owning instance for the common root entity of the entities provided.")); } // If the first entity id is a container entity id, then we need to mark its parent as the common owning instance // This is because containers, despite representing the nested instance in the parent, are owned by the child. if (commonOwningInstance->get().GetContainerEntityId() == firstEntityIdToDuplicate) { commonOwningInstance = commonOwningInstance->get().GetParentInstance(); } if (!commonOwningInstance.has_value()) { return AZ::Failure(AZStd::string("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(entityIdsNoFocusContainer); AZ_PROFILE_FUNCTION(AzToolsFramework); ScopedUndoBatch undoBatch("Duplicate Entities"); { AZ_PROFILE_SCOPE(AzToolsFramework, "DuplicateEntitiesInInstance::UndoCaptureAndDuplicateEntities"); AZStd::vector entities; AZStd::vector instances; EntityList inputEntityList = EntityIdSetToEntityList(duplicationSet); PrefabOperationResult retrieveEntitiesAndInstancesOutcome = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances); if (!retrieveEntitiesAndInstancesOutcome.IsSuccess()) { return AZStd::move(retrieveEntitiesAndInstancesOutcome); } // Take a snapshot of the instance DOM before we manipulate it PrefabDom instanceDomBefore; m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, commonOwningInstance->get()); // Make a copy of our before instance DOM where we will add our duplicated entities and/or instances PrefabDom instanceDomAfter; instanceDomAfter.CopyFrom(instanceDomBefore, instanceDomAfter.GetAllocator()); EntityIdList duplicatedEntityAndInstanceIds; // Duplicate any nested entities and instances as requested AZStd::unordered_map newInstanceAliasToOldInstanceMap; AZStd::unordered_map duplicateEntityAliasMap; DuplicateNestedEntitiesInInstance(commonOwningInstance->get(), entities, instanceDomAfter, duplicatedEntityAndInstanceIds, duplicateEntityAliasMap); PrefabUndoInstance* command = aznew PrefabUndoInstance("Entity/Instance duplication"); command->SetParent(undoBatch.GetUndoBatch()); command->Capture(instanceDomBefore, instanceDomAfter, commonOwningInstance->get().GetTemplateId()); command->RedoBatched(); DuplicateNestedInstancesInInstance(commonOwningInstance->get(), instances, instanceDomAfter, duplicatedEntityAndInstanceIds, newInstanceAliasToOldInstanceMap); // Create links for our duplicated instances (if any were duplicated) for (auto [newInstanceAlias, oldInstance] : newInstanceAliasToOldInstanceMap) { LinkId oldLinkId = oldInstance->GetLinkId(); auto linkRef = m_prefabSystemComponentInterface->FindLink(oldLinkId); AZ_Assert( linkRef.has_value(), "Unable to find link with id '%llu' during instance duplication.", oldLinkId); PrefabDomValueReference linkPatches = linkRef->get().GetLinkPatches(); AZ_Assert( linkPatches.has_value(), "Link with id '%llu' is missing patches.", oldLinkId); PrefabDom linkPatchesCopy; linkPatchesCopy.CopyFrom(linkPatches->get(), linkPatchesCopy.GetAllocator()); // If the instance was duplicated as part of an ancestor's nested hierarchy, the container's parent patch // will need to be refreshed to point to the new duplicated parent entity auto oldInstanceContainerEntityId = oldInstance->GetContainerEntityId(); AZ_Assert(oldInstanceContainerEntityId.IsValid(), "Instance returned invalid Container Entity Id"); AZ::EntityId previousParentEntityId; AZ::TransformBus::EventResult(previousParentEntityId, oldInstanceContainerEntityId, &AZ::TransformBus::Events::GetParentId); if (previousParentEntityId.IsValid() && AZStd::find(duplicatedEntityAndInstanceIds.begin(), duplicatedEntityAndInstanceIds.end(), previousParentEntityId)) { auto oldParentAlias = commonOwningInstance->get().GetEntityAlias(previousParentEntityId); if (oldParentAlias.has_value() && duplicateEntityAliasMap.contains(oldParentAlias->get())) { // Get the dom into a QString for search/replace purposes rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); linkPatchesCopy.Accept(writer); QString linkPatchesString(buffer.GetString()); ReplaceOldAliases(linkPatchesString, oldParentAlias->get(), duplicateEntityAliasMap[oldParentAlias->get()]); linkPatchesCopy.Parse(linkPatchesString.toUtf8().constData()); } } PrefabUndoHelpers::CreateLink( oldInstance->GetTemplateId(), commonOwningInstance->get().GetTemplateId(), AZStd::move(linkPatchesCopy), newInstanceAlias, undoBatch.GetUndoBatch()); } // Select the duplicated entities/instances auto selectionUndo = aznew SelectionCommand(duplicatedEntityAndInstanceIds, "Select Duplicated Entities/Instances"); selectionUndo->SetParent(undoBatch.GetUndoBatch()); ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::SetSelectedEntities, duplicatedEntityAndInstanceIds); } return AZ::Success(); } PrefabOperationResult PrefabPublicHandler::DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants) { // Remove the container entity of the focused prefab from the list, if it is included. const EntityIdList entityIdsNoFocusContainer = GenerateEntityIdListWithoutFocusedInstanceContainer(entityIds); if (entityIdsNoFocusContainer.empty()) { return AZ::Success(); } // All entities in this list need to belong to the same prefab instance for the operation to be valid. if (!EntitiesBelongToSameInstance(entityIdsNoFocusContainer)) { return AZ::Failure(AZStd::string("Cannot delete multiple entities belonging to different instances with one operation.")); } AZ::EntityId firstEntityIdToDelete = entityIdsNoFocusContainer[0]; InstanceOptionalReference commonOwningInstance = GetOwnerInstanceByEntityId(firstEntityIdToDelete); // If the first entity id is a container entity id, then we need to mark its parent as the common owning instance because you // cannot delete an instance from itself. if (commonOwningInstance->get().GetContainerEntityId() == firstEntityIdToDelete) { commonOwningInstance = commonOwningInstance->get().GetParentInstance(); } // We only allow explicit deletions for entities inside the currently focused prefab. AzFramework::EntityContextId editorEntityContextId = AzToolsFramework::GetEntityContextId(); if (&m_prefabFocusInterface->GetFocusedPrefabInstance(editorEntityContextId)->get() != &commonOwningInstance->get()) { return AZ::Failure(AZStd::string("Cannot delete entities belonging to an instance that is not being edited.")); } // Retrieve entityList from entityIds EntityList inputEntityList = EntityIdListToEntityList(entityIdsNoFocusContainer); AZ_PROFILE_FUNCTION(AzToolsFramework); 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. // Commands always execute themselves first and then their children (when going forwards) // and do the opposite when going backwards. EntityIdList selectedEntities; ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &ToolsApplicationRequests::GetSelectedEntities); SelectionCommand* selCommand = aznew SelectionCommand(selectedEntities, "Delete Entities"); // We insert a "deselect all" command before we delete the entities. This ensures the delete operations aren't changing // selection state, which triggers expensive UI updates. By deselecting up front, we are able to do those expensive // UI updates once at the start instead of once for each entity. { EntityIdList deselection; SelectionCommand* deselectAllCommand = aznew SelectionCommand(deselection, "Deselect Entities"); deselectAllCommand->SetParent(selCommand); } { AZ_PROFILE_SCOPE(AzToolsFramework, "Internal::DeleteEntities:UndoCaptureAndPurgeEntities"); Prefab::PrefabDom instanceDomBefore; m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, commonOwningInstance->get()); if (deleteDescendants) { AZStd::vector entities; AZStd::vector instances; PrefabOperationResult retrieveEntitiesAndInstancesOutcome = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances); if (!retrieveEntitiesAndInstancesOutcome.IsSuccess()) { return AZStd::move(retrieveEntitiesAndInstancesOutcome); } for (AZ::Entity* entity : entities) { commonOwningInstance->get().DetachEntity(entity->GetId()).release(); AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::DeleteEntity, entity->GetId()); } for (auto& nestedInstance : instances) { AZStd::unique_ptr outInstance = commonOwningInstance->get().DetachNestedInstance(nestedInstance->GetInstanceAlias()); RemoveLink(outInstance, commonOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch()); outInstance.reset(); } } else { for (AZ::EntityId entityId : entityIdsNoFocusContainer) { InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId); // If this is the container entity, it actually represents the instance so get its owner if (owningInstance->get().GetContainerEntityId() == entityId) { auto instancePtr = commonOwningInstance->get().DetachNestedInstance(owningInstance->get().GetInstanceAlias()); RemoveLink(instancePtr, commonOwningInstance->get().GetTemplateId(), undoBatch.GetUndoBatch()); } else { commonOwningInstance->get().DetachEntity(entityId); AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::DeleteEntity, entityId); } } } Prefab::PrefabDom instanceDomAfter; m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, commonOwningInstance->get()); PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance deletion"); command->Capture(instanceDomBefore, instanceDomAfter, commonOwningInstance->get().GetTemplateId()); command->SetParent(selCommand); } selCommand->SetParent(undoBatch.GetUndoBatch()); { AZ_PROFILE_SCOPE(AzToolsFramework, "Internal::DeleteEntities:RunRedo"); selCommand->RunRedo(); } return AZ::Success(); } PrefabOperationResult PrefabPublicHandler::DetachPrefab(const AZ::EntityId& containerEntityId) { if (!containerEntityId.IsValid()) { return AZ::Failure(AZStd::string("Cannot detach Prefab Instance with invalid container entity.")); } auto editorEntityContextId = AzFramework::EntityContextId::CreateNull(); EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId); if (containerEntityId == m_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId)) { return AZ::Failure(AZStd::string("Cannot detach focused Prefab Instance.")); } InstanceOptionalReference owningInstance = GetOwnerInstanceByEntityId(containerEntityId); if (owningInstance->get().GetContainerEntityId() != containerEntityId) { return AZ::Failure(AZStd::string("Input entity should be its owning Instance's container entity.")); } AZ_PROFILE_FUNCTION(AzToolsFramework); { AZ_PROFILE_SCOPE(AzToolsFramework, "Internal::DetachPrefab:UndoCapture"); ScopedUndoBatch undoBatch("Detach Prefab"); InstanceOptionalReference getParentInstanceResult = owningInstance->get().GetParentInstance(); AZ_Assert(getParentInstanceResult.has_value(), "Can't get parent Instance from Instance of given container entity."); auto& parentInstance = getParentInstanceResult->get(); const auto parentTemplateId = parentInstance.GetTemplateId(); { auto instancePtr = parentInstance.DetachNestedInstance(owningInstance->get().GetInstanceAlias()); AZ_Assert(instancePtr, "Can't detach selected Instance from its parent Instance."); RemoveLink(instancePtr, parentTemplateId, undoBatch.GetUndoBatch()); Prefab::PrefabDom instanceDomBefore; m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, parentInstance); AZStd::unordered_map oldEntityAliases; oldEntityAliases.emplace(containerEntityId, instancePtr->GetEntityAlias(containerEntityId)->get()); auto containerEntityPtr = instancePtr->DetachContainerEntity(); auto& containerEntity = *containerEntityPtr.release(); auto editorPrefabComponent = containerEntity.FindComponent(); containerEntity.Deactivate(); [[maybe_unused]] const bool editorPrefabComponentRemoved = containerEntity.RemoveComponent(editorPrefabComponent); AZ_Assert(editorPrefabComponentRemoved, "Remove EditorPrefabComponent failed."); delete editorPrefabComponent; containerEntity.Activate(); [[maybe_unused]] const bool containerEntityAdded = parentInstance.AddEntity(containerEntity); AZ_Assert(containerEntityAdded, "Add target Instance's container entity to its parent Instance failed."); EntityIdList entityIds; entityIds.emplace_back(containerEntity.GetId()); instancePtr->GetEntities( [&](AZStd::unique_ptr& entityPtr) { oldEntityAliases.emplace(entityPtr->GetId(), instancePtr->GetEntityAlias(entityPtr->GetId())->get()); return true; }); instancePtr->DetachEntities( [&](AZStd::unique_ptr entityPtr) { auto& entity = *entityPtr.release(); [[maybe_unused]] const bool entityAdded = parentInstance.AddEntity(entity); AZ_Assert(entityAdded, "Add target Instance's entity to its parent Instance failed."); entityIds.emplace_back(entity.GetId()); }); Prefab::PrefabDom instanceDomAfter; m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, parentInstance); PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance detachment"); command->Capture(instanceDomBefore, instanceDomAfter, parentTemplateId); command->SetParent(undoBatch.GetUndoBatch()); { AZ_PROFILE_SCOPE(AzToolsFramework, "Internal::DetachPrefab:RunRedo"); command->RunRedo(); } instancePtr->DetachNestedInstances( [&](AZStd::unique_ptr detachedNestedInstance) { PrefabDom& nestedInstanceTemplateDom = m_prefabSystemComponentInterface->FindTemplateDom(detachedNestedInstance->GetTemplateId()); Instance& nestedInstanceUnderNewParent = parentInstance.AddInstance(AZStd::move(detachedNestedInstance)); PrefabDom nestedInstanceDomUnderNewParent; m_instanceToTemplateInterface->GenerateDomForInstance( nestedInstanceDomUnderNewParent, nestedInstanceUnderNewParent); PrefabDom reparentPatch; m_instanceToTemplateInterface->GeneratePatch( reparentPatch, nestedInstanceTemplateDom, nestedInstanceDomUnderNewParent); CreateLink(nestedInstanceUnderNewParent, parentTemplateId, undoBatch.GetUndoBatch(), AZStd::move(reparentPatch), true); }); } AzToolsFramework::ToolsApplicationRequestBus::Broadcast( &AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities); } return AZ::Success(); } void PrefabPublicHandler::GenerateContainerEntityTransform(const EntityList& topLevelEntities, AZ::Vector3& translation, AZ::Quaternion& rotation) { // --- Multiple top level entities // Translation is the average of all translations, with the minimum Z value. // Rotation is set to zero. if (topLevelEntities.size() > 1) { AZ::Vector3 translationSum = AZ::Vector3::CreateZero(); float minZ = AZStd::numeric_limits::max(); int transformCount = 0; for (AZ::Entity* topLevelEntity : topLevelEntities) { if (topLevelEntity != nullptr) { AzToolsFramework::Components::TransformComponent* transformComponent = topLevelEntity->FindComponent(); if (transformComponent != nullptr) { ++transformCount; auto currentTranslation = transformComponent->GetLocalTranslation(); translationSum += currentTranslation; minZ = AZ::GetMin(minZ, currentTranslation.GetZ()); } } } if (transformCount > 0) { translation = translationSum / aznumeric_cast(transformCount); translation.SetZ(minZ); rotation = AZ::Quaternion::CreateZero(); } } // --- Single top level entity // World Translation and Rotation are inherited, unchanged. else if (topLevelEntities.size() == 1) { AZ::Entity* topLevelEntity = topLevelEntities[0]; if (topLevelEntity) { AzToolsFramework::Components::TransformComponent* transformComponent = topLevelEntity->FindComponent(); if (transformComponent) { translation = transformComponent->GetLocalTranslation(); rotation = transformComponent->GetLocalRotationQuaternion(); } } } } InstanceOptionalReference PrefabPublicHandler::GetOwnerInstanceByEntityId(AZ::EntityId entityId) const { if (entityId.IsValid()) { return m_instanceEntityMapperInterface->FindOwningInstance(entityId); } // If the entityId is invalid, then the owning instance would be the root prefab instance of the // PrefabEditorEntityOwnershipService. auto prefabEditorEntityOwnershipInterface = AZ::Interface::Get(); if (!prefabEditorEntityOwnershipInterface) { AZ_Assert(false, "Could not get owning instance of common root entity :" "PrefabEditorEntityOwnershipInterface unavailable."); } return prefabEditorEntityOwnershipInterface->GetRootPrefabInstance(); } Instance* PrefabPublicHandler::GetParentInstance(Instance* instance) { auto instanceRef = instance->GetParentInstance(); if (instanceRef != AZStd::nullopt) { return &instanceRef->get(); } return nullptr; } Instance* PrefabPublicHandler::GetAncestorOfInstanceThatIsChildOfRoot(const Instance* root, Instance* instance) { while (instance != nullptr) { Instance* parent = GetParentInstance(instance); if (parent == root) { return instance; } instance = parent; } return nullptr; } PrefabOperationResult PrefabPublicHandler::RetrieveAndSortPrefabEntitiesAndInstances( const EntityList& inputEntities, Instance& commonRootEntityOwningInstance, EntityList& outEntities, AZStd::vector& outInstances) const { if (inputEntities.size() == 0) { return AZ::Failure( AZStd::string("An empty list of input entities is provided to retrieve the prefab entities and instances.")); } AZStd::queue entityQueue; auto editorEntityContextId = AzFramework::EntityContextId::CreateNull(); EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId); AZ::EntityId focusedPrefabContainerEntityId = m_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId); for (auto inputEntity : inputEntities) { if (inputEntity && inputEntity->GetId() != focusedPrefabContainerEntityId) { entityQueue.push(inputEntity); } } // Support sets to easily identify if we're processing the same entity multiple times. AZStd::unordered_set entities; AZStd::unordered_set instances; while (!entityQueue.empty()) { AZ::Entity* entity = entityQueue.front(); entityQueue.pop(); // Get this entity's owning instance. InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entity->GetId()); AZ_Assert( owningInstance.has_value(), "An error occurred while retrieving entities and prefab instances : " "Owning instance of entity with name '%s' and id '%llu' couldn't be found", entity->GetName().c_str(), static_cast(entity->GetId())); // Check if this entity is owned by the same instance owning the root. if (&owningInstance->get() == &commonRootEntityOwningInstance) { // If it's the same instance, we can add this entity to the new instance entities. size_t priorEntitiesSize = entities.size(); entities.insert(entity); // If the size of entities increased, then it wasn't added before. // In that case, add the children of this entity to the queue. if (entities.size() > priorEntitiesSize) { EntityIdList childrenIds; EditorEntityInfoRequestBus::EventResult( childrenIds, entity->GetId(), &EditorEntityInfoRequests::GetChildren ); for (AZ::EntityId childId : childrenIds) { AZ::Entity* child = GetEntityById(childId); entityQueue.push(child); } } } else { // The instances differ, so we should add the instance to the instances set, // but only if it's a direct descendant of the root instance! Instance* childInstance = GetAncestorOfInstanceThatIsChildOfRoot(&commonRootEntityOwningInstance, &owningInstance->get()); if (childInstance != nullptr) { instances.insert(childInstance); } else { // This can only happen if one entity does not share the common root! return AZ::Failure(AZStd::string::format( "Entity with name '%s' and id '%llu' has an owning instance that doesn't belong to the instance " "hierarchy of the selected entities.", entity->GetName().c_str(), static_cast(entity->GetId()))); } } } // Store results outEntities.clear(); outEntities.reserve(entities.size()); for (AZ::Entity* entity : entities) { outEntities.emplace_back(entity); } outInstances.clear(); outInstances.reserve(instances.size()); for (Instance* instancePtr : instances) { outInstances.push_back(instancePtr); } if ((outEntities.size() + outInstances.size()) == 0) { return AZ::Failure( AZStd::string("An empty list of entities and prefab instances were retrieved from the selected entities")); } return AZ::Success(); } EntityIdList PrefabPublicHandler::GenerateEntityIdListWithoutFocusedInstanceContainer( const EntityIdList& entityIds) const { EntityIdList outEntityIds(entityIds); AzFramework::EntityContextId editorEntityContextId = AzToolsFramework::GetEntityContextId(); AZ::EntityId focusedInstanceContainerEntityId = m_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId); if (auto iter = AZStd::find(outEntityIds.begin(), outEntityIds.end(), focusedInstanceContainerEntityId); iter != outEntityIds.end()) { outEntityIds.erase(iter); } return outEntityIds; } bool PrefabPublicHandler::EntitiesBelongToSameInstance(const EntityIdList& entityIds) const { if (entityIds.size() <= 1) { return true; } InstanceOptionalReference sharedInstance = AZStd::nullopt; for (AZ::EntityId entityId : entityIds) { InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId); if (!owningInstance.has_value()) { AZ_Assert( false, "An error occurred in function EntitiesBelongToSameInstance: " "Owning instance of entity with id '%llu' couldn't be found", entityId); return false; } // If this is a container entity, it actually represents a child instance so get its owner. // The only exception in the level root instance. We leave it as is to streamline operations. if (owningInstance->get().GetContainerEntityId() == entityId && !IsLevelInstanceContainerEntity(entityId)) { owningInstance = owningInstance->get().GetParentInstance(); } if (!sharedInstance.has_value()) { sharedInstance = owningInstance; } else { if (&sharedInstance->get() != &owningInstance->get()) { return false; } } } return true; } void PrefabPublicHandler::DuplicateNestedEntitiesInInstance(Instance& commonOwningInstance, const AZStd::vector& entities, PrefabDom& domToAddDuplicatedEntitiesUnder, EntityIdList& duplicatedEntityIds, AZStd::unordered_map& oldAliasToNewAliasMap) { if (entities.empty()) { return; } AZStd::unordered_map aliasToEntityDomMap; for (AZ::Entity* entity : entities) { EntityAliasOptionalReference oldAliasRef = commonOwningInstance.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 PrefabDom entityDomBefore(&domToAddDuplicatedEntitiesUnder.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.emplace(oldAlias, newEntityAlias); rapidjson::StringBuffer buffer; rapidjson::Writer 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.emplace(newEntityAlias, entityDomString); } auto entitiesIter = domToAddDuplicatedEntitiesUnder.FindMember(PrefabDomUtils::EntitiesName); AZ_Assert(entitiesIter != domToAddDuplicatedEntitiesUnder.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 [newEntityAlias, newEntityDomString] : aliasToEntityDomMap) { // Replace all of the old alias references with the new ones for (auto [oldAlias, newAlias] : oldAliasToNewAliasMap) { ReplaceOldAliases(newEntityDomString, oldAlias, newAlias); } // Create the new Entity DOM from parsing the JSON string PrefabDom entityDomAfter(&domToAddDuplicatedEntitiesUnder.GetAllocator()); entityDomAfter.Parse(newEntityDomString.toUtf8().constData()); // Add the new Entity DOM to the Entities member of the instance rapidjson::Value aliasName(newEntityAlias.c_str(), static_cast(newEntityAlias.length()), domToAddDuplicatedEntitiesUnder.GetAllocator()); entitiesIter->value.AddMember(AZStd::move(aliasName), entityDomAfter, domToAddDuplicatedEntitiesUnder.GetAllocator()); } for (auto aliasMapIter : oldAliasToNewAliasMap) { EntityAlias newEntityAlias = aliasMapIter.second; AliasPath absoluteEntityPath = commonOwningInstance.GetAbsoluteInstanceAliasPath(); absoluteEntityPath.Append(newEntityAlias); AZ::EntityId newEntityId = InstanceEntityIdMapper::GenerateEntityIdForAliasPath(absoluteEntityPath); duplicatedEntityIds.push_back(newEntityId); } } void PrefabPublicHandler::DuplicateNestedInstancesInInstance(Instance& commonOwningInstance, const AZStd::vector& instances, PrefabDom& domToAddDuplicatedInstancesUnder, EntityIdList& duplicatedEntityIds, AZStd::unordered_map& newInstanceAliasToOldInstanceMap) { if (instances.empty()) { return; } AZStd::unordered_map oldInstanceAliasToNewInstanceAliasMap; AZStd::unordered_map aliasToInstanceDomMap; for (auto instance : instances) { PrefabDom nestedInstanceDomBefore; m_instanceToTemplateInterface->GenerateDomForInstance(nestedInstanceDomBefore, *instance); // Keep track of the old alias <-> new alias mapping for this duplicated instance // so we can fixup references later InstanceAlias oldAlias = instance->GetInstanceAlias(); InstanceAlias newInstanceAlias = Instance::GenerateInstanceAlias(); oldInstanceAliasToNewInstanceAliasMap.emplace(oldAlias, newInstanceAlias); // Keep track of our new instance alias with the Instance it was duplicated from, // so that after all instances are duplicated, we can go back and create links for them newInstanceAliasToOldInstanceMap.emplace(newInstanceAlias, instance); rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); nestedInstanceDomBefore.Accept(writer); // Store our duplicated Instance DOM with its new alias as a string // so that we can fixup instance alias references before adding it // to the Instances member of our instance DOM QString instanceDomString(buffer.GetString()); aliasToInstanceDomMap.emplace(newInstanceAlias, instanceDomString); } auto instancesIter = domToAddDuplicatedInstancesUnder.FindMember(PrefabDomUtils::InstancesName); AZ_Assert(instancesIter != domToAddDuplicatedInstancesUnder.MemberEnd(), "Instance DOM missing the Instances member."); // Now that all the duplicated Instance DOMs have been created, we need to iterate // through them and replace any previous InstanceAlias references with the new ones. for (auto [newInstanceAlias, newInstanceDomString]: aliasToInstanceDomMap) { // Replace all of the old alias references with the new ones for (auto [oldAlias, newAlias] : oldInstanceAliasToNewInstanceAliasMap) { ReplaceOldAliases(newInstanceDomString, oldAlias, newAlias); } // Create the new Instance DOM from parsing the JSON string PrefabDom nestedInstanceDomAfter(&domToAddDuplicatedInstancesUnder.GetAllocator()); nestedInstanceDomAfter.Parse(newInstanceDomString.toUtf8().constData()); // Add the new Instance DOM to the Instances member of the instance rapidjson::Value aliasName(newInstanceAlias.c_str(), static_cast(newInstanceAlias.length()), domToAddDuplicatedInstancesUnder.GetAllocator()); instancesIter->value.AddMember(AZStd::move(aliasName), nestedInstanceDomAfter, domToAddDuplicatedInstancesUnder.GetAllocator()); } for (auto aliasMapIter : oldInstanceAliasToNewInstanceAliasMap) { InstanceAlias newInstanceAlias = aliasMapIter.second; AliasPath absoluteInstancePath = commonOwningInstance.GetAbsoluteInstanceAliasPath(); absoluteInstancePath.Append(newInstanceAlias); absoluteInstancePath.Append(PrefabDomUtils::ContainerEntityName); AZ::EntityId newEntityId = InstanceEntityIdMapper::GenerateEntityIdForAliasPath(absoluteInstancePath); duplicatedEntityIds.push_back(newEntityId); } } void PrefabPublicHandler::ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias) { // 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 QString oldAliasQuotes = QString("\"%1\"").arg(oldAlias.data()); QString newAliasQuotes = QString("\"%1\"").arg(newAlias.data()); stringToReplace.replace(oldAliasQuotes, newAliasQuotes); QString oldAliasPathRef = QString("/%1").arg(oldAlias.data()); QString newAliasPathRef = QString("/%1").arg(newAlias.data()); stringToReplace.replace(oldAliasPathRef, newAliasPathRef); } void PrefabPublicHandler::UpdateLinkPatchesWithNewEntityAliases( PrefabDom& linkPatch, const AZStd::unordered_map& oldEntityAliases, Instance& newParent) { rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); linkPatch.Accept(writer); QString previousPatchString(buffer.GetString()); for (const auto& [entityId, oldEntityAlias] : oldEntityAliases) { EntityAliasOptionalReference newEntityAlias = newParent.GetEntityAlias(entityId); AZ_Assert( newEntityAlias.has_value(), "Could not fetch entity alias for entity with id '%llu' during prefab creation.", static_cast(entityId)); ReplaceOldAliases(previousPatchString, oldEntityAlias, newEntityAlias->get()); } linkPatch.Parse(previousPatchString.toUtf8().constData()); } } // namespace Prefab } // namespace AzToolsFramework