From 4b610058b5eadc9f2885885094eb2858cebb8db5 Mon Sep 17 00:00:00 2001 From: chiyteng Date: Wed, 26 May 2021 15:47:41 -0700 Subject: [PATCH] implement function DetachPrefabFromParent in PrefabPublicHandler --- .../Prefab/Instance/Instance.cpp | 12 + .../Prefab/Instance/Instance.h | 5 +- .../Instance/InstanceToTemplatePropagator.cpp | 5 + .../Prefab/PrefabPublicHandler.cpp | 224 +++++++++++++++++- .../Prefab/PrefabPublicHandler.h | 2 + .../Prefab/PrefabPublicInterface.h | 8 + .../UI/Prefab/PrefabIntegrationManager.cpp | 32 +++ .../UI/Prefab/PrefabIntegrationManager.h | 1 + 8 files changed, 275 insertions(+), 14 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp index c4cb3e0316..8c7604f680 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp @@ -401,6 +401,17 @@ namespace AzToolsFramework } } + InstancePtrOptionalReference Instance::GetNestedInstance(const InstanceAlias& instanceAlias) + { + auto nestedInstanceIterator = m_nestedInstances.find(instanceAlias); + if (nestedInstanceIterator != m_nestedInstances.end()) + { + return nestedInstanceIterator->second; + } + + return AZStd::nullopt; + } + void Instance::GetNestedInstances(const AZStd::function&)>& callback) { for (auto& [instanceAlias, instance] : m_nestedInstances) @@ -613,6 +624,7 @@ namespace AzToolsFramework AZStd::unique_ptr Instance::DetachContainerEntity() { + m_instanceEntityMapper->UnregisterEntity(m_containerEntity->GetId()); return AZStd::move(m_containerEntity); } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h index 821478b706..377be68753 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h @@ -48,6 +48,8 @@ namespace AzToolsFramework using EntityAliasOptionalReference = AZStd::optional>; using InstanceOptionalReference = AZStd::optional>; using InstanceOptionalConstReference = AZStd::optional>; + using InstancePtrOptionalReference = AZStd::optional>>; + using InstanceSet = AZStd::unordered_set; using InstanceSetConstReference = AZStd::optional>; using EntityOptionalReference = AZStd::optional>; @@ -85,6 +87,7 @@ namespace AzToolsFramework bool AddEntity(AZ::Entity& entity); bool AddEntity(AZ::Entity& entity, EntityAlias entityAlias); AZStd::unique_ptr DetachEntity(const AZ::EntityId& entityId); + void DetachEntities(const AZStd::function)>& callback); void DetachNestedEntities(const AZStd::function)>& callback); void RemoveNestedEntities(const AZStd::function&)>& filter); @@ -92,6 +95,7 @@ namespace AzToolsFramework Instance& AddInstance(AZStd::unique_ptr instance); AZStd::unique_ptr DetachNestedInstance(const InstanceAlias& instanceAlias); + InstancePtrOptionalReference GetNestedInstance(const InstanceAlias& instanceAlias); /** * Gets the aliases for the entities in the Instance DOM. @@ -182,7 +186,6 @@ namespace AzToolsFramework void ClearEntities(); - void DetachEntities(const AZStd::function)>& callback); void RemoveEntities(const AZStd::function&)>& filter); bool RegisterEntity(const AZ::EntityId& entityId, const EntityAlias& entityAlias); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp index 6d3ddedd51..cd8fad1725 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp @@ -176,10 +176,15 @@ namespace AzToolsFramework { PrefabDom& templateDomReference = m_prefabSystemComponentInterface->FindTemplateDom(templateId); + PrefabDomUtils::PrintPrefabDomValue("providedPatch", providedPatch); + PrefabDomUtils::PrintPrefabDomValue("templateDomReference", templateDomReference); + //apply patch to template AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::ApplyPatch(templateDomReference, templateDomReference.GetAllocator(), providedPatch, AZ::JsonMergeApproach::JsonPatch); + PrefabDomUtils::PrintPrefabDomValue("templateDomReference(Patch applied)", templateDomReference); + //trigger propagation if (result.GetOutcome() == AZ::JsonSerializationResult::Outcomes::Success) { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index 0181050a32..315c7b5710 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -986,6 +987,216 @@ namespace AzToolsFramework return AZ::Success(); } + PrefabOperationResult PrefabPublicHandler::DetachPrefabFromParent(const AZ::EntityId& entityId) + { + if (!entityId.IsValid()) + { + return AZ::Failure(AZStd::string("Cannot detach Prefab Instance with invalid container entity.")); + } + + if (IsLevelInstanceContainerEntity(entityId)) + { + return AZ::Failure(AZStd::string("Cannot detach level Prefab Instance.")); + } + + InstanceOptionalReference owningInstance = GetOwnerInstanceByEntityId(entityId); + if (owningInstance->get().GetContainerEntityId() != entityId) + { + return AZ::Failure(AZStd::string("Input entity should be its owning Instance's container entity.")); + } + + 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, "Detach Prefab"); + AZ_Assert(currentUndoBatch, "Failed to create new undo batch."); + } + + // In order to undo Prefab Instance detachment, we have to create a selection command which selects the current selection + // and then add the detach 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, "Detach Prefab"); + + // We insert a "deselect all" command before we detach the Prefab Instance. This ensures the detach 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(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DetachPrefab:UndoCapture"); + + InstanceOptionalReference parentInstance = owningInstance->get().GetParentInstance(); + const auto parentTemplateId = parentInstance->get().GetTemplateId(); + + Prefab::PrefabDom instanceDomBefore; + m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, parentInstance->get()); + + { + auto getInstancePtrResult = parentInstance->get().GetNestedInstance(owningInstance->get().GetInstanceAlias()); + AZ_Assert(getInstancePtrResult, "Can't find selected container entity's owning Instance."); + + auto& instancePtr = getInstancePtrResult->get(); + + AZStd::unordered_map oldEntityAliases; + oldEntityAliases.emplace(entityId, instancePtr->GetEntityAlias(entityId)->get()); + + auto containerEntityPtr = instancePtr->DetachContainerEntity(); + auto& containerEntity = *containerEntityPtr.release(); + auto editorPrefabComponent = containerEntity.FindComponent(); + containerEntity.Deactivate(); + const bool editorPrefabComponentRemoved = containerEntity.RemoveComponent(editorPrefabComponent); + AZ_Assert(editorPrefabComponentRemoved, "Remove EditorPrefabComponent failed."); + delete editorPrefabComponent; + containerEntity.Activate(); + + const bool containerEntityAdded = parentInstance->get().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(); + const bool entityAdded = parentInstance->get().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->get()); + + PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance detachment"); + command->Capture(instanceDomBefore, instanceDomAfter, parentTemplateId); + command->SetParent(selCommand); + + selCommand->SetParent(currentUndoBatch); + { + AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DetachPrefab:RunRedo"); + selCommand->RunRedo(); + } + + const auto instanceTemplateId = instancePtr->GetTemplateId(); + auto parentContainerEntityId = parentInstance->get().GetContainerEntityId(); + instancePtr->GetNestedInstances( + [&](AZStd::unique_ptr& nestedInstancePtr) + { + //get previous link patch + auto linkRef = m_prefabSystemComponentInterface->FindLink(nestedInstancePtr->GetLinkId()); + PrefabDomValueReference linkPatches = linkRef->get().GetLinkPatches(); + AZ_Assert( + linkPatches.has_value(), "Unable to get patches on link with id '%llu' during prefab creation.", + nestedInstancePtr->GetLinkId()); + + PrefabDom linkPatchesCopy; + linkPatchesCopy.CopyFrom(linkPatches->get(), linkPatchesCopy.GetAllocator()); + + RemoveLink(nestedInstancePtr, instanceTemplateId, currentUndoBatch); + + /*auto getNestedInstanceContainerEntityResult = nestedInstancePtr->GetContainerEntity(); + AZ_Assert(getNestedInstanceContainerEntityResult.has_value(), "Can't get nested instance container entitt."); + + auto& nestedInstanceContainerEntity = getNestedInstanceContainerEntityResult->get(); + auto nestedInstanceContainerEntityId = nestedInstanceContainerEntity.GetId(); + + PrefabDom containerEntityDomBefore; + m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomBefore, nestedInstanceContainerEntity); + + AZ::TransformBus::Event(nestedInstanceContainerEntityId, &AZ::TransformInterface::SetParent, containerEntity.GetId()); + + PrefabDom containerEntityDomAfter; + m_instanceToTemplateInterface->GenerateDomForEntity(containerEntityDomAfter, nestedInstanceContainerEntity); + + PrefabDom reparentPatch; + m_instanceToTemplateInterface->GeneratePatch(reparentPatch, containerEntityDomBefore, containerEntityDomAfter); + m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(reparentPatch, nestedInstanceContainerEntityId);*/ + + PrefabDomUtils::PrintPrefabDomValue("linkPatchesCopy", linkPatchesCopy); + + //update aliases + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + linkPatchesCopy.Accept(writer); + QString previousPatchString(buffer.GetString()); + + for (AZ::EntityId entityId : entityIds) + { + AZStd::string oldEntityAlias = oldEntityAliases[entityId]; + EntityAliasOptionalReference newEntityAlias = parentInstance->get().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()); + } + + linkPatchesCopy.Parse(previousPatchString.toUtf8().constData()); + + CreateLink(*nestedInstancePtr, parentTemplateId, currentUndoBatch, AZStd::move(linkPatchesCopy), true); + + //update links? + //// 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(nestedInstanceContainerEntityId))); + //linkUpdate->SetParent(undoBatch.GetUndoBatch()); + //linkUpdate->Capture(reparentPatch, nestedInstance->GetLinkId()); + + //linkUpdate->Redo(); + }); + + RemoveLink(instancePtr, parentTemplateId, currentUndoBatch); + + AzToolsFramework::ToolsApplicationRequestBus::Broadcast( + &AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities); + } + } + + if (createdUndo) + { + ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::EndUndoBatch); + } + + return AZ::Success(); + } + + void PrefabPublicHandler::ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias) + { + 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::GenerateContainerEntityTransform(const EntityList& topLevelEntities, AZ::Vector3& translation, AZ::Quaternion& rotation) { @@ -1236,18 +1447,5 @@ namespace AzToolsFramework return true; } - - void PrefabPublicHandler::ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias) - { - 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); - } } // namespace Prefab } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h index 7e2357dd44..f1c32ee35c 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h @@ -64,6 +64,8 @@ namespace AzToolsFramework PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override; PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override; + PrefabOperationResult DetachPrefabFromParent(const AZ::EntityId& entityId) override; + private: PrefabOperationResult DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants); bool RetrieveAndSortPrefabEntitiesAndInstances(const EntityList& inputEntities, Instance& commonRootEntityOwningInstance, diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h index 0750c4d264..f12bc359f2 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h @@ -150,6 +150,14 @@ namespace AzToolsFramework * @return An outcome object; on failure, it comes with an error message detailing the cause of the error. */ virtual PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0; + + /** + * Detaches target container entity's owning instance from its parent instance. + * Bails if the entity is not a container entity or belongs to the level prefab instance. + * @param entityId The container entity whose instance to detach. + * @return An outcome object; on failure, it comes with an error message detailing the cause of the error. + */ + virtual PrefabOperationResult DetachPrefabFromParent(const AZ::EntityId& entityId) = 0; }; } // namespace Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp index 3edc190fb7..090c7bdc54 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp @@ -237,6 +237,27 @@ namespace AzToolsFramework { deleteAction->setDisabled(true); } + + QAction* detachPrefabAction = menu->addAction(QObject::tr("Detach Prefab...")); + if (selectedEntities.size() != 1) + { + detachPrefabAction->setDisabled(true); + } + else + { + AZ::EntityId selectedEntity = selectedEntities[0]; + + if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity) && + !s_prefabPublicInterface->IsLevelInstanceContainerEntity(selectedEntity)) + { + QObject::connect(detachPrefabAction, &QAction::triggered, detachPrefabAction, + [this, selectedEntity] { ContextMenu_DetachPrefab(selectedEntity); }); + } + else + { + detachPrefabAction->setDisabled(true); + } + } } void PrefabIntegrationManager::HandleSourceFileType(AZStd::string_view sourceFilePath, AZ::EntityId parentId, AZ::Vector3 position) const @@ -392,6 +413,17 @@ namespace AzToolsFramework } } + void PrefabIntegrationManager::ContextMenu_DetachPrefab(AZ::EntityId containerEntity) + { + PrefabOperationResult detachPrefabResult = + s_prefabPublicInterface->DetachPrefabFromParent(containerEntity); + + if (!detachPrefabResult.IsSuccess()) + { + WarnUserOfError("Detach Prefab error", detachPrefabResult.GetError()); + } + } + void PrefabIntegrationManager::GenerateSuggestedFilenameFromEntities(const EntityIdList& entityIds, AZStd::string& outName) { AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h index c9b846aa5b..69ec3013cc 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h @@ -93,6 +93,7 @@ namespace AzToolsFramework static void ContextMenu_EditPrefab(AZ::EntityId containerEntity); static void ContextMenu_SavePrefab(AZ::EntityId containerEntity); static void ContextMenu_DeleteSelected(); + static void ContextMenu_DetachPrefab(AZ::EntityId containerEntity); // Prompt and resolve dialogs static bool QueryUserForPrefabSaveLocation(