From 4b610058b5eadc9f2885885094eb2858cebb8db5 Mon Sep 17 00:00:00 2001 From: chiyteng Date: Wed, 26 May 2021 15:47:41 -0700 Subject: [PATCH 01/45] 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( From 4fc7d72b2b755fc8ccd7a1f4baee601015a96f4d Mon Sep 17 00:00:00 2001 From: chiyteng Date: Wed, 26 May 2021 16:49:09 -0700 Subject: [PATCH 02/45] modify DetachPrefabFromParent function for debugging --- .../Prefab/PrefabPublicHandler.cpp | 44 +++---------------- 1 file changed, 6 insertions(+), 38 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index 315c7b5710..d3553c78e1 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -1042,14 +1042,14 @@ namespace AzToolsFramework 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 = parentInstance->get().DetachNestedInstance(owningInstance->get().GetInstanceAlias()); + AZ_Assert(instancePtr, "Can't detach selected Instance from its parent Instance."); - auto& instancePtr = getInstancePtrResult->get(); + RemoveLink(instancePtr, parentTemplateId, currentUndoBatch); + + Prefab::PrefabDom instanceDomBefore; + m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, parentInstance->get()); AZStd::unordered_map oldEntityAliases; oldEntityAliases.emplace(entityId, instancePtr->GetEntityAlias(entityId)->get()); @@ -1115,25 +1115,6 @@ namespace AzToolsFramework 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 @@ -1156,21 +1137,8 @@ namespace AzToolsFramework 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); } From 053e273b97bcacb191886afcad15c917addfa45c Mon Sep 17 00:00:00 2001 From: srikappa Date: Wed, 26 May 2021 18:24:49 -0700 Subject: [PATCH 03/45] Clear dirty entities at the end of InstantiatePrefab logic --- .../AzToolsFramework/Prefab/PrefabPublicHandler.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index d3553c78e1..1b98711352 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -384,7 +384,10 @@ namespace AzToolsFramework 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)); + //m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter)); + + AzToolsFramework::ToolsApplicationRequestBus::Broadcast( + &AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities); } return AZ::Success(); From 78afc45709a26a03c30a1d8159671a91482840c8 Mon Sep 17 00:00:00 2001 From: scottr Date: Thu, 27 May 2021 12:39:14 -0700 Subject: [PATCH 04/45] [ftue_auto_register] add logic to auto register engine if it is not already --- .../ProjectManager/Source/PythonBindings.cpp | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 8c79a153c8..0672e02ff4 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -286,6 +286,30 @@ namespace O3DE::ProjectManager m_registration = pybind11::module::import("cmake.Tools.registration"); m_engineTemplate = pybind11::module::import("cmake.Tools.engine_template"); + // register the current engine if it isn't already + bool registerThis = true; + auto allEngines = m_registration.attr("get_engines")(); + if (pybind11::isinstance(allEngines)) + { + for (const auto& engine : allEngines) + { + AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"])); + if (enginePath.Compare(m_enginePath) == 0) + { + registerThis = false; + break; + } + } + } + + if (registerThis) + { + auto registrationResult = m_registration.attr("register")(m_enginePath.c_str()); + + AZ_Error("ProjectManagerWindow", registrationResult.cast() == 0, + "Registration of this engine failed!"); + } + return result == 0 && !PyErr_Occurred(); } catch ([[maybe_unused]] const std::exception& e) { From ecc18338fa0ef9f71c9ee4956ae7275a5cbf91af Mon Sep 17 00:00:00 2001 From: scottr Date: Thu, 27 May 2021 14:19:31 -0700 Subject: [PATCH 05/45] [ftue_auto_register] move engine registration check into private helper function --- .../ProjectManager/Source/PythonBindings.cpp | 56 +++++++++++-------- .../ProjectManager/Source/PythonBindings.h | 2 + 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 0672e02ff4..90e18f5f27 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -286,29 +286,8 @@ namespace O3DE::ProjectManager m_registration = pybind11::module::import("cmake.Tools.registration"); m_engineTemplate = pybind11::module::import("cmake.Tools.engine_template"); - // register the current engine if it isn't already - bool registerThis = true; - auto allEngines = m_registration.attr("get_engines")(); - if (pybind11::isinstance(allEngines)) - { - for (const auto& engine : allEngines) - { - AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"])); - if (enginePath.Compare(m_enginePath) == 0) - { - registerThis = false; - break; - } - } - } - - if (registerThis) - { - auto registrationResult = m_registration.attr("register")(m_enginePath.c_str()); - - AZ_Error("ProjectManagerWindow", registrationResult.cast() == 0, - "Registration of this engine failed!"); - } + // make sure the engine is registered + RegisterThisEngine(); return result == 0 && !PyErr_Occurred(); } catch ([[maybe_unused]] const std::exception& e) @@ -332,6 +311,37 @@ namespace O3DE::ProjectManager return !PyErr_Occurred(); } + bool PythonBindings::RegisterThisEngine() + { + bool registerThis = true; + + // check current engine path against all other registered engines + // to see if we are already registered + auto allEngines = m_registration.attr("get_engines")(); + if (pybind11::isinstance(allEngines)) + { + for (const auto& engine : allEngines) + { + AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"])); + if (enginePath.Compare(m_enginePath) == 0) + { + registerThis = false; + break; + } + } + } + + bool result = true; + if (registerThis) + { + auto registrationResult = m_registration.attr("register")(m_enginePath.c_str()); + result = (registrationResult.cast() == 0); + } + + AZ_Error("ProjectManagerWindow", result, "Registration of this engine failed!"); + return result; + } + bool PythonBindings::ExecuteWithLock(AZStd::function executionCallback) { AZStd::lock_guard lock(m_lock); diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h index 892e13a65b..71616de4e0 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -60,9 +60,11 @@ namespace O3DE::ProjectManager GemInfo GemInfoFromPath(pybind11::handle path); ProjectInfo ProjectInfoFromPath(pybind11::handle path); ProjectTemplateInfo ProjectTemplateInfoFromPath(pybind11::handle path); + bool RegisterThisEngine(); bool StartPython(); bool StopPython(); + AZ::IO::FixedMaxPath m_enginePath; pybind11::handle m_engineTemplate; AZStd::recursive_mutex m_lock; From 3137b961bf860f03aac17bd5192477380c7056e3 Mon Sep 17 00:00:00 2001 From: scottr Date: Thu, 27 May 2021 14:54:51 -0700 Subject: [PATCH 06/45] [ftue_auto_register] elevated error to assert if registration fails --- Code/Tools/ProjectManager/Source/PythonBindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 90e18f5f27..7158bfefb7 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -338,7 +338,7 @@ namespace O3DE::ProjectManager result = (registrationResult.cast() == 0); } - AZ_Error("ProjectManagerWindow", result, "Registration of this engine failed!"); + AZ_Assert(result, "Registration of this engine failed!"); return result; } From e1dfac34fc7fba0d5478c1a412e368a1805aad86 Mon Sep 17 00:00:00 2001 From: scottr Date: Thu, 27 May 2021 15:24:39 -0700 Subject: [PATCH 07/45] [ftue_auto_register] wrapped registration pybind calls with ExecuteWithLock --- .../ProjectManager/Source/PythonBindings.cpp | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 7158bfefb7..1a3e30f99f 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -313,33 +313,38 @@ namespace O3DE::ProjectManager bool PythonBindings::RegisterThisEngine() { - bool registerThis = true; - - // check current engine path against all other registered engines - // to see if we are already registered - auto allEngines = m_registration.attr("get_engines")(); - if (pybind11::isinstance(allEngines)) - { - for (const auto& engine : allEngines) + bool registrationResult = true; // already registered is considered successful + bool pythonResult = ExecuteWithLock( + [&] { - AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"])); - if (enginePath.Compare(m_enginePath) == 0) + bool registerThis = true; + + // check current engine path against all other registered engines + // to see if we are already registered + auto allEngines = m_registration.attr("get_engines")(); + if (pybind11::isinstance(allEngines)) { - registerThis = false; - break; + for (const auto& engine : allEngines) + { + AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"])); + if (enginePath.Compare(m_enginePath) == 0) + { + registerThis = false; + break; + } + } } - } - } - bool result = true; - if (registerThis) - { - auto registrationResult = m_registration.attr("register")(m_enginePath.c_str()); - result = (registrationResult.cast() == 0); - } + if (registerThis) + { + auto result = m_registration.attr("register")(m_enginePath.c_str()); + registrationResult = (result.cast() == 0); + } + }); - AZ_Assert(result, "Registration of this engine failed!"); - return result; + bool finalResult = (registrationResult && pythonResult); + AZ_Assert(finalResult, "Registration of this engine failed!"); + return finalResult; } bool PythonBindings::ExecuteWithLock(AZStd::function executionCallback) From c21a59af50933d1d0541de154b2ef154de3c6c44 Mon Sep 17 00:00:00 2001 From: chiyteng Date: Thu, 27 May 2021 17:19:32 -0700 Subject: [PATCH 08/45] Remove print dom for debug previously --- .../Prefab/Instance/InstanceToTemplatePropagator.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp index cd8fad1725..6d3ddedd51 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp @@ -176,15 +176,10 @@ 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) { From 57913d837854fafe4b47725266d62aaabc694f6e Mon Sep 17 00:00:00 2001 From: srikappa Date: Thu, 27 May 2021 18:19:28 -0700 Subject: [PATCH 09/45] A couple of bug fixes --- .../AzToolsFramework/Prefab/PrefabPublicHandler.cpp | 6 +++--- .../AzToolsFramework/Prefab/PrefabSystemComponent.cpp | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index 1b98711352..f6ded4ae45 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -1141,12 +1141,12 @@ namespace AzToolsFramework CreateLink(*nestedInstancePtr, parentTemplateId, currentUndoBatch, AZStd::move(linkPatchesCopy), true); }); - - AzToolsFramework::ToolsApplicationRequestBus::Broadcast( - &AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities); } } + AzToolsFramework::ToolsApplicationRequestBus::Broadcast( + &AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities); + if (createdUndo) { ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::EndUndoBatch); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp index c4e6415b02..1d478ad581 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp @@ -652,7 +652,8 @@ namespace AzToolsFramework if (instancesValue->get().FindMember(rapidjson::StringRef(instanceAlias.c_str())) == instancesValue->get().MemberEnd()) { instancesValue->get().AddMember( - rapidjson::StringRef(instanceAlias.c_str()), PrefabDomValue(), targetTemplateDom.GetAllocator()); + rapidjson::Value(instanceAlias.c_str(), targetTemplateDom.GetAllocator()), PrefabDomValue(), + targetTemplateDom.GetAllocator()); } Template& sourceTemplate = sourceTemplateRef->get(); From dc4a15628f4702d54035a7680dc3c68a478fcdf9 Mon Sep 17 00:00:00 2001 From: srikappa Date: Fri, 28 May 2021 00:43:31 -0700 Subject: [PATCH 10/45] Remove unused GetNestedInstance method in Prefab Instance class --- .../AzToolsFramework/Prefab/Instance/Instance.cpp | 11 ----------- .../AzToolsFramework/Prefab/Instance/Instance.h | 1 - 2 files changed, 12 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp index 8c7604f680..8f483ec818 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp @@ -401,17 +401,6 @@ 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) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h index 377be68753..31dfb7b8b4 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h @@ -95,7 +95,6 @@ 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. From be6cee806ddcc1b629cfcf3fad51c5429091a504 Mon Sep 17 00:00:00 2001 From: srikappa Date: Fri, 28 May 2021 02:14:28 -0700 Subject: [PATCH 11/45] Show detach prefab only when a single instance is selected --- .../Prefab/PrefabPublicHandler.cpp | 26 +++++++++---------- .../UI/Prefab/PrefabIntegrationManager.cpp | 21 +++++++-------- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index f6ded4ae45..653b7d4878 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -1155,19 +1155,6 @@ namespace AzToolsFramework 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) { @@ -1418,5 +1405,18 @@ 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/UI/Prefab/PrefabIntegrationManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp index 090c7bdc54..45499a4071 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp @@ -238,24 +238,21 @@ namespace AzToolsFramework deleteAction->setDisabled(true); } - QAction* detachPrefabAction = menu->addAction(QObject::tr("Detach Prefab...")); - if (selectedEntities.size() != 1) - { - detachPrefabAction->setDisabled(true); - } - else + // Detach Prefab + if (selectedEntities.size() == 1) { 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); + QAction* detachPrefabAction = menu->addAction(QObject::tr("Detach Prefab...")); + QObject::connect( + detachPrefabAction, &QAction::triggered, detachPrefabAction, + [this, selectedEntity] + { + ContextMenu_DetachPrefab(selectedEntity); + }); } } } From 55e1da64bb2e95a6668fd919619334a74ebb8d90 Mon Sep 17 00:00:00 2001 From: srikappa Date: Fri, 28 May 2021 02:29:09 -0700 Subject: [PATCH 12/45] Renamed a function and improved comments --- .../AzToolsFramework/Prefab/Instance/Instance.h | 1 - .../AzToolsFramework/Prefab/PrefabPublicHandler.cpp | 2 +- .../AzToolsFramework/Prefab/PrefabPublicHandler.h | 2 +- .../AzToolsFramework/Prefab/PrefabPublicInterface.h | 9 ++++++--- .../UI/Prefab/PrefabIntegrationManager.cpp | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h index 31dfb7b8b4..68bc395012 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h @@ -48,7 +48,6 @@ 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>; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index 653b7d4878..7ec0ddf8ad 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -990,7 +990,7 @@ namespace AzToolsFramework return AZ::Success(); } - PrefabOperationResult PrefabPublicHandler::DetachPrefabFromParent(const AZ::EntityId& entityId) + PrefabOperationResult PrefabPublicHandler::DetachPrefab(const AZ::EntityId& entityId) { if (!entityId.IsValid()) { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h index f1c32ee35c..f3b3b242dd 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h @@ -64,7 +64,7 @@ namespace AzToolsFramework PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override; PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override; - PrefabOperationResult DetachPrefabFromParent(const AZ::EntityId& entityId) override; + PrefabOperationResult DetachPrefab(const AZ::EntityId& entityId) override; private: PrefabOperationResult DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h index f12bc359f2..ec13a852ee 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h @@ -152,12 +152,15 @@ namespace AzToolsFramework virtual PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0; /** - * Detaches target container entity's owning instance from its parent instance. + * If the entity id is a container entity id, detaches the prefab instance corresponding to it. This includes converting + * the container entity into a regular entity and putting it under the parent prefab, removing the link between this + * instance and the parent, removing links between this instance and it's nested instances, adding entities directly + * owned by this instance under the 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. + * @param entityId The container entity id of the 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; + virtual PrefabOperationResult DetachPrefab(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 45499a4071..b7feb1a8c1 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp @@ -413,7 +413,7 @@ namespace AzToolsFramework void PrefabIntegrationManager::ContextMenu_DetachPrefab(AZ::EntityId containerEntity) { PrefabOperationResult detachPrefabResult = - s_prefabPublicInterface->DetachPrefabFromParent(containerEntity); + s_prefabPublicInterface->DetachPrefab(containerEntity); if (!detachPrefabResult.IsSuccess()) { From 984db9c1b48571998f5a19381262ce391ed2727d Mon Sep 17 00:00:00 2001 From: chiyteng Date: Fri, 28 May 2021 10:56:03 -0700 Subject: [PATCH 13/45] Remove selection command and update undo batch in DetachPrefab function --- .../Prefab/PrefabPublicHandler.cpp | 53 ++++--------------- 1 file changed, 10 insertions(+), 43 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index 7ec0ddf8ad..e223775add 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -1010,38 +1010,11 @@ 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, "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"); + ScopedUndoBatch undoBatch("Detach Prefab"); + InstanceOptionalReference parentInstance = owningInstance->get().GetParentInstance(); const auto parentTemplateId = parentInstance->get().GetTemplateId(); @@ -1049,7 +1022,7 @@ namespace AzToolsFramework auto instancePtr = parentInstance->get().DetachNestedInstance(owningInstance->get().GetInstanceAlias()); AZ_Assert(instancePtr, "Can't detach selected Instance from its parent Instance."); - RemoveLink(instancePtr, parentTemplateId, currentUndoBatch); + RemoveLink(instancePtr, parentTemplateId, undoBatch.GetUndoBatch()); Prefab::PrefabDom instanceDomBefore; m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, parentInstance->get()); @@ -1094,12 +1067,10 @@ namespace AzToolsFramework PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance detachment"); command->Capture(instanceDomBefore, instanceDomAfter, parentTemplateId); - command->SetParent(selCommand); - - selCommand->SetParent(currentUndoBatch); + command->SetParent(undoBatch.GetUndoBatch()); { AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DetachPrefab:RunRedo"); - selCommand->RunRedo(); + command->RunRedo(); } const auto instanceTemplateId = instancePtr->GetTemplateId(); @@ -1117,7 +1088,7 @@ namespace AzToolsFramework PrefabDom linkPatchesCopy; linkPatchesCopy.CopyFrom(linkPatches->get(), linkPatchesCopy.GetAllocator()); - RemoveLink(nestedInstancePtr, instanceTemplateId, currentUndoBatch); + RemoveLink(nestedInstancePtr, instanceTemplateId, undoBatch.GetUndoBatch()); PrefabDomUtils::PrintPrefabDomValue("linkPatchesCopy", linkPatchesCopy); //update aliases @@ -1139,17 +1110,13 @@ namespace AzToolsFramework linkPatchesCopy.Parse(previousPatchString.toUtf8().constData()); - CreateLink(*nestedInstancePtr, parentTemplateId, currentUndoBatch, AZStd::move(linkPatchesCopy), true); + CreateLink(*nestedInstancePtr, parentTemplateId, undoBatch.GetUndoBatch(), + AZStd::move(linkPatchesCopy), true); }); } - } - AzToolsFramework::ToolsApplicationRequestBus::Broadcast( - &AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities); - - if (createdUndo) - { - ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::EndUndoBatch); + AzToolsFramework::ToolsApplicationRequestBus::Broadcast( + &AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities); } return AZ::Success(); From de4cfdb5d7d7ef8c736763eb366013640e6d00b5 Mon Sep 17 00:00:00 2001 From: chiyteng Date: Fri, 28 May 2021 11:33:58 -0700 Subject: [PATCH 14/45] Add helper function to update entity aliases in link patch --- .../Prefab/PrefabPublicHandler.cpp | 67 +++++++++++-------- .../Prefab/PrefabPublicHandler.h | 7 +- 2 files changed, 45 insertions(+), 29 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index e223775add..1c645100d1 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -189,24 +189,7 @@ namespace AzToolsFramework if (nestedInstanceLinkPatchesMap.contains(nestedInstance.get())) { previousPatch = AZStd::move(nestedInstanceLinkPatchesMap[nestedInstance.get()]); - rapidjson::StringBuffer buffer; - rapidjson::Writer writer(buffer); - previousPatch.Accept(writer); - QString previousPatchString(buffer.GetString()); - - for (AZ::Entity* entity : entities) - { - AZ::EntityId entityId = entity->GetId(); - AZStd::string oldEntityAlias = oldEntityAliases[entityId]; - EntityAliasOptionalReference newEntityAlias = instanceToCreate->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()); - } - - previousPatch.Parse(previousPatchString.toUtf8().constData()); + UpdateLinkPatchForNewParent(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 @@ -1015,17 +998,20 @@ namespace AzToolsFramework ScopedUndoBatch undoBatch("Detach Prefab"); - InstanceOptionalReference parentInstance = owningInstance->get().GetParentInstance(); - const auto parentTemplateId = parentInstance->get().GetTemplateId(); + 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->get().DetachNestedInstance(owningInstance->get().GetInstanceAlias()); + 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->get()); + m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, parentInstance); AZStd::unordered_map oldEntityAliases; oldEntityAliases.emplace(entityId, instancePtr->GetEntityAlias(entityId)->get()); @@ -1039,7 +1025,7 @@ namespace AzToolsFramework delete editorPrefabComponent; containerEntity.Activate(); - const bool containerEntityAdded = parentInstance->get().AddEntity(containerEntity); + const bool containerEntityAdded = parentInstance.AddEntity(containerEntity); AZ_Assert(containerEntityAdded, "Add target Instance's container entity to its parent Instance failed."); EntityIdList entityIds; @@ -1056,14 +1042,14 @@ namespace AzToolsFramework [&](AZStd::unique_ptr entityPtr) { auto& entity = *entityPtr.release(); - const bool entityAdded = parentInstance->get().AddEntity(entity); + 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->get()); + m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, parentInstance); PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance detachment"); command->Capture(instanceDomBefore, instanceDomAfter, parentTemplateId); @@ -1074,7 +1060,7 @@ namespace AzToolsFramework } const auto instanceTemplateId = instancePtr->GetTemplateId(); - auto parentContainerEntityId = parentInstance->get().GetContainerEntityId(); + auto parentContainerEntityId = parentInstance.GetContainerEntityId(); instancePtr->GetNestedInstances( [&](AZStd::unique_ptr& nestedInstancePtr) { @@ -1089,8 +1075,8 @@ namespace AzToolsFramework linkPatchesCopy.CopyFrom(linkPatches->get(), linkPatchesCopy.GetAllocator()); RemoveLink(nestedInstancePtr, instanceTemplateId, undoBatch.GetUndoBatch()); - PrefabDomUtils::PrintPrefabDomValue("linkPatchesCopy", linkPatchesCopy); + UpdateLinkPatchForNewParent(linkPatchesCopy, oldEntityAliases, parentInstance); //update aliases rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); @@ -1100,7 +1086,7 @@ namespace AzToolsFramework for (AZ::EntityId entityId : entityIds) { AZStd::string oldEntityAlias = oldEntityAliases[entityId]; - EntityAliasOptionalReference newEntityAlias = parentInstance->get().GetEntityAlias(entityId); + EntityAliasOptionalReference newEntityAlias = parentInstance.GetEntityAlias(entityId); AZ_Assert( newEntityAlias.has_value(), "Could not fetch entity alias for entity with id '%llu' during prefab creation.", @@ -1385,5 +1371,30 @@ namespace AzToolsFramework stringToReplace.replace(oldAliasPathRef, newAliasPathRef); } + + void PrefabPublicHandler::UpdateLinkPatchForNewParent( + 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 diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h index f3b3b242dd..c339c17a48 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h @@ -134,7 +134,12 @@ namespace AzToolsFramework bool IsCyclicalDependencyFound( InstanceOptionalConstReference instance, const AZStd::unordered_set& templateSourcePaths); - void ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias); + static void UpdateLinkPatchForNewParent( + PrefabDom& linkPatch, + const AZStd::unordered_map& oldEntityAliases, + Instance& newParent); + + static void ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias); static Instance* GetParentInstance(Instance* instance); static Instance* GetAncestorOfInstanceThatIsChildOfRoot(const Instance* ancestor, Instance* descendant); From da0ab84f1cbd279294eab50ea832e5122f06e147 Mon Sep 17 00:00:00 2001 From: chiyteng Date: Fri, 28 May 2021 11:42:36 -0700 Subject: [PATCH 15/45] Add helper function to update entity aliases in link patch --- .../Prefab/PrefabPublicHandler.cpp | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index 1c645100d1..fdc4302c1b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -1077,25 +1077,7 @@ namespace AzToolsFramework RemoveLink(nestedInstancePtr, instanceTemplateId, undoBatch.GetUndoBatch()); UpdateLinkPatchForNewParent(linkPatchesCopy, oldEntityAliases, parentInstance); - //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.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, undoBatch.GetUndoBatch(), AZStd::move(linkPatchesCopy), true); }); From d115eae84a4470a8272a7a1ce91e06b9a335a34f Mon Sep 17 00:00:00 2001 From: guthadam Date: Tue, 1 Jun 2021 11:43:05 -0500 Subject: [PATCH 16/45] LYN-3871/3872 Added JSON serializer for MaterialAssignment property overrides --- .../Source/Material/MaterialAssignment.cpp | 8 + .../Material/MaterialAssignmentSerializer.cpp | 214 ++++++++++++++++++ .../Material/MaterialAssignmentSerializer.h | 50 ++++ ...m_feature_common_staticlibrary_files.cmake | 2 + .../Material/EditorMaterialComponentSlot.cpp | 4 +- 5 files changed, 275 insertions(+), 3 deletions(-) create mode 100644 Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.cpp create mode 100644 Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.h diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp index ffb5469aef..b4d8200dbc 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp @@ -12,8 +12,11 @@ #include #include +#include #include +#include "MaterialAssignmentSerializer.h" + namespace AZ { namespace Render @@ -22,6 +25,11 @@ namespace AZ { MaterialAssignmentId::Reflect(context); + if (auto jsonContext = azrtti_cast(context)) + { + jsonContext->Serializer()->HandlesType(); + } + if (auto serializeContext = azrtti_cast(context)) { serializeContext->RegisterGenericType(); diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.cpp new file mode 100644 index 0000000000..bb68a05a3e --- /dev/null +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.cpp @@ -0,0 +1,214 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include "MaterialAssignmentSerializer.h" +#include + +namespace AZ +{ + namespace Render + { + AZ_CLASS_ALLOCATOR_IMPL(JsonMaterialAssignmentSerializer, AZ::SystemAllocator, 0); + + JsonSerializationResult::Result JsonMaterialAssignmentSerializer::Load( + void* outputValue, [[maybe_unused]] const Uuid& outputValueTypeId, const rapidjson::Value& inputValue, + JsonDeserializerContext& context) + { + namespace JSR = JsonSerializationResult; + + AZ_Assert( + azrtti_typeid() == outputValueTypeId, + "Unable to deserialize MaterialAssignment from json because the provided type is %s.", + outputValueTypeId.ToString().c_str()); + + AZ::Render::MaterialAssignment* materialAssignment = reinterpret_cast(outputValue); + AZ_Assert(materialAssignment, "Output value for JsonMaterialAssignmentSerializer can't be null."); + + JSR::ResultCode result(JSR::Tasks::ReadField); + { + result.Combine(ContinueLoadingFromJsonObjectField( + &materialAssignment->m_materialAsset, azrtti_typeidm_materialAsset)>(), inputValue, + "MaterialAsset", context)); + } + + if (inputValue.HasMember("PropertyOverrides") && inputValue["PropertyOverrides"].IsObject()) + { + // Attempt to load material property override values for a subset of types + for (const auto& inputPropertyPair : inputValue["PropertyOverrides"].GetObject()) + { + const AZ::Name propertyName(inputPropertyPair.name.GetString()); + if (!propertyName.IsEmpty()) + { + AZStd::any propertyValue; + if (LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny(propertyValue, inputPropertyPair.value, context, result) || + LoadAny>(propertyValue, inputPropertyPair.value, context, result) || + LoadAny>(propertyValue, inputPropertyPair.value, context, result)) + { + materialAssignment->m_propertyOverrides[propertyName] = propertyValue; + } + } + } + } + + return context.Report( + result, + result.GetProcessing() != JSR::Processing::Halted ? "Succesfully loaded MaterialAssignment information." + : "Failed to load MaterialAssignment information."); + } + + JsonSerializationResult::Result JsonMaterialAssignmentSerializer::Store( + rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue, [[maybe_unused]] const Uuid& valueTypeId, + JsonSerializerContext& context) + { + namespace JSR = AZ::JsonSerializationResult; + + AZ_Assert( + azrtti_typeid() == valueTypeId, + "Unable to Serialize MaterialAssignment because the provided type is %s.", valueTypeId.ToString().c_str()); + + const AZ::Render::MaterialAssignment* materialAssignment = reinterpret_cast(inputValue); + AZ_Assert(materialAssignment, "Input value for JsonMaterialAssignmentSerializer can't be null."); + const AZ::Render::MaterialAssignment* defaultMaterialAssignmentInstance = + reinterpret_cast(defaultValue); + + outputValue.SetObject(); + + JSR::ResultCode result(JSR::Tasks::WriteValue); + { + AZ::ScopedContextPath subPathMaterialAsset(context, "m_materialAsset"); + const AZ::Data::Asset* materialAsset = &materialAssignment->m_materialAsset; + const AZ::Data::Asset* defaultmaterialAsset = + defaultMaterialAssignmentInstance ? &defaultMaterialAssignmentInstance->m_materialAsset : nullptr; + + result.Combine(ContinueStoringToJsonObjectField( + outputValue, "MaterialAsset", materialAsset, defaultmaterialAsset, + azrtti_typeidm_materialAsset)>(), context)); + } + + { + AZ::ScopedContextPath subPathPropertyOverrides(context, "m_propertyOverrides"); + if (!materialAssignment->m_propertyOverrides.empty()) + { + rapidjson::Value outputPropertyValueContainer; + outputPropertyValueContainer.SetObject(); + + // Attempt to extract and store material property override values for a subset of types + for (const auto& propertyPair : materialAssignment->m_propertyOverrides) + { + const AZ::Name& propertyName = propertyPair.first; + const AZStd::any& propertyValue = propertyPair.second; + if (!propertyName.IsEmpty() && !propertyValue.empty()) + { + rapidjson::Value outputPropertyValue; + if (StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny(propertyValue, outputPropertyValue, context, result) || + StoreAny>(propertyValue, outputPropertyValue, context, result) || + StoreAny>( + propertyValue, outputPropertyValue, context, result)) + { + outputPropertyValueContainer.AddMember( + rapidjson::Value::StringRefType(propertyName.GetCStr()), outputPropertyValue, + context.GetJsonAllocator()); + } + } + } + + if (outputPropertyValueContainer.MemberCount() > 0) + { + outputValue.AddMember("PropertyOverrides", outputPropertyValueContainer, context.GetJsonAllocator()); + } + } + } + + return context.Report( + result, + result.GetProcessing() != JSR::Processing::Halted ? "Successfully stored MaterialAssignment information." + : "Failed to store MaterialAssignment information."); + } + + template + bool JsonMaterialAssignmentSerializer::LoadAny( + AZStd::any& propertyValue, const rapidjson::Value& inputPropertyValue, AZ::JsonDeserializerContext& context, + AZ::JsonSerializationResult::ResultCode& result) + { + if (inputPropertyValue.IsObject() && inputPropertyValue.HasMember("Value") && inputPropertyValue.HasMember("$type")) + { + // Requiring explicit type info to differentiate be=tween colors versus vectors and numeric types + const AZ::Uuid baseTypeId = azrtti_typeid(); + AZ::Uuid typeId = AZ::Uuid::CreateNull(); + result.Combine(LoadTypeId(typeId, inputPropertyValue, context, &baseTypeId)); + + if (typeId == azrtti_typeid()) + { + T value = {}; + result.Combine(ContinueLoadingFromJsonObjectField(&value, azrtti_typeid(), inputPropertyValue, "Value", context)); + propertyValue = value; + return true; + } + } + return false; + } + + template + bool JsonMaterialAssignmentSerializer::StoreAny( + const AZStd::any& propertyValue, rapidjson::Value& outputPropertyValue, AZ::JsonSerializerContext& context, + AZ::JsonSerializationResult::ResultCode& result) + { + if (propertyValue.is()) + { + outputPropertyValue.SetObject(); + + // Storing explicit type info to differentiate be=tween colors versus vectors and numeric types + rapidjson::Value typeValue; + result.Combine(StoreTypeId(typeValue, azrtti_typeid(), context)); + outputPropertyValue.AddMember("$type", typeValue, context.GetJsonAllocator()); + + T value = AZStd::any_cast(propertyValue); + result.Combine( + ContinueStoringToJsonObjectField(outputPropertyValue, "Value", &value, nullptr, azrtti_typeid(), context)); + return true; + } + return false; + } + } // namespace Render +} // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.h b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.h new file mode 100644 index 0000000000..14db019668 --- /dev/null +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.h @@ -0,0 +1,50 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include +#include + +namespace AZ +{ + namespace Render + { + // Custom JSON serializer for material assignment objects containing AZStd::any property overrides, + // which aren't supported by the system + class JsonMaterialAssignmentSerializer : public BaseJsonSerializer + { + public: + AZ_RTTI(JsonMaterialAssignmentSerializer, "{3D33653E-4582-483F-91F5-BBCC347C3DF0}", BaseJsonSerializer); + AZ_CLASS_ALLOCATOR_DECL; + + JsonSerializationResult::Result Load( + void* outputValue, const Uuid& outputValueTypeId, const rapidjson::Value& inputValue, + JsonDeserializerContext& context) override; + + JsonSerializationResult::Result Store( + rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue, const Uuid& valueTypeId, + JsonSerializerContext& context) override; + + private: + template + bool LoadAny( + AZStd::any& propertyValue, const rapidjson::Value& inputPropertyValue, AZ::JsonDeserializerContext& context, + AZ::JsonSerializationResult::ResultCode& result); + template + bool StoreAny( + const AZStd::any& propertyValue, rapidjson::Value& outputPropertyValue, AZ::JsonSerializerContext& context, + AZ::JsonSerializationResult::ResultCode& result); + }; + } // namespace Render +} // namespace AZ diff --git a/Gems/Atom/Feature/Common/Code/atom_feature_common_staticlibrary_files.cmake b/Gems/Atom/Feature/Common/Code/atom_feature_common_staticlibrary_files.cmake index 553f307409..285659ab82 100644 --- a/Gems/Atom/Feature/Common/Code/atom_feature_common_staticlibrary_files.cmake +++ b/Gems/Atom/Feature/Common/Code/atom_feature_common_staticlibrary_files.cmake @@ -16,6 +16,8 @@ set(FILES Include/Atom/Feature/Utils/ModelPreset.h Source/Material/MaterialAssignment.cpp Source/Material/MaterialAssignmentId.cpp + Source/Material/MaterialAssignmentSerializer.cpp + Source/Material/MaterialAssignmentSerializer.h Source/Utils/LightingPreset.cpp Source/Utils/ModelPreset.cpp ) diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp index 7ebf25454d..bdc82acda6 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentSlot.cpp @@ -78,11 +78,9 @@ namespace AZ if (AZ::SerializeContext* serializeContext = azrtti_cast(context)) { serializeContext->Class() - ->Version(4, &EditorMaterialComponentSlot::ConvertVersion) + ->Version(5, &EditorMaterialComponentSlot::ConvertVersion) ->Field("id", &EditorMaterialComponentSlot::m_id) ->Field("materialAsset", &EditorMaterialComponentSlot::m_materialAsset) - ->Field("propertyOverrides", &EditorMaterialComponentSlot::m_propertyOverrides) - ->Field("matModUvOverrides", &EditorMaterialComponentSlot::m_matModUvOverrides) ; if (AZ::EditContext* editContext = serializeContext->GetEditContext()) From e8e9096dda7ea79d6275f26db4eb0dbf8eb82932 Mon Sep 17 00:00:00 2001 From: srikappa Date: Tue, 1 Jun 2021 11:11:13 -0700 Subject: [PATCH 17/45] Changed a function name and removed a comment --- .../AzToolsFramework/Prefab/PrefabPublicHandler.cpp | 9 +++------ .../AzToolsFramework/Prefab/PrefabPublicHandler.h | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index fdc4302c1b..3187b55721 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -189,7 +189,7 @@ namespace AzToolsFramework if (nestedInstanceLinkPatchesMap.contains(nestedInstance.get())) { previousPatch = AZStd::move(nestedInstanceLinkPatchesMap[nestedInstance.get()]); - UpdateLinkPatchForNewParent(previousPatch, oldEntityAliases, instanceToCreate->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 @@ -366,9 +366,6 @@ namespace AzToolsFramework 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)); - AzToolsFramework::ToolsApplicationRequestBus::Broadcast( &AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities); } @@ -1076,7 +1073,7 @@ namespace AzToolsFramework RemoveLink(nestedInstancePtr, instanceTemplateId, undoBatch.GetUndoBatch()); - UpdateLinkPatchForNewParent(linkPatchesCopy, oldEntityAliases, parentInstance); + UpdateLinkPatchesWithNewEntityAliases(linkPatchesCopy, oldEntityAliases, parentInstance); CreateLink(*nestedInstancePtr, parentTemplateId, undoBatch.GetUndoBatch(), AZStd::move(linkPatchesCopy), true); @@ -1354,7 +1351,7 @@ namespace AzToolsFramework stringToReplace.replace(oldAliasPathRef, newAliasPathRef); } - void PrefabPublicHandler::UpdateLinkPatchForNewParent( + void PrefabPublicHandler::UpdateLinkPatchesWithNewEntityAliases( PrefabDom& linkPatch, const AZStd::unordered_map& oldEntityAliases, Instance& newParent) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h index c339c17a48..acaf9b5753 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h @@ -134,7 +134,7 @@ namespace AzToolsFramework bool IsCyclicalDependencyFound( InstanceOptionalConstReference instance, const AZStd::unordered_set& templateSourcePaths); - static void UpdateLinkPatchForNewParent( + void UpdateLinkPatchesWithNewEntityAliases( PrefabDom& linkPatch, const AZStd::unordered_map& oldEntityAliases, Instance& newParent); From 9fd690f0048a2d84abdec52271346ab86c28d553 Mon Sep 17 00:00:00 2001 From: srikappa Date: Tue, 1 Jun 2021 17:03:50 -0700 Subject: [PATCH 18/45] Changed a function parameter name --- .../AzToolsFramework/Prefab/PrefabPublicHandler.cpp | 12 ++++++------ .../AzToolsFramework/Prefab/PrefabPublicHandler.h | 2 +- .../AzToolsFramework/Prefab/PrefabPublicInterface.h | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index afda2aef09..b44d8fde14 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -973,20 +973,20 @@ namespace AzToolsFramework return AZ::Success(); } - PrefabOperationResult PrefabPublicHandler::DetachPrefab(const AZ::EntityId& entityId) + PrefabOperationResult PrefabPublicHandler::DetachPrefab(const AZ::EntityId& containerEntityId) { - if (!entityId.IsValid()) + if (!containerEntityId.IsValid()) { return AZ::Failure(AZStd::string("Cannot detach Prefab Instance with invalid container entity.")); } - if (IsLevelInstanceContainerEntity(entityId)) + if (IsLevelInstanceContainerEntity(containerEntityId)) { return AZ::Failure(AZStd::string("Cannot detach level Prefab Instance.")); } - InstanceOptionalReference owningInstance = GetOwnerInstanceByEntityId(entityId); - if (owningInstance->get().GetContainerEntityId() != entityId) + InstanceOptionalReference owningInstance = GetOwnerInstanceByEntityId(containerEntityId); + if (owningInstance->get().GetContainerEntityId() != containerEntityId) { return AZ::Failure(AZStd::string("Input entity should be its owning Instance's container entity.")); } @@ -1014,7 +1014,7 @@ namespace AzToolsFramework m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, parentInstance); AZStd::unordered_map oldEntityAliases; - oldEntityAliases.emplace(entityId, instancePtr->GetEntityAlias(entityId)->get()); + oldEntityAliases.emplace(containerEntityId, instancePtr->GetEntityAlias(containerEntityId)->get()); auto containerEntityPtr = instancePtr->DetachContainerEntity(); auto& containerEntity = *containerEntityPtr.release(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h index f73af93195..e7b6f8c932 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h @@ -64,7 +64,7 @@ namespace AzToolsFramework PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override; PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override; - PrefabOperationResult DetachPrefab(const AZ::EntityId& entityId) override; + PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) override; private: PrefabOperationResult DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h index ad6a28cb3e..1dbed53223 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h @@ -157,10 +157,10 @@ namespace AzToolsFramework * instance and the parent, removing links between this instance and it's nested instances, adding entities directly * owned by this instance under the parent instance. * Bails if the entity is not a container entity or belongs to the level prefab instance. - * @param entityId The container entity id of the instance to detach. + * @param containerEntityId The container entity id of the instance to detach. * @return An outcome object; on failure, it comes with an error message detailing the cause of the error. */ - virtual PrefabOperationResult DetachPrefab(const AZ::EntityId& entityId) = 0; + virtual PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) = 0; }; } // namespace Prefab From 553318ed17f5e5673a2b4d73a46a96d804e21e36 Mon Sep 17 00:00:00 2001 From: Benjamin Jillich Date: Tue, 1 Jun 2021 14:01:52 +0200 Subject: [PATCH 19/45] [LYN-2514] Extending gem model * Added was previously added state for gems. * Added helpers to add/remove gems from the model. * Helpers for extracting the gem model indices to be added/removed. --- .../Source/GemCatalog/GemModel.cpp | 65 +++++++++++++++++-- .../Source/GemCatalog/GemModel.h | 15 ++++- 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp index df11c4c7a6..6c09c95572 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp @@ -38,6 +38,7 @@ namespace O3DE::ProjectManager item->setData(aznumeric_cast(gemInfo.m_platforms), RolePlatforms); item->setData(aznumeric_cast(gemInfo.m_types), RoleTypes); item->setData(gemInfo.m_summary, RoleSummary); + item->setData(false, RoleWasPreviouslyAdded); item->setData(gemInfo.m_isAdded, RoleIsAdded); item->setData(gemInfo.m_directoryLink, RoleDirectoryLink); item->setData(gemInfo.m_documentationLink, RoleDocLink); @@ -47,6 +48,7 @@ namespace O3DE::ProjectManager item->setData(gemInfo.m_lastUpdatedDate, RoleLastUpdated); item->setData(gemInfo.m_binarySizeInKB, RoleBinarySize); item->setData(gemInfo.m_features, RoleFeatures); + item->setData(gemInfo.m_path, RolePath); appendRow(item); @@ -89,11 +91,6 @@ namespace O3DE::ProjectManager return modelIndex.data(RoleSummary).toString(); } - bool GemModel::IsAdded(const QModelIndex& modelIndex) - { - return modelIndex.data(RoleIsAdded).toBool(); - } - QString GemModel::GetDirectoryLink(const QModelIndex& modelIndex) { return modelIndex.data(RoleDirectoryLink).toString(); @@ -180,4 +177,62 @@ namespace O3DE::ProjectManager { return modelIndex.data(RoleFeatures).toStringList(); } + + QString GemModel::GetPath(const QModelIndex& modelIndex) + { + return modelIndex.data(RolePath).toString(); + } + + bool GemModel::IsAdded(const QModelIndex& modelIndex) + { + return modelIndex.data(RoleIsAdded).toBool(); + } + + void GemModel::SetIsAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded) + { + model.setData(modelIndex, isAdded, RoleIsAdded); + } + + void GemModel::SetWasPreviouslyAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool wasAdded) + { + model.setData(modelIndex, wasAdded, RoleWasPreviouslyAdded); + } + + bool GemModel::NeedsToBeAdded(const QModelIndex& modelIndex) + { + return (!modelIndex.data(RoleWasPreviouslyAdded).toBool() && modelIndex.data(RoleIsAdded).toBool()); + } + + bool GemModel::NeedsToBeRemoved(const QModelIndex& modelIndex) + { + return (modelIndex.data(RoleWasPreviouslyAdded).toBool() && !modelIndex.data(RoleIsAdded).toBool()); + } + + QVector GemModel::GatherGemsToBeAdded() const + { + QVector result; + for (int row = 0; row < rowCount(); ++row) + { + const QModelIndex modelIndex = index(row, 0); + if (NeedsToBeAdded(modelIndex)) + { + result.push_back(modelIndex); + } + } + return result; + } + + QVector GemModel::GatherGemsToBeRemoved() const + { + QVector result; + for (int row = 0; row < rowCount(); ++row) + { + const QModelIndex modelIndex = index(row, 0); + if (NeedsToBeRemoved(modelIndex)) + { + result.push_back(modelIndex); + } + } + return result; + } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h index 0caa399b58..77f973a91c 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h @@ -46,13 +46,22 @@ namespace O3DE::ProjectManager static GemInfo::Platforms GetPlatforms(const QModelIndex& modelIndex); static GemInfo::Types GetTypes(const QModelIndex& modelIndex); static QString GetSummary(const QModelIndex& modelIndex); - static bool IsAdded(const QModelIndex& modelIndex); static QString GetDirectoryLink(const QModelIndex& modelIndex); static QString GetDocLink(const QModelIndex& modelIndex); static QString GetVersion(const QModelIndex& modelIndex); static QString GetLastUpdated(const QModelIndex& modelIndex); static int GetBinarySizeInKB(const QModelIndex& modelIndex); static QStringList GetFeatures(const QModelIndex& modelIndex); + static QString GetPath(const QModelIndex& modelIndex); + + static bool IsAdded(const QModelIndex& modelIndex); + static void SetIsAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded); + static void SetWasPreviouslyAdded(QAbstractItemModel& model, const QModelIndex& modelIndex, bool wasAdded); + static bool NeedsToBeAdded(const QModelIndex& modelIndex); + static bool NeedsToBeRemoved(const QModelIndex& modelIndex); + + QVector GatherGemsToBeAdded() const; + QVector GatherGemsToBeRemoved() const; private: enum UserRole @@ -62,6 +71,7 @@ namespace O3DE::ProjectManager RoleGemOrigin, RolePlatforms, RoleSummary, + RoleWasPreviouslyAdded, RoleIsAdded, RoleDirectoryLink, RoleDocLink, @@ -71,7 +81,8 @@ namespace O3DE::ProjectManager RoleLastUpdated, RoleBinarySize, RoleFeatures, - RoleTypes + RoleTypes, + RolePath }; QHash m_nameToIndexMap; From 7b6226a8aa5f7d7f504c1c6a975abfeff00c4d96 Mon Sep 17 00:00:00 2001 From: Benjamin Jillich Date: Tue, 1 Jun 2021 14:03:11 +0200 Subject: [PATCH 20/45] [LYN-2514] GemCatalog: Item delegate changes enabled/disabled state in the gem model When clicking the button on the right side of the gem item delegate, it changes the enabled/disabled state of the gem in the model. --- .../Source/GemCatalog/GemItemDelegate.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp index 57200e3b36..0fc0d89fcb 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp @@ -131,6 +131,22 @@ namespace O3DE::ProjectManager return false; } + if (event->type() == QEvent::MouseButtonPress) + { + QMouseEvent* mouseEvent = static_cast(event); + + QRect fullRect, itemRect, contentRect; + CalcRects(option, fullRect, itemRect, contentRect); + const QRect buttonRect = CalcButtonRect(contentRect); + + if (buttonRect.contains(mouseEvent->pos())) + { + const bool isAdded = GemModel::IsAdded(modelIndex); + GemModel::SetIsAdded(*model, modelIndex, !isAdded); + return true; + } + } + return QStyledItemDelegate::editorEvent(event, model, option, modelIndex); } From 4a5b7edbfe864c6a5bbb38824302ac22644394ca Mon Sep 17 00:00:00 2001 From: Tom Hulton-Harrop <82228511+hultonha@users.noreply.github.com> Date: Wed, 2 Jun 2021 09:20:45 +0100 Subject: [PATCH 21/45] Updates to kd-tree ray intersection - ATOM-15673 (#1026) * updates to kd-tree ray intersection * update tests for kd-tree * add one more test for kd-tree intersection * updates to ModelKdTree following review feedback * improve api doc comment for RayIntersection in ModelKdTree * updates following review feedback * update .clang-format to stack parameters if they do not all fit on one line --- .clang-format | 1 + .../Atom/RPI.Reflect/Model/ModelKdTree.h | 20 +++++- .../Code/Source/RPI.Public/Model/Model.cpp | 9 ++- .../Source/RPI.Reflect/Model/ModelKdTree.cpp | 71 ++++++++++++------- Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp | 69 ++++++++++++++++-- .../Code/Source/Mesh/EditorMeshComponent.cpp | 9 ++- .../Source/Mesh/MeshComponentController.cpp | 8 +-- 7 files changed, 143 insertions(+), 44 deletions(-) diff --git a/.clang-format b/.clang-format index 04e0284f97..ef3ce64192 100644 --- a/.clang-format +++ b/.clang-format @@ -13,6 +13,7 @@ AllowShortFunctionsOnASingleLine: None AllowShortLambdasOnASingleLine: None AlwaysBreakAfterReturnType: None AlwaysBreakTemplateDeclarations: true +BinPackParameters: false BreakBeforeBraces: Custom BraceWrapping: AfterClass: true diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelKdTree.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelKdTree.h index 9cd33eda6c..832681d7f1 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelKdTree.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelKdTree.h @@ -35,7 +35,15 @@ namespace AZ ModelKdTree() = default; bool Build(const ModelAsset* model); - bool RayIntersection(const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distance, AZ::Vector3& normal) const; + //! Return if a ray intersected the model. + //! @param raySrc The starting point of the ray. + //! @param rayDir The direction and length of the ray (magnitude is encoded in the direction). + //! @param[out] The normalized distance of the intersection (in the range 0.0-1.0) - to calculate the actual + //! distance, multiply distanceNormalized by the magnitude of rayDir. + //! @param[out] The surface normal of the intersection with the model. + //! @return Return true if there was an intersection with the model, false otherwise. + bool RayIntersection( + const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const; void GetPenetratedBoxes(const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, AZStd::vector& outBoxes); enum ESplitAxis @@ -53,8 +61,14 @@ namespace AZ private: void BuildRecursively(ModelKdTreeNode* pNode, const AZ::Aabb& boundbox, AZStd::vector& indices); - bool RayIntersectionRecursively(ModelKdTreeNode* pNode, const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distance, AZ::Vector3& normal) const; - void GetPenetratedBoxesRecursively(ModelKdTreeNode* pNode, const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, AZStd::vector& outBoxes); + bool RayIntersectionRecursively( + ModelKdTreeNode* pNode, + const AZ::Vector3& raySrc, + const AZ::Vector3& rayDir, + float& distanceNormalized, + AZ::Vector3& normal) const; + void GetPenetratedBoxesRecursively( + ModelKdTreeNode* pNode, const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, AZStd::vector& outBoxes); void ConstructMeshList(const ModelAsset* model, const AZ::Transform& matParent); static const int s_MinimumVertexSizeInLeafNode = 3 * 10; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp index 15c8aaf528..86477bf785 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp @@ -140,8 +140,9 @@ namespace AZ bool Model::LocalRayIntersection(const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distance, AZ::Vector3& normal) const { AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); - float firstHit; - const int result = Intersect::IntersectRayAABB2(rayStart, dir.GetReciprocal(), m_aabb, firstHit, distance); + float start; + float end; + const int result = Intersect::IntersectRayAABB2(rayStart, dir.GetReciprocal(), m_aabb, start, end); if (Intersect::ISECT_RAY_AABB_NONE != result) { if (ModelAsset* modelAssetPtr = m_modelAsset.Get()) @@ -164,7 +165,9 @@ namespace AZ return false; } - bool Model::RayIntersection(const AZ::Transform& modelTransform, const AZ::Vector3& nonUniformScale, const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distanceFactor, AZ::Vector3& normal) const + bool Model::RayIntersection( + const AZ::Transform& modelTransform, const AZ::Vector3& nonUniformScale, const AZ::Vector3& rayStart, const AZ::Vector3& dir, + float& distanceFactor, AZ::Vector3& normal) const { AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); const AZ::Vector3 clampedScale = nonUniformScale.GetMax(AZ::Vector3(AZ::MinTransformScale)); diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelKdTree.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelKdTree.cpp index 6a1897815f..bee489c2fd 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelKdTree.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelKdTree.cpp @@ -11,6 +11,7 @@ */ #include +#include #include #include @@ -191,10 +192,10 @@ namespace AZ if (ModelLodAsset* lodAssetPtr = model->GetLodAssets()[0].Get()) { - AZ_Warning("ModelKdTree", lodAssetPtr->GetMeshes().size() <= std::numeric_limits::max() + 1, + AZ_Warning("ModelKdTree", lodAssetPtr->GetMeshes().size() <= AZStd::numeric_limits::max() + 1, "KdTree generation doesn't support models with greater than 256 meshes. RayIntersection results will be incorrect " "unless the meshes are merged or broken up into multiple models"); - const size_t size = AZStd::min(lodAssetPtr->GetMeshes().size(), std::numeric_limits::max() + 1); + const size_t size = AZStd::min(lodAssetPtr->GetMeshes().size(), AZStd::numeric_limits::max() + 1); m_meshes.reserve(size); AZStd::transform( lodAssetPtr->GetMeshes().begin(), AZStd::next(lodAssetPtr->GetMeshes().begin(), size), @@ -204,20 +205,42 @@ namespace AZ } } - bool ModelKdTree::RayIntersection(const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distance, AZ::Vector3& normal) const + bool ModelKdTree::RayIntersection( + const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const { - return RayIntersectionRecursively(m_pRootNode.get(), raySrc, rayDir, distance, normal); + float closestDistanceNormalized = AZStd::numeric_limits::max(); + if (RayIntersectionRecursively(m_pRootNode.get(), raySrc, rayDir, closestDistanceNormalized, normal)) + { + distanceNormalized = closestDistanceNormalized; + return true; + } + + return false; } - bool ModelKdTree::RayIntersectionRecursively(ModelKdTreeNode* pNode, const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distance, AZ::Vector3& normal) const + bool ModelKdTree::RayIntersectionRecursively( + ModelKdTreeNode* pNode, + const AZ::Vector3& raySrc, + const AZ::Vector3& rayDir, + float& distanceNormalized, + AZ::Vector3& normal) const { + using Intersect::IntersectRayAABB2; + using Intersect::IntersectSegmentTriangleCCW; + using Intersect::ISECT_RAY_AABB_NONE; + if (!pNode) { return false; } float start, end; - if (AZ::Intersect::IntersectRayAABB2(raySrc, rayDir.GetReciprocal(), pNode->GetBoundBox(), start, end) == Intersect::ISECT_RAY_AABB_NONE) + if (IntersectRayAABB2(raySrc, rayDir.GetReciprocal(), pNode->GetBoundBox(), start, end) == ISECT_RAY_AABB_NONE) + { + return false; + } + + if (start > distanceNormalized) { return false; } @@ -235,17 +258,13 @@ namespace AZ return false; } - AZ::Vector3 intersectionNormal; - float hitDistanceNormalized; - const float maxDist(FLT_MAX); - float nearestDist = maxDist; - + float nearestDistanceNormalized = distanceNormalized; for (AZ::u32 i = 0; i < nVBuffSize; ++i) { const auto& [first, second, third] = pNode->GetVertexIndex(i); const AZ::u32 nObjIndex = pNode->GetObjIndex(i); - AZStd::array_view positionBuffer = m_meshes[nObjIndex].m_vertexData; + const AZStd::array_view positionBuffer = m_meshes[nObjIndex].m_vertexData; if (positionBuffer.empty()) { @@ -258,25 +277,23 @@ namespace AZ AZ::Vector3{positionBuffer[third * 3 + 0], positionBuffer[third * 3 + 1], positionBuffer[third * 3 + 2]}, }; - const AZ::Vector3 rayEnd = raySrc + rayDir * distance; - - if (AZ::Intersect::IntersectSegmentTriangleCCW(raySrc, rayEnd, trianglePoints[0], trianglePoints[1], trianglePoints[2], - intersectionNormal, hitDistanceNormalized) != Intersect::ISECT_RAY_AABB_NONE) + float hitDistanceNormalized; + AZ::Vector3 intersectionNormal; + const AZ::Vector3 rayEnd = raySrc + rayDir; + if (IntersectSegmentTriangleCCW(raySrc, rayEnd, trianglePoints[0], trianglePoints[1], trianglePoints[2], + intersectionNormal, hitDistanceNormalized) != ISECT_RAY_AABB_NONE) { - float hitDistance = hitDistanceNormalized * distance; - - if (nearestDist > hitDistance) + if (nearestDistanceNormalized > hitDistanceNormalized) { normal = intersectionNormal; + nearestDistanceNormalized = hitDistanceNormalized; } - - nearestDist = AZStd::GetMin(nearestDist, hitDistance); } } - if (nearestDist < maxDist) + if (nearestDistanceNormalized < distanceNormalized) { - distance = AZStd::GetMin(distance, nearestDist); + distanceNormalized = nearestDistanceNormalized; return true; } @@ -284,8 +301,8 @@ namespace AZ } // running both sides to find the closest intersection - const bool bFoundChild0 = RayIntersectionRecursively(pNode->GetChild(0), raySrc, rayDir, distance, normal); - const bool bFoundChild1 = RayIntersectionRecursively(pNode->GetChild(1), raySrc, rayDir, distance, normal); + const bool bFoundChild0 = RayIntersectionRecursively(pNode->GetChild(0), raySrc, rayDir, distanceNormalized, normal); + const bool bFoundChild1 = RayIntersectionRecursively(pNode->GetChild(1), raySrc, rayDir, distanceNormalized, normal); return bFoundChild0 || bFoundChild1; } @@ -311,5 +328,5 @@ namespace AZ GetPenetratedBoxesRecursively(pNode->GetChild(0), raySrc, rayDir, outBoxes); GetPenetratedBoxesRecursively(pNode->GetChild(1), raySrc, rayDir, outBoxes); } - } -} + } // namespace RPI +} // namespace AZ diff --git a/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp b/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp index 1ec14c6169..3ce17bee8b 100644 --- a/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp @@ -1074,13 +1074,13 @@ namespace UnitTest } }; - class KdTreeIntersectsFixture + class KdTreeIntersectsParameterizedFixture : public ModelTests , public ::testing::WithParamInterface { }; - TEST_P(KdTreeIntersectsFixture, KdTreeIntersects) + TEST_P(KdTreeIntersectsParameterizedFixture, KdTreeIntersects) { TwoSeparatedPlanesMesh mesh; @@ -1090,7 +1090,10 @@ namespace UnitTest float distance = AZStd::numeric_limits::max(); AZ::Vector3 normal; - EXPECT_THAT(kdTree.RayIntersection(AZ::Vector3(GetParam().xpos, GetParam().ypos, GetParam().zpos), AZ::Vector3::CreateAxisZ(-1.0f), distance, normal), testing::Eq(GetParam().expectedShouldIntersect)); + EXPECT_THAT( + kdTree.RayIntersection( + AZ::Vector3(GetParam().xpos, GetParam().ypos, GetParam().zpos), AZ::Vector3::CreateAxisZ(-1.0f), distance, normal), + testing::Eq(GetParam().expectedShouldIntersect)); EXPECT_THAT(distance, testing::FloatEq(GetParam().expectedDistance)); } @@ -1119,5 +1122,63 @@ namespace UnitTest KdTreeIntersectParams{0.778f, 0.111f, 1.0f, 0.5f, true}, KdTreeIntersectParams{0.778f, 0.778f, 1.0f, 0.5f, true}, }; - INSTANTIATE_TEST_CASE_P(KdTreeIntersectsPlane, KdTreeIntersectsFixture, ::testing::ValuesIn(intersectTestData)); + + INSTANTIATE_TEST_CASE_P(KdTreeIntersectsPlane, KdTreeIntersectsParameterizedFixture, ::testing::ValuesIn(intersectTestData)); + + class KdTreeIntersectsFixture + : public ModelTests + { + public: + void SetUp() override + { + ModelTests::SetUp(); + + m_mesh = AZStd::make_unique(); + m_kdTree = AZStd::make_unique(); + ASSERT_TRUE(m_kdTree->Build(m_mesh->GetModel().Get())); + } + + void TearDown() override + { + m_kdTree.reset(); + m_mesh.reset(); + + ModelTests::TearDown(); + } + + AZStd::unique_ptr m_mesh; + AZStd::unique_ptr m_kdTree; + }; + + TEST_F(KdTreeIntersectsFixture, KdTreeIntersectionReturnsNormalizedDistance) + { + float t = AZStd::numeric_limits::max(); + AZ::Vector3 normal; + + constexpr float rayLength = 100.0f; + EXPECT_THAT( + m_kdTree->RayIntersection( + AZ::Vector3::CreateZero(), AZ::Vector3::CreateAxisZ(-rayLength), t, normal), testing::Eq(true)); + EXPECT_THAT(t, testing::FloatEq(0.005f)); + } + + TEST_F(KdTreeIntersectsFixture, KdTreeIntersectionHandlesInvalidStartingNormalizedDistance) + { + float t = -0.5f; // invalid starting distance + AZ::Vector3 normal; + + constexpr float rayLength = 10.0f; + EXPECT_THAT( + m_kdTree->RayIntersection(AZ::Vector3::CreateAxisZ(0.75f), AZ::Vector3::CreateAxisZ(-rayLength), t, normal), testing::Eq(true)); + EXPECT_THAT(t, testing::FloatEq(0.025f)); + } + + TEST_F(KdTreeIntersectsFixture, KdTreeIntersectionDoesNotScaleRayByStartingDistance) + { + float t = 10.0f; // starting distance (used to check it is not read from initially by RayIntersection) + AZ::Vector3 normal; + + EXPECT_THAT( + m_kdTree->RayIntersection(AZ::Vector3::CreateAxisZ(5.0f), -AZ::Vector3::CreateAxisZ(), t, normal), testing::Eq(false)); + } } // namespace UnitTest diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.cpp index f2ad9a13e6..e1c9026c74 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/EditorMeshComponent.cpp @@ -140,9 +140,16 @@ namespace AZ AZ::Vector3 nonUniformScale = AZ::Vector3::CreateOne(); AZ::NonUniformScaleRequestBus::EventResult(nonUniformScale, GetEntityId(), &AZ::NonUniformScaleRequests::GetScale); + float t; AZ::Vector3 ignoreNormal; + constexpr float rayLength = 1000.0f; + if (m_controller.GetModel()->RayIntersection(transform, nonUniformScale, src, dir * rayLength, t, ignoreNormal)) + { + distance = rayLength * t; + return true; + } - return m_controller.GetModel()->RayIntersection(transform, nonUniformScale, src, dir, distance, ignoreNormal); + return false; } bool EditorMeshComponent::SupportsEditorRayIntersect() diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp index c089dd01f9..e7eecd3c7f 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Mesh/MeshComponentController.cpp @@ -485,16 +485,12 @@ namespace AZ m_transformInterface->GetWorldTM(), m_cachedNonUniformScale, ray.m_startWorldPosition, ray.m_endWorldPosition - ray.m_startWorldPosition, t, normal)) { - // note: this is a temporary workaround to handle cases where model->RayIntersection - // returns negative distances, follow-up ATOM-15673 - const auto absT = AZStd::abs(t); - // fill in ray result structure after successful intersection const auto intersectionLine = (ray.m_endWorldPosition - ray.m_startWorldPosition); result.m_uv = AZ::Vector2::CreateZero(); - result.m_worldPosition = ray.m_startWorldPosition + intersectionLine * absT; + result.m_worldPosition = ray.m_startWorldPosition + intersectionLine * t; result.m_worldNormal = normal; - result.m_distance = intersectionLine.GetLength() * absT; + result.m_distance = intersectionLine.GetLength() * t; result.m_entityAndComponent = m_entityComponentIdPair; } } From d02ba51d03644b4b60ccac16b4f4ca2020661c36 Mon Sep 17 00:00:00 2001 From: jjjoness <82226755+jjjoness@users.noreply.github.com> Date: Wed, 2 Jun 2021 10:34:08 +0100 Subject: [PATCH 22/45] Changed svg and layout of the new logo. (#1059) --- Code/Sandbox/Editor/StartupLogoDialog.ui | 8 ++-- Code/Sandbox/Editor/o3de_logo.svg | 51 +++++++++++++++--------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/Code/Sandbox/Editor/StartupLogoDialog.ui b/Code/Sandbox/Editor/StartupLogoDialog.ui index 6e01808a84..0815fa8b18 100644 --- a/Code/Sandbox/Editor/StartupLogoDialog.ui +++ b/Code/Sandbox/Editor/StartupLogoDialog.ui @@ -42,14 +42,14 @@ - 161 - 49 + 175 + 66 - 161 - 50 + 175 + 66 diff --git a/Code/Sandbox/Editor/o3de_logo.svg b/Code/Sandbox/Editor/o3de_logo.svg index ba44566ce8..35a880c5c8 100644 --- a/Code/Sandbox/Editor/o3de_logo.svg +++ b/Code/Sandbox/Editor/o3de_logo.svg @@ -1,22 +1,35 @@ - - Group 12 - - - - - - - - - - - - - - - - + + Artboard + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + \ No newline at end of file From ede8daaece0f8348f73d2f3f5c4989f1a8a4c142 Mon Sep 17 00:00:00 2001 From: Benjamin Jillich Date: Wed, 2 Jun 2021 13:27:55 +0200 Subject: [PATCH 23/45] [LYN-2514] Extending pythin bindings to adapt CLI changes for the gem catalog * Added ExecuteWithLockErrorHandling() which returns an outcome with the actual error we get from python so that we can expose that to the UI. * Added cmake pybind. * Get gems now calling get_all_gems and alphabetically sorting the result. * Added get enabled gems function which first gets the cmake enabled gems file path from the project path and then the list of gem names that are enabled. * Some changes to the enable and disable gem functions. --- .../ProjectManager/Source/PythonBindings.cpp | 125 +++++++++++------- .../ProjectManager/Source/PythonBindings.h | 11 +- .../Source/PythonBindingsInterface.h | 24 ++-- 3 files changed, 101 insertions(+), 59 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index c0481d6c87..e6ebfbefca 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -283,6 +283,7 @@ namespace O3DE::ProjectManager AZ_Warning("ProjectManagerWindow", result != -1, "Append to sys path failed"); // import required modules + m_cmake = pybind11::module::import("o3de.cmake"); m_register = pybind11::module::import("o3de.register"); m_manifest = pybind11::module::import("o3de.manifest"); m_engineTemplate = pybind11::module::import("o3de.engine_template"); @@ -311,7 +312,7 @@ namespace O3DE::ProjectManager return !PyErr_Occurred(); } - bool PythonBindings::ExecuteWithLock(AZStd::function executionCallback) + AZ::Outcome PythonBindings::ExecuteWithLockErrorHandling(AZStd::function executionCallback) { AZStd::lock_guard lock(m_lock); pybind11::gil_scoped_release release; @@ -320,15 +321,20 @@ namespace O3DE::ProjectManager try { executionCallback(); - return true; + return AZ::Success(); } catch ([[maybe_unused]] const std::exception& e) { AZ_Warning("PythonBindings", false, "Python exception %s", e.what()); - return false; + return AZ::Failure(e.what()); } } + bool PythonBindings::ExecuteWithLock(AZStd::function executionCallback) + { + return ExecuteWithLockErrorHandling(executionCallback).IsSuccess(); + } + AZ::Outcome PythonBindings::GetEngineInfo() { EngineInfo engineInfo; @@ -419,7 +425,7 @@ namespace O3DE::ProjectManager return result; } - AZ::Outcome PythonBindings::GetGem(const QString& path) + AZ::Outcome PythonBindings::GetGemInfo(const QString& path) { GemInfo gemInfo = GemInfoFromPath(pybind11::str(path.toStdString())); if (gemInfo.IsValid()) @@ -432,32 +438,59 @@ namespace O3DE::ProjectManager } } - AZ::Outcome> PythonBindings::GetGems() + AZ::Outcome, AZStd::string> PythonBindings::GetAllGemInfos(const QString& projectPath) { QVector gems; - bool result = ExecuteWithLock([&] { - // external gems - for (auto path : m_manifest.attr("get_gems")()) + auto result = ExecuteWithLockErrorHandling([&] { - gems.push_back(GemInfoFromPath(path)); - } + pybind11::str pyProjectPath = projectPath.toStdString(); + for (auto path : m_manifest.attr("get_all_gems")(pyProjectPath)) + { + gems.push_back(GemInfoFromPath(path)); + } + }); + if (!result.IsSuccess()) + { + return AZ::Failure(result.GetError().c_str()); + } - // gems from the engine - for (auto path : m_manifest.attr("get_engine_gems")()) + std::sort(gems.begin(), gems.end()); + return AZ::Success(AZStd::move(gems)); + } + + AZ::Outcome, AZStd::string> PythonBindings::GetEnabledGemNames(const QString& projectPath) + { + // Retrieve the path to the cmake file that lists the enabled gems. + pybind11::str enabledGemsFilename; + auto result = ExecuteWithLockErrorHandling([&] { - gems.push_back(GemInfoFromPath(path)); - } - }); + const pybind11::str pyProjectPath = projectPath.toStdString(); + enabledGemsFilename = m_cmake.attr("get_enabled_gem_cmake_file")( + pybind11::none(), // project_name + pyProjectPath); // project_path + }); + if (!result.IsSuccess()) + { + return AZ::Failure(result.GetError().c_str()); + } - if (!result) + // Retrieve the actual list of names from the cmake file. + QVector gemNames; + result = ExecuteWithLockErrorHandling([&] + { + const auto pyGemNames = m_cmake.attr("get_enabled_gems")(enabledGemsFilename); + for (auto gemName : pyGemNames) + { + gemNames.push_back(Py_To_String(gemName)); + } + }); + if (!result.IsSuccess()) { - return AZ::Failure(); - } - else - { - return AZ::Success(AZStd::move(gems)); + return AZ::Failure(result.GetError().c_str()); } + + return AZ::Success(AZStd::move(gemNames)); } bool PythonBindings::AddProject(const QString& path) @@ -637,38 +670,36 @@ namespace O3DE::ProjectManager } } - bool PythonBindings::AddGemToProject(const QString& gemPath, const QString& projectPath) + AZ::Outcome PythonBindings::AddGemToProject(const QString& gemPath, const QString& projectPath) { - bool result = ExecuteWithLock([&] { - pybind11::str pyGemPath = gemPath.toStdString(); - pybind11::str pyProjectPath = projectPath.toStdString(); + return ExecuteWithLockErrorHandling([&] + { + pybind11::str pyGemPath = gemPath.toStdString(); + pybind11::str pyProjectPath = projectPath.toStdString(); - m_enableGemProject.attr("enable_gem_in_project")( - pybind11::none(), // gem_name - pyGemPath, - pybind11::none(), // project_name - pyProjectPath - ); - }); - - return result; + m_enableGemProject.attr("enable_gem_in_project")( + pybind11::none(), // gem name not needed as path is provided + pyGemPath, + pybind11::none(), // project name not needed as path is provided + pyProjectPath + ); + }); } - bool PythonBindings::RemoveGemFromProject(const QString& gemPath, const QString& projectPath) + AZ::Outcome PythonBindings::RemoveGemFromProject(const QString& gemPath, const QString& projectPath) { - bool result = ExecuteWithLock([&] { - pybind11::str pyGemPath = gemPath.toStdString(); - pybind11::str pyProjectPath = projectPath.toStdString(); + return ExecuteWithLockErrorHandling([&] + { + pybind11::str pyGemPath = gemPath.toStdString(); + pybind11::str pyProjectPath = projectPath.toStdString(); - m_disableGemProject.attr("disable_gem_in_project")( - pybind11::none(), // gem_name - pyGemPath, - pybind11::none(), // project_name - pyProjectPath - ); - }); - - return result; + m_disableGemProject.attr("disable_gem_in_project")( + pybind11::none(), // gem name not needed as path is provided + pyGemPath, + pybind11::none(), // project name not needed as path is provided + pyProjectPath + ); + }); } bool PythonBindings::UpdateProject([[maybe_unused]] const ProjectInfo& projectInfo) diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h index 18122f484b..44958b0b0f 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -39,8 +39,9 @@ namespace O3DE::ProjectManager bool SetEngineInfo(const EngineInfo& engineInfo) override; // Gem - AZ::Outcome GetGem(const QString& path) override; - AZ::Outcome> GetGems() override; + AZ::Outcome GetGemInfo(const QString& path) override; + AZ::Outcome, AZStd::string> GetAllGemInfos(const QString& projectPath) override; + AZ::Outcome, AZStd::string> GetEnabledGemNames(const QString& projectPath) override; // Project AZ::Outcome CreateProject(const QString& projectTemplatePath, const ProjectInfo& projectInfo) override; @@ -49,8 +50,8 @@ namespace O3DE::ProjectManager bool AddProject(const QString& path) override; bool RemoveProject(const QString& path) override; bool UpdateProject(const ProjectInfo& projectInfo) override; - bool AddGemToProject(const QString& gemPath, const QString& projectPath) override; - bool RemoveGemFromProject(const QString& gemPath, const QString& projectPath) override; + AZ::Outcome AddGemToProject(const QString& gemPath, const QString& projectPath) override; + AZ::Outcome RemoveGemFromProject(const QString& gemPath, const QString& projectPath) override; // ProjectTemplate AZ::Outcome> GetProjectTemplates() override; @@ -58,6 +59,7 @@ namespace O3DE::ProjectManager private: AZ_DISABLE_COPY_MOVE(PythonBindings); + AZ::Outcome ExecuteWithLockErrorHandling(AZStd::function executionCallback); bool ExecuteWithLock(AZStd::function executionCallback); GemInfo GemInfoFromPath(pybind11::handle path); ProjectInfo ProjectInfoFromPath(pybind11::handle path); @@ -68,6 +70,7 @@ namespace O3DE::ProjectManager AZ::IO::FixedMaxPath m_enginePath; pybind11::handle m_engineTemplate; AZStd::recursive_mutex m_lock; + pybind11::handle m_cmake; pybind11::handle m_register; pybind11::handle m_manifest; pybind11::handle m_enableGemProject; diff --git a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h index a58eea0fe6..2e8347f488 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h +++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h @@ -57,13 +57,21 @@ namespace O3DE::ProjectManager * @param path the absolute path to the Gem * @return an outcome with GemInfo on success */ - virtual AZ::Outcome GetGem(const QString& path) = 0; + virtual AZ::Outcome GetGemInfo(const QString& path) = 0; /** - * Get info about all known Gems - * @return an outcome with GemInfos on success + * Get all available gem infos. This concatenates gems registered by the engine and the project. + * @param path The absolute path to the project. + * @return A list of gem infos. */ - virtual AZ::Outcome> GetGems() = 0; + virtual AZ::Outcome, AZStd::string> GetAllGemInfos(const QString& projectPath) = 0; + + /** + * Get a list of all enabled gem names for a given project. + * @param[in] projectPath Absolute file path to the project. + * @return A list of gem names of all the enabled gems for a given project or a error message on failure. + */ + virtual AZ::Outcome, AZStd::string> GetEnabledGemNames(const QString& projectPath) = 0; // Projects @@ -114,17 +122,17 @@ namespace O3DE::ProjectManager * Add a gem to a project * @param gemPath the absolute path to the gem * @param projectPath the absolute path to the project - * @return true on success, false on failure + * @return An outcome with the success flag as well as an error message in case of a failure. */ - virtual bool AddGemToProject(const QString& gemPath, const QString& projectPath) = 0; + virtual AZ::Outcome AddGemToProject(const QString& gemPath, const QString& projectPath) = 0; /** * Remove gem to a project * @param gemPath the absolute path to the gem * @param projectPath the absolute path to the project - * @return true on success, false on failure + * @return An outcome with the success flag as well as an error message in case of a failure. */ - virtual bool RemoveGemFromProject(const QString& gemPath, const QString& projectPath) = 0; + virtual AZ::Outcome RemoveGemFromProject(const QString& gemPath, const QString& projectPath) = 0; // Project Templates From bcdb541b7c9ec6488d6e26b69896bff67c1d1327 Mon Sep 17 00:00:00 2001 From: Benjamin Jillich Date: Wed, 2 Jun 2021 13:29:10 +0200 Subject: [PATCH 24/45] [LYN-2514] Sorting gems in the gem catalog after retrieving all gem infos Added Date: Wed, 2 Jun 2021 14:46:08 +0100 Subject: [PATCH 25/45] Changed logo and repositioned. (#1061) --- Code/Sandbox/Editor/AboutDialog.ui | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Code/Sandbox/Editor/AboutDialog.ui b/Code/Sandbox/Editor/AboutDialog.ui index 67767c0de1..0b86f15542 100644 --- a/Code/Sandbox/Editor/AboutDialog.ui +++ b/Code/Sandbox/Editor/AboutDialog.ui @@ -75,14 +75,14 @@ - 161 - 49 + 175 + 66 - 161 - 49 + 175 + 66 From 01f69acc5f086824223ed3e4785f5423fa13eb81 Mon Sep 17 00:00:00 2001 From: Nicholas Lawson <70027408+lawsonamzn@users.noreply.github.com> Date: Wed, 2 Jun 2021 07:38:39 -0700 Subject: [PATCH 26/45] Cleans up remaining 3p package todos (#1080) Removes Clang Adds a required LICENSE.TXT to the folder of glad in atom. --- .../glad/2.0.0-beta/include/glad/license.txt | 209 ++++++++++++++++++ cmake/3rdParty/FindClang.cmake | 15 -- cmake/3rdParty/cmake_files.cmake | 1 - 3 files changed, 209 insertions(+), 16 deletions(-) create mode 100644 Gems/Atom/RHI/Vulkan/External/glad/2.0.0-beta/include/glad/license.txt delete mode 100644 cmake/3rdParty/FindClang.cmake diff --git a/Gems/Atom/RHI/Vulkan/External/glad/2.0.0-beta/include/glad/license.txt b/Gems/Atom/RHI/Vulkan/External/glad/2.0.0-beta/include/glad/license.txt new file mode 100644 index 0000000000..158138973a --- /dev/null +++ b/Gems/Atom/RHI/Vulkan/External/glad/2.0.0-beta/include/glad/license.txt @@ -0,0 +1,209 @@ +vulkan.h was generated using a code generator from https://github.com/Dav1dde/glad + +/* +** Copyright (c) 2014-2020 The Khronos Group Inc. +** +** SPDX-License-Identifier: Apache-2.0 +*/ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/cmake/3rdParty/FindClang.cmake b/cmake/3rdParty/FindClang.cmake deleted file mode 100644 index 8062e6ebb2..0000000000 --- a/cmake/3rdParty/FindClang.cmake +++ /dev/null @@ -1,15 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# - -ly_add_external_target( - NAME Clang - VERSION 6.0.1-az -) diff --git a/cmake/3rdParty/cmake_files.cmake b/cmake/3rdParty/cmake_files.cmake index ebbad6e156..f7a315686f 100644 --- a/cmake/3rdParty/cmake_files.cmake +++ b/cmake/3rdParty/cmake_files.cmake @@ -11,7 +11,6 @@ set(FILES BuiltInPackages.cmake - FindClang.cmake FindOpenGLInterface.cmake FindRadTelemetry.cmake FindVkValidation.cmake From 274e1972f3574fc70fc41a58e0de3c5db5a9d8d2 Mon Sep 17 00:00:00 2001 From: Hasareej <82398396+Hasareej@users.noreply.github.com> Date: Wed, 2 Jun 2021 15:54:30 +0100 Subject: [PATCH 27/45] Adding a shortcut to hide clusters. (#1071) Adding the shortcut "U" to hide the TransformModeSelection & SpaceSelection clusters from the viewport. --- .../EditorTransformComponentSelection.cpp | 12 +++++++++++- .../EditorTransformComponentSelection.h | 1 + Code/Sandbox/Editor/Resource.h | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp index 3507f532b5..91ac495e0e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp @@ -2471,7 +2471,17 @@ namespace AzToolsFramework break; } }); - + + AddAction( + m_actions, { QKeySequence(Qt::Key_U) }, + /*ID_VIEWPORTUI_VISIBLE=*/50040, "Toggle ViewportUI", "Hide/Unhide Viewport UI", + [this]() + { + SetViewportUiClusterVisible(m_transformModeClusterId, !m_viewportUiVisible); + SetViewportUiClusterVisible(m_spaceCluster.m_spaceClusterId, !m_viewportUiVisible); + m_viewportUiVisible = !m_viewportUiVisible; + }); + EditorMenuRequestBus::Broadcast(&EditorMenuRequests::RestoreEditMenuToDefault); } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h index 2bc4d7cbf6..2ec7fa2489 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h @@ -306,6 +306,7 @@ namespace AzToolsFramework AzFramework::ClickDetector m_clickDetector; //!< Detect different types of mouse click. AzFramework::CursorState m_cursorState; //!< Track the mouse position and delta movement each frame. SpaceCluster m_spaceCluster; //!< Related viewport ui state for controlling the current reference space. + bool m_viewportUiVisible = true; //!< Used to hide/show the viewport ui elements. }; //! The ETCS (EntityTransformComponentSelection) namespace contains functions and data used exclusively by diff --git a/Code/Sandbox/Editor/Resource.h b/Code/Sandbox/Editor/Resource.h index 9c50045367..98a6e56d14 100644 --- a/Code/Sandbox/Editor/Resource.h +++ b/Code/Sandbox/Editor/Resource.h @@ -369,3 +369,4 @@ #define ID_TOOLBAR_WIDGET_SPACER_RIGHT 50013 #define ID_TOOLBAR_WIDGET_PLAYCONSOLE_LABEL 50014 #define ID_TOOLBAR_WIDGET_LAST 50020 +#define ID_VIEWPORTUI_VISIBLE 50040 From 39d1f2702192090408d59746b78d59870495f38f Mon Sep 17 00:00:00 2001 From: scottr Date: Wed, 2 Jun 2021 08:39:57 -0700 Subject: [PATCH 28/45] [ftue_auto_register] post merge and linux build fixes --- Code/Tools/ProjectManager/Source/PythonBindings.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index ab13e67591..ca8e571de5 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -324,10 +324,10 @@ namespace O3DE::ProjectManager // check current engine path against all other registered engines // to see if we are already registered - auto allEngines = m_registration.attr("get_engines")(); + auto allEngines = m_manifest.attr("get_engines")(); if (pybind11::isinstance(allEngines)) { - for (const auto& engine : allEngines) + for (auto engine : allEngines) { AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"])); if (enginePath.Compare(m_enginePath) == 0) @@ -340,7 +340,7 @@ namespace O3DE::ProjectManager if (registerThis) { - auto result = m_registration.attr("register")(m_enginePath.c_str()); + auto result = m_register.attr("register")(m_enginePath.c_str()); registrationResult = (result.cast() == 0); } }); From 5554bdf329d69ba7f7180fa6c2eb0cacd55697f3 Mon Sep 17 00:00:00 2001 From: scottr Date: Wed, 2 Jun 2021 08:46:57 -0700 Subject: [PATCH 29/45] [ftue_auto_register] use early return in registered engines loop instead of break --- Code/Tools/ProjectManager/Source/PythonBindings.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index ca8e571de5..2859a8fc9d 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -320,8 +320,6 @@ namespace O3DE::ProjectManager bool pythonResult = ExecuteWithLock( [&] { - bool registerThis = true; - // check current engine path against all other registered engines // to see if we are already registered auto allEngines = m_manifest.attr("get_engines")(); @@ -332,17 +330,13 @@ namespace O3DE::ProjectManager AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"])); if (enginePath.Compare(m_enginePath) == 0) { - registerThis = false; - break; + return; } } } - if (registerThis) - { - auto result = m_register.attr("register")(m_enginePath.c_str()); - registrationResult = (result.cast() == 0); - } + auto result = m_register.attr("register")(m_enginePath.c_str()); + registrationResult = (result.cast() == 0); }); bool finalResult = (registrationResult && pythonResult); From 53615230c1c7ebd905c37629898f35dc8b04160d Mon Sep 17 00:00:00 2001 From: Benjamin Jillich Date: Wed, 2 Jun 2021 17:49:40 +0200 Subject: [PATCH 30/45] [LYN-2514] Adding get engine gem infos to the python bindings --- .../ProjectManager/Source/PythonBindings.cpp | 20 +++++++++++++++++++ .../ProjectManager/Source/PythonBindings.h | 1 + .../Source/PythonBindingsInterface.h | 6 ++++++ 3 files changed, 27 insertions(+) diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index e6ebfbefca..72db92ff20 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -438,6 +438,26 @@ namespace O3DE::ProjectManager } } + AZ::Outcome, AZStd::string> PythonBindings::GetEngineGemInfos() + { + QVector gems; + + auto result = ExecuteWithLockErrorHandling([&] + { + for (auto path : m_manifest.attr("get_engine_gems")()) + { + gems.push_back(GemInfoFromPath(path)); + } + }); + if (!result.IsSuccess()) + { + return AZ::Failure(result.GetError().c_str()); + } + + std::sort(gems.begin(), gems.end()); + return AZ::Success(AZStd::move(gems)); + } + AZ::Outcome, AZStd::string> PythonBindings::GetAllGemInfos(const QString& projectPath) { QVector gems; diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h index 44958b0b0f..d508f15b95 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -40,6 +40,7 @@ namespace O3DE::ProjectManager // Gem AZ::Outcome GetGemInfo(const QString& path) override; + AZ::Outcome, AZStd::string> GetEngineGemInfos() override; AZ::Outcome, AZStd::string> GetAllGemInfos(const QString& projectPath) override; AZ::Outcome, AZStd::string> GetEnabledGemNames(const QString& projectPath) override; diff --git a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h index 2e8347f488..09d9187dbd 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h +++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h @@ -66,6 +66,12 @@ namespace O3DE::ProjectManager */ virtual AZ::Outcome, AZStd::string> GetAllGemInfos(const QString& projectPath) = 0; + /** + * Get engine gem infos. + * @return A list of all registered gem infos. + */ + virtual AZ::Outcome, AZStd::string> GetEngineGemInfos() = 0; + /** * Get a list of all enabled gem names for a given project. * @param[in] projectPath Absolute file path to the project. From f10627366726495bb1d13f2474bb856a96a0e15c Mon Sep 17 00:00:00 2001 From: Benjamin Jillich Date: Wed, 2 Jun 2021 17:52:17 +0200 Subject: [PATCH 31/45] [LYN-2514] Adding functionality to reinit the gem catalog for a given project and enable/disable the changed gems * Removed test data from gem catalog screen as we can now extract the real data. * Reiniting the catalog to a project updates the filters, and clears and fills the gem model. * Added functionality to enable/disable gems based on the user adjustments on the gem catalog. --- .../Source/GemCatalog/GemCatalogScreen.cpp | 213 +++++++++--------- .../Source/GemCatalog/GemCatalogScreen.h | 10 +- 2 files changed, 114 insertions(+), 109 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index 2d243e7f8b..670cbbc5a3 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -15,13 +15,12 @@ #include #include #include -#include #include #include #include #include - -//#define USE_TESTGEMDATA +#include +#include namespace O3DE::ProjectManager { @@ -29,47 +28,32 @@ namespace O3DE::ProjectManager : ScreenWidget(parent) { m_gemModel = new GemModel(this); - GemSortFilterProxyModel* proxyModel = new GemSortFilterProxyModel(m_gemModel, this); + m_proxModel = new GemSortFilterProxyModel(m_gemModel, this); QVBoxLayout* vLayout = new QVBoxLayout(); vLayout->setMargin(0); vLayout->setSpacing(0); setLayout(vLayout); - GemCatalogHeaderWidget* headerWidget = new GemCatalogHeaderWidget(proxyModel); + GemCatalogHeaderWidget* headerWidget = new GemCatalogHeaderWidget(m_proxModel); vLayout->addWidget(headerWidget); QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->setMargin(0); vLayout->addLayout(hLayout); - m_gemListView = new GemListView(proxyModel, proxyModel->GetSelectionModel(), this); + m_gemListView = new GemListView(m_proxModel, m_proxModel->GetSelectionModel(), this); m_gemInspector = new GemInspector(m_gemModel, this); - m_gemInspector->setFixedWidth(320); + m_gemInspector->setFixedWidth(240); - // Start: Temporary gem test data -#ifdef USE_TESTGEMDATA - QVector testGemData = GenerateTestData(); - for (const GemInfo& gemInfo : testGemData) - { - m_gemModel->AddGem(gemInfo); - } -#else - // End: Temporary gem test data - auto result = PythonBindingsInterface::Get()->GetGems(); - if (result.IsSuccess()) - { - for (auto gemInfo : result.GetValue()) - { - m_gemModel->AddGem(gemInfo); - } - } -#endif + QWidget* filterWidget = new QWidget(this); + filterWidget->setFixedWidth(240); + m_filterWidgetLayout = new QVBoxLayout(); + m_filterWidgetLayout->setMargin(0); + m_filterWidgetLayout->setSpacing(0); + filterWidget->setLayout(m_filterWidgetLayout); - GemFilterWidget* filterWidget = new GemFilterWidget(proxyModel); - filterWidget->setFixedWidth(250); - - GemListHeaderWidget* listHeaderWidget = new GemListHeaderWidget(proxyModel); + GemListHeaderWidget* listHeaderWidget = new GemListHeaderWidget(m_proxModel); QVBoxLayout* middleVLayout = new QVBoxLayout(); middleVLayout->setMargin(0); @@ -80,98 +64,111 @@ namespace O3DE::ProjectManager hLayout->addWidget(filterWidget); hLayout->addLayout(middleVLayout); hLayout->addWidget(m_gemInspector); - - proxyModel->InvalidateFilter(); } - QVector GemCatalogScreen::GenerateTestData() + void GemCatalogScreen::ReinitForProject(const QString& projectPath, bool isNewProject) { - QVector result; + m_gemModel->clear(); + FillModel(projectPath, isNewProject); - GemInfo gem("EMotion FX", - "O3DE Foundation", - "EMFX is a real-time character animation system. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", - (GemInfo::Android | GemInfo::iOS | GemInfo::macOS | GemInfo::Windows | GemInfo::Linux), - true); - gem.m_directoryLink = "C:/"; - gem.m_documentationLink = "http://www.amazon.com"; - gem.m_dependingGemUuids = QStringList({"EMotionFX", "Atom"}); - gem.m_conflictingGemUuids = QStringList({"Vegetation", "Camera", "ScriptCanvas", "CloudCanvas", "Networking"}); - gem.m_types = (GemInfo::Code | GemInfo::Asset); - gem.m_version = "v1.01"; - gem.m_lastUpdatedDate = "24th April 2021"; - gem.m_binarySizeInKB = 40; - gem.m_features = QStringList({"Animation", "Assets", "Physics"}); - gem.m_gemOrigin = GemInfo::O3DEFoundation; - result.push_back(gem); + if (m_filterWidget) + { + m_filterWidget->hide(); + m_filterWidget->deleteLater(); + } - gem.m_name = "Atom"; - gem.m_creator = "O3DE Seattle"; - gem.m_summary = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; - gem.m_platforms = (GemInfo::Android | GemInfo::Windows | GemInfo::Linux | GemInfo::macOS); - gem.m_isAdded = true; - gem.m_directoryLink = "C:/"; - gem.m_documentationLink = "https://aws.amazon.com/gametech/"; - gem.m_dependingGemUuids = QStringList({"EMotionFX", "Core", "AudioSystem", "Camera", "Particles"}); - gem.m_conflictingGemUuids = QStringList({"CloudCanvas", "NovaNet"}); - gem.m_version = "v2.31"; - gem.m_lastUpdatedDate = "24th November 2020"; - gem.m_features = QStringList({"Assets", "Rendering", "UI", "VR", "Debug", "Environment"}); - gem.m_binarySizeInKB = 2087; - result.push_back(gem); + m_filterWidget = new GemFilterWidget(m_proxModel); + m_filterWidgetLayout->addWidget(m_filterWidget); - gem.m_name = "Physics"; - gem.m_creator = "O3DE London"; - gem.m_summary = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; - gem.m_platforms = (GemInfo::Android | GemInfo::Linux | GemInfo::macOS); - gem.m_isAdded = true; - gem.m_directoryLink = "C:/"; - gem.m_documentationLink = "https://aws.amazon.com/gametech/"; - gem.m_dependingGemUuids = QStringList({"GraphCanvas", "ExpressionEvaluation", "UI Lib", "Multiplayer", "GameStateSamples"}); - gem.m_conflictingGemUuids = QStringList({"Cloud Canvas", "EMotion FX", "Streaming", "MessagePopup", "Cloth", "Graph Canvas", "Twitch Integration"}); - gem.m_version = "v1.5.102145"; - gem.m_lastUpdatedDate = "1st January 2021"; - gem.m_binarySizeInKB = 2000000; - gem.m_features = QStringList({"Physics", "Gameplay", "Debug", "Assets"}); - result.push_back(gem); + m_proxModel->InvalidateFilter(); - result.push_back(O3DE::ProjectManager::GemInfo("Certificate Manager", - "O3DE Irvine", - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", - GemInfo::Windows, - false)); + // Select the first entry after everything got correctly sized + QTimer::singleShot(200, [=]{ + QModelIndex firstModelIndex = m_gemListView->model()->index(0,0); + m_gemListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect); + }); + } - result.push_back(O3DE::ProjectManager::GemInfo("Cloud Gem Framework", - "O3DE Seattle", - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", - GemInfo::iOS | GemInfo::Linux, - false)); + void GemCatalogScreen::FillModel(const QString& projectPath, [[maybe_unused]] bool isNewProject) + { + AZ::Outcome, AZStd::string> allGemInfosResult; + if (isNewProject) + { + allGemInfosResult = PythonBindingsInterface::Get()->GetEngineGemInfos(); + } + else + { + allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(projectPath); + } - result.push_back(O3DE::ProjectManager::GemInfo("Cloud Gem Core", - "O3DE Foundation", - "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", - GemInfo::Android | GemInfo::Windows | GemInfo::Linux, - true)); + if (allGemInfosResult.IsSuccess()) + { + // Add all available gems to the model. + const QVector allGemInfos = allGemInfosResult.GetValue(); + for (const GemInfo& gemInfo : allGemInfos) + { + m_gemModel->AddGem(gemInfo); + } - result.push_back(O3DE::ProjectManager::GemInfo("Gestures", - "O3DE Foundation", - "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", - GemInfo::Android | GemInfo::Windows | GemInfo::Linux, - false)); + // Gather enabled gems for the given project. + auto enabledGemNamesResult = PythonBindingsInterface::Get()->GetEnabledGemNames(projectPath); + if (enabledGemNamesResult.IsSuccess()) + { + const QVector enabledGemNames = enabledGemNamesResult.GetValue(); + for (const AZStd::string& enabledGemName : enabledGemNames) + { + const QModelIndex modelIndex = m_gemModel->FindIndexByNameString(enabledGemName.c_str()); + if (modelIndex.isValid()) + { + GemModel::SetWasPreviouslyAdded(*m_gemModel, modelIndex, true); + GemModel::SetIsAdded(*m_gemModel, modelIndex, true); + } + else + { + AZ_Warning("ProjectManager::GemCatalog", false, + "Cannot find entry for gem with name '%s'. The CMake target name probably does not match the specified name in the gem.json.", + enabledGemName.c_str()); + } + } + } + else + { + QMessageBox::critical(nullptr, "Operation failed", QString("Cannot retrieve enabled gems for project %1.\n\nError:\n%2").arg(projectPath, enabledGemNamesResult.GetError().c_str())); + } + } + else + { + QMessageBox::critical(nullptr, "Operation failed", QString("Cannot retrieve gems for %1.\n\nError:\n%2").arg(projectPath, allGemInfosResult.GetError().c_str())); + } + } - result.push_back(O3DE::ProjectManager::GemInfo("Effects System", - "O3DE Foundation", - "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", - GemInfo::Android | GemInfo::Windows | GemInfo::Linux, - true)); + void GemCatalogScreen::EnableDisableGemsForProject(const QString& projectPath) + { + IPythonBindings* pythonBindings = PythonBindingsInterface::Get(); + QVector toBeAdded = m_gemModel->GatherGemsToBeAdded(); + QVector toBeRemoved = m_gemModel->GatherGemsToBeRemoved(); - result.push_back(O3DE::ProjectManager::GemInfo("Microphone", - "O3DE Foundation", - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus euismod ligula vitae dui dictum, a sodales dolor luctus. Sed id elit dapibus, finibus neque sed, efficitur mi. Nam facilisis ligula at eleifend pellentesque. Praesent non ex consectetur, blandit tellus in, venenatis lacus. Duis nec neque in urna ullamcorper euismod id eu leo. Nam efficitur dolor sed odio vehicula venenatis. Suspendisse nec est non velit commodo cursus in sit amet dui. Ut bibendum nisl et libero hendrerit dapibus. Vestibulum ultrices ullamcorper urna, placerat porttitor est lobortis in. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer a magna ac tellus sollicitudin porttitor. Phasellus lobortis viverra justo id bibendum. Etiam ac pharetra risus. Nulla vitae justo nibh. Nulla viverra leo et molestie interdum. Duis sit amet bibendum nulla, sit amet vehicula augue.", - GemInfo::Android | GemInfo::Windows | GemInfo::Linux, - false)); + for (const QModelIndex& modelIndex : toBeAdded) + { + const QString gemPath = GemModel::GetPath(modelIndex); + const AZ::Outcome result = pythonBindings->AddGemToProject(gemPath, projectPath); + if (!result.IsSuccess()) + { + QMessageBox::critical(nullptr, "Operation failed", + QString("Cannot add gem %1 to project.\n\nError:\n%2").arg(GemModel::GetName(modelIndex), result.GetError().c_str())); + } + } - return result; + for (const QModelIndex& modelIndex : toBeRemoved) + { + const QString gemPath = GemModel::GetPath(modelIndex); + const AZ::Outcome result = pythonBindings->RemoveGemFromProject(gemPath, projectPath); + if (!result.IsSuccess()) + { + QMessageBox::critical(nullptr, "Operation failed", + QString("Cannot remove gem %1 from project.\n\nError:\n%2").arg(GemModel::GetName(modelIndex), result.GetError().c_str())); + } + } } ProjectManagerScreen GemCatalogScreen::GetScreenEnum() diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h index 44e0727c7e..0847d9b74e 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.h @@ -14,9 +14,11 @@ #if !defined(Q_MOC_RUN) #include +#include #include #include #include +#include #endif namespace O3DE::ProjectManager @@ -29,11 +31,17 @@ namespace O3DE::ProjectManager ~GemCatalogScreen() = default; ProjectManagerScreen GetScreenEnum() override; + void ReinitForProject(const QString& projectPath, bool isNewProject); + void EnableDisableGemsForProject(const QString& projectPath); + private: - QVector GenerateTestData(); + void FillModel(const QString& projectPath, bool isNewProject); GemListView* m_gemListView = nullptr; GemInspector* m_gemInspector = nullptr; GemModel* m_gemModel = nullptr; + GemSortFilterProxyModel* m_proxModel = nullptr; + QVBoxLayout* m_filterWidgetLayout = nullptr; + GemFilterWidget* m_filterWidget = nullptr; }; } // namespace O3DE::ProjectManager From b0ef89edf967b9d78ddc948382e45a5ad27d99b5 Mon Sep 17 00:00:00 2001 From: Benjamin Jillich Date: Wed, 2 Jun 2021 17:53:02 +0200 Subject: [PATCH 32/45] [LYN-2514] Update project control now reinits for the selected project and enabled/disables gems based on the user selection in the gem catalog --- .../Source/UpdateProjectCtrl.cpp | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp index b3180966ce..bfa62dcae8 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -26,6 +27,7 @@ namespace O3DE::ProjectManager : ScreenWidget(parent) { QVBoxLayout* vLayout = new QVBoxLayout(); + vLayout->setMargin(0); setLayout(vLayout); m_screensCtrl = new ScreensCtrl(); @@ -95,6 +97,17 @@ namespace O3DE::ProjectManager } m_projectInfo = projectScreen->GetProjectInfo(); + + // The next page is the gem catalog. Gather the available gems that will be shown in the gem catalog. + auto* gemCatalogScreen = reinterpret_cast(m_screensCtrl->FindScreen(ProjectManagerScreen::GemCatalog)); + if (gemCatalogScreen) + { + gemCatalogScreen->ReinitForProject(m_projectInfo.m_path, /*isNewProject=*/false); + } + else + { + QMessageBox::critical(this, tr("Operation failed"), tr("Cannot find gem catalog screen.")); + } } } @@ -114,6 +127,17 @@ namespace O3DE::ProjectManager { QMessageBox::critical(this, tr("Project update failed"), tr("Failed to update project.")); } + + // Enable or disable the gems that got adjusted in the gem catalog and apply them to the given project. + auto* gemCatalogScreen = reinterpret_cast(m_screensCtrl->FindScreen(ProjectManagerScreen::GemCatalog)); + if (gemCatalogScreen) + { + gemCatalogScreen->EnableDisableGemsForProject(m_projectInfo.m_path); + } + else + { + QMessageBox::critical(this, tr("Operation failed"), tr("Cannot find gem catalog screen.")); + } } } From 39bb0bf2fc4cce33aa4404a5ac183604611a26db Mon Sep 17 00:00:00 2001 From: Benjamin Jillich Date: Wed, 2 Jun 2021 17:53:49 +0200 Subject: [PATCH 33/45] [LYN-2514] Create a new project control now reinits the gem catalog and enables gems based on the user selection --- .../ProjectManager/Source/CreateProjectCtrl.cpp | 14 +++++++++++--- .../ProjectManager/Source/CreateProjectCtrl.h | 3 +++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp index 60e351cdb4..559141bc82 100644 --- a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include @@ -42,9 +41,10 @@ namespace O3DE::ProjectManager m_stack = new QStackedWidget(this); m_stack->setObjectName("body"); - m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred,QSizePolicy::Expanding)); + m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding)); m_stack->addWidget(new NewProjectSettingsScreen()); - m_stack->addWidget(new GemCatalogScreen()); + m_gemCatalog = new GemCatalogScreen(); + m_stack->addWidget(m_gemCatalog); vLayout->addWidget(m_stack); QDialogButtonBox* backNextButtons = new QDialogButtonBox(); @@ -88,9 +88,11 @@ namespace O3DE::ProjectManager emit GotoPreviousScreenRequest(); } } + void CreateProjectCtrl::HandleNextButton() { ScreenWidget* currentScreen = reinterpret_cast(m_stack->currentWidget()); + const int currentScreenIndex = m_stack->currentIndex(); ProjectManagerScreen screenEnum = currentScreen->GetScreenEnum(); if (screenEnum == ProjectManagerScreen::NewProjectSettings) @@ -106,6 +108,9 @@ namespace O3DE::ProjectManager m_projectInfo = newProjectScreen->GetProjectInfo(); m_projectTemplatePath = newProjectScreen->GetProjectTemplatePath(); + + // The next page is the gem catalog. Gather the available gems that will be shown in the gem catalog. + m_gemCatalog->ReinitForProject(m_projectInfo.m_path, /*isNewProject=*/true); } } @@ -129,6 +134,9 @@ namespace O3DE::ProjectManager { QMessageBox::critical(this, tr("Project creation failed"), tr("Failed to create project.")); } + + // Enable/disable gems for the newly created project. + m_gemCatalog->EnableDisableGemsForProject(m_projectInfo.m_path); } } diff --git a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h index 355ba3941d..89d18a9ebc 100644 --- a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h +++ b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.h @@ -14,6 +14,7 @@ #if !defined(Q_MOC_RUN) #include #include +#include #endif QT_FORWARD_DECLARE_CLASS(QStackedWidget) @@ -48,6 +49,8 @@ namespace O3DE::ProjectManager QString m_projectTemplatePath; ProjectInfo m_projectInfo; + + GemCatalogScreen* m_gemCatalog = nullptr; }; } // namespace O3DE::ProjectManager From 1f65c3ba3a8560d20895d1cf51460a3dbb2096bd Mon Sep 17 00:00:00 2001 From: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> Date: Wed, 2 Jun 2021 09:13:04 -0700 Subject: [PATCH 34/45] LYN-4134 Automatically add `--project-path=` to debugging parameters in Editor/AP for engine-centric (#1081) --- Code/Sandbox/Editor/CMakeLists.txt | 16 +++++++--------- Code/Tools/AssetProcessor/CMakeLists.txt | 4 ++++ cmake/Projects.cmake | 3 +++ 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Code/Sandbox/Editor/CMakeLists.txt b/Code/Sandbox/Editor/CMakeLists.txt index 7be9947e99..01b58e3f77 100644 --- a/Code/Sandbox/Editor/CMakeLists.txt +++ b/Code/Sandbox/Editor/CMakeLists.txt @@ -177,6 +177,11 @@ ly_add_target( Legacy::EditorLib ProjectManager ) +set_property(SOURCE + CryEdit.cpp + APPEND PROPERTY + COMPILE_DEFINITIONS LY_CMAKE_TARGET="Editor" +) ly_add_translations( TARGETS Editor PREFIX Translations @@ -186,15 +191,8 @@ ly_add_translations( ) ly_add_dependencies(Editor AssetProcessor) -if(TARGET Editor) - set_property(SOURCE - CryEdit.cpp - APPEND PROPERTY - COMPILE_DEFINITIONS LY_CMAKE_TARGET="Editor" - ) -else() - message(FATAL_ERROR "Cannot set LY_CMAKE_TARGET define to Editor as the target doesn't exist anymore." - " Perhaps it has been renamed") +if(LY_FIRST_PROJECT_PATH) + set_property(TARGET Editor APPEND PROPERTY VS_DEBUGGER_COMMAND_ARGUMENTS "--project-path=\"${LY_FIRST_PROJECT_PATH}\"") endif() ################################################################################ diff --git a/Code/Tools/AssetProcessor/CMakeLists.txt b/Code/Tools/AssetProcessor/CMakeLists.txt index 5d4980eed4..6c12ab6024 100644 --- a/Code/Tools/AssetProcessor/CMakeLists.txt +++ b/Code/Tools/AssetProcessor/CMakeLists.txt @@ -125,6 +125,10 @@ ly_add_target( AZ::AssetProcessorBatch.Static ) +if(LY_FIRST_PROJECT_PATH) + set_property(TARGET AssetProcessor AssetProcessorBatch APPEND PROPERTY VS_DEBUGGER_COMMAND_ARGUMENTS "--project-path=\"${LY_FIRST_PROJECT_PATH}\"") +endif() + # Adds the AssetProcessorBatch target as a C preprocessor define so that it can be used as a Settings Registry # specialization in order to look up the generated .setreg which contains the dependencies # specified for the target. diff --git a/cmake/Projects.cmake b/cmake/Projects.cmake index 297dad4ddf..adaf7ee15f 100644 --- a/cmake/Projects.cmake +++ b/cmake/Projects.cmake @@ -167,6 +167,9 @@ endfunction() # Add the projects here so the above function is found foreach(project ${LY_PROJECTS}) file(REAL_PATH ${project} full_directory_path BASE_DIRECTORY ${CMAKE_SOURCE_DIR}) + if(NOT LY_FIRST_PROJECT) + ly_set(LY_FIRST_PROJECT_PATH ${full_directory_path}) + endif() string(SHA256 full_directory_hash ${full_directory_path}) # Truncate the full_directory_hash down to 8 characters to avoid hitting the Windows 260 character path limit From 25a114d32447282f1d19a71da621fd8626b3ab15 Mon Sep 17 00:00:00 2001 From: guthadam Date: Wed, 2 Jun 2021 11:35:40 -0500 Subject: [PATCH 35/45] updating includes --- .../Feature/Common/Code/Source/Material/MaterialAssignment.cpp | 3 +-- .../Code/Source/Material/MaterialAssignmentSerializer.cpp | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp index b4d8200dbc..029a81ec49 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp @@ -14,8 +14,7 @@ #include #include #include - -#include "MaterialAssignmentSerializer.h" +#include namespace AZ { diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.cpp index bb68a05a3e..a895c04b95 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignmentSerializer.cpp @@ -10,7 +10,7 @@ * */ -#include "MaterialAssignmentSerializer.h" +#include #include namespace AZ From fc0de9e0e309a6172457af7c913f44b369e8e7d2 Mon Sep 17 00:00:00 2001 From: AMZN-stankowi Date: Wed, 2 Jun 2021 09:36:34 -0700 Subject: [PATCH 36/45] Added [[maybe unused]] to fix release build compile issue (#1090) --- Code/Tools/SceneAPI/SDKWrapper/AssImpSceneWrapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/Tools/SceneAPI/SDKWrapper/AssImpSceneWrapper.cpp b/Code/Tools/SceneAPI/SDKWrapper/AssImpSceneWrapper.cpp index 791af4bf68..336cc1b172 100644 --- a/Code/Tools/SceneAPI/SDKWrapper/AssImpSceneWrapper.cpp +++ b/Code/Tools/SceneAPI/SDKWrapper/AssImpSceneWrapper.cpp @@ -42,7 +42,7 @@ namespace AZ } #if AZ_TRAIT_COMPILER_SUPPORT_CSIGNAL - void signal_handler(int signal) + void signal_handler([[maybe_unused]] int signal) { AZ_TracePrintf( SceneAPI::Utilities::ErrorWindow, From e7e85f91d6ca30dd6e3fb872b27a693e6e8f0816 Mon Sep 17 00:00:00 2001 From: Benjamin Jillich Date: Wed, 2 Jun 2021 19:08:10 +0200 Subject: [PATCH 37/45] Addressing PR feedback --- Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp | 1 - .../ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp | 6 +++--- Code/Tools/ProjectManager/Source/PythonBindings.cpp | 3 ++- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp index 559141bc82..85e34aeced 100644 --- a/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp @@ -92,7 +92,6 @@ namespace O3DE::ProjectManager void CreateProjectCtrl::HandleNextButton() { ScreenWidget* currentScreen = reinterpret_cast(m_stack->currentWidget()); - const int currentScreenIndex = m_stack->currentIndex(); ProjectManagerScreen screenEnum = currentScreen->GetScreenEnum(); if (screenEnum == ProjectManagerScreen::NewProjectSettings) diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index 670cbbc5a3..aa36c1b0ab 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -89,7 +89,7 @@ namespace O3DE::ProjectManager }); } - void GemCatalogScreen::FillModel(const QString& projectPath, [[maybe_unused]] bool isNewProject) + void GemCatalogScreen::FillModel(const QString& projectPath, bool isNewProject) { AZ::Outcome, AZStd::string> allGemInfosResult; if (isNewProject) @@ -133,12 +133,12 @@ namespace O3DE::ProjectManager } else { - QMessageBox::critical(nullptr, "Operation failed", QString("Cannot retrieve enabled gems for project %1.\n\nError:\n%2").arg(projectPath, enabledGemNamesResult.GetError().c_str())); + QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve enabled gems for project %1.\n\nError:\n%2").arg(projectPath, enabledGemNamesResult.GetError().c_str())); } } else { - QMessageBox::critical(nullptr, "Operation failed", QString("Cannot retrieve gems for %1.\n\nError:\n%2").arg(projectPath, allGemInfosResult.GetError().c_str())); + QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve gems for %1.\n\nError:\n%2").arg(projectPath, allGemInfosResult.GetError().c_str())); } } diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 72db92ff20..07edb1722f 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -321,13 +321,14 @@ namespace O3DE::ProjectManager try { executionCallback(); - return AZ::Success(); } catch ([[maybe_unused]] const std::exception& e) { AZ_Warning("PythonBindings", false, "Python exception %s", e.what()); return AZ::Failure(e.what()); } + + return AZ::Success(); } bool PythonBindings::ExecuteWithLock(AZStd::function executionCallback) From 8a281072996706b469e94795718d74843b4b6f2d Mon Sep 17 00:00:00 2001 From: AMZN-nggieber <52797929+AMZN-nggieber@users.noreply.github.com> Date: Wed, 2 Jun 2021 10:23:58 -0700 Subject: [PATCH 38/45] Project Manager Setup Update Project Settings Screen and Flow * Filled out and connected up UpdateProjectCtrl and UpdateProjectsSettingsScreen --- Code/Tools/ProjectManager/CMakeLists.txt | 1 - .../Resources/ProjectManager.qss | 12 ++ .../Source/NewProjectSettingsScreen.cpp | 154 ++++------------ .../Source/NewProjectSettingsScreen.h | 17 +- .../Source/ProjectButtonWidget.cpp | 10 - .../Source/ProjectButtonWidget.h | 2 - .../ProjectManager/Source/ProjectInfo.cpp | 13 ++ .../Tools/ProjectManager/Source/ProjectInfo.h | 2 + .../Source/ProjectSettingsScreen.cpp | 120 ++++++++++-- .../Source/ProjectSettingsScreen.h | 24 ++- .../Source/ProjectSettingsScreen.ui | 113 ------------ .../ProjectManager/Source/ProjectsScreen.cpp | 10 - .../ProjectManager/Source/ProjectsScreen.h | 1 - Code/Tools/ProjectManager/Source/ScreenDefs.h | 4 +- .../ProjectManager/Source/ScreenFactory.cpp | 6 +- .../ProjectManager/Source/ScreensCtrl.cpp | 1 + .../Source/UpdateProjectCtrl.cpp | 174 +++++++++++------- .../ProjectManager/Source/UpdateProjectCtrl.h | 39 ++-- .../Source/UpdateProjectSettingsScreen.cpp | 51 +++++ .../Source/UpdateProjectSettingsScreen.h | 34 ++++ .../project_manager_files.cmake | 3 +- 21 files changed, 416 insertions(+), 375 deletions(-) delete mode 100644 Code/Tools/ProjectManager/Source/ProjectSettingsScreen.ui create mode 100644 Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp create mode 100644 Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.h diff --git a/Code/Tools/ProjectManager/CMakeLists.txt b/Code/Tools/ProjectManager/CMakeLists.txt index a655600325..aeb7be9793 100644 --- a/Code/Tools/ProjectManager/CMakeLists.txt +++ b/Code/Tools/ProjectManager/CMakeLists.txt @@ -25,7 +25,6 @@ ly_add_target( OUTPUT_NAME o3de NAMESPACE AZ AUTOMOC - AUTOUIC AUTORCC FILES_CMAKE project_manager_files.cmake diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qss b/Code/Tools/ProjectManager/Resources/ProjectManager.qss index a85b911c15..224574f522 100644 --- a/Code/Tools/ProjectManager/Resources/ProjectManager.qss +++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qss @@ -232,6 +232,18 @@ QTabBar::tab:pressed margin-left:30px; } +#projectSettingsTab::tab-bar { + left: 60px; +} + +#projectSettingsTabBar::tab { + height:50px; +} + +#projectSettingsTopFrame { + background-color:#1E252F; +} + /************** Projects **************/ #firstTimeContent > #titleLabel { font-size:60px; diff --git a/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp index 53400b3193..c8dc8451ae 100644 --- a/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp @@ -34,83 +34,57 @@ namespace O3DE::ProjectManager constexpr const char* k_pathProperty = "Path"; NewProjectSettingsScreen::NewProjectSettingsScreen(QWidget* parent) - : ScreenWidget(parent) + : ProjectSettingsScreen(parent) { - QHBoxLayout* hLayout = new QHBoxLayout(this); - hLayout->setAlignment(Qt::AlignLeft); - hLayout->setContentsMargins(0,0,0,0); + const QString defaultName{ "NewProject" }; + const QString defaultPath = QDir::toNativeSeparators(GetDefaultProjectPath() + "/" + defaultName); - // if we don't provide a parent for this box layout the stylesheet doesn't take - // if we don't set this in a frame (just use a sub-layout) all the content will align incorrectly horizontally - QFrame* projectSettingsFrame = new QFrame(this); - projectSettingsFrame->setObjectName("projectSettings"); - QVBoxLayout* vLayout = new QVBoxLayout(this); + m_projectName->lineEdit()->setText(defaultName); + m_projectPath->lineEdit()->setText(defaultPath); - // you cannot remove content margins in qss - vLayout->setContentsMargins(0,0,0,0); - vLayout->setAlignment(Qt::AlignTop); + // if we don't use a QFrame we cannot "contain" the widgets inside and move them around + // as a group + QFrame* projectTemplateWidget = new QFrame(this); + projectTemplateWidget->setObjectName("projectTemplate"); + QVBoxLayout* containerLayout = new QVBoxLayout(); + containerLayout->setAlignment(Qt::AlignTop); { - const QString defaultName{ "NewProject" }; - const QString defaultPath = QDir::toNativeSeparators(GetDefaultProjectPath() + "/" + defaultName); + QLabel* projectTemplateLabel = new QLabel(tr("Select a Project Template")); + projectTemplateLabel->setObjectName("projectTemplateLabel"); + containerLayout->addWidget(projectTemplateLabel); - m_projectName = new FormLineEditWidget(tr("Project name"), defaultName, this); - connect(m_projectName->lineEdit(), &QLineEdit::textChanged, this, &NewProjectSettingsScreen::ValidateProjectPath); - vLayout->addWidget(m_projectName); + QLabel* projectTemplateDetailsLabel = new QLabel(tr("Project templates are pre-configured with relevant Gems that provide " + "additional functionality and content to the project.")); + projectTemplateDetailsLabel->setWordWrap(true); + projectTemplateDetailsLabel->setObjectName("projectTemplateDetailsLabel"); + containerLayout->addWidget(projectTemplateDetailsLabel); - m_projectPath = new FormBrowseEditWidget(tr("Project Location"), defaultPath, this); - m_projectPath->lineEdit()->setReadOnly(true); - connect(m_projectPath->lineEdit(), &QLineEdit::textChanged, this, &NewProjectSettingsScreen::ValidateProjectPath); - vLayout->addWidget(m_projectPath); + QHBoxLayout* templateLayout = new QHBoxLayout(this); + containerLayout->addItem(templateLayout); - // if we don't use a QFrame we cannot "contain" the widgets inside and move them around - // as a group - QFrame* projectTemplateWidget = new QFrame(this); - projectTemplateWidget->setObjectName("projectTemplate"); - QVBoxLayout* containerLayout = new QVBoxLayout(); - containerLayout->setAlignment(Qt::AlignTop); + m_projectTemplateButtonGroup = new QButtonGroup(this); + m_projectTemplateButtonGroup->setObjectName("templateButtonGroup"); + auto templatesResult = PythonBindingsInterface::Get()->GetProjectTemplates(); + if (templatesResult.IsSuccess() && !templatesResult.GetValue().isEmpty()) { - QLabel* projectTemplateLabel = new QLabel(tr("Select a Project Template")); - projectTemplateLabel->setObjectName("projectTemplateLabel"); - containerLayout->addWidget(projectTemplateLabel); - - QLabel* projectTemplateDetailsLabel = new QLabel(tr("Project templates are pre-configured with relevant Gems that provide " - "additional functionality and content to the project.")); - projectTemplateDetailsLabel->setWordWrap(true); - projectTemplateDetailsLabel->setObjectName("projectTemplateDetailsLabel"); - containerLayout->addWidget(projectTemplateDetailsLabel); - - QHBoxLayout* templateLayout = new QHBoxLayout(this); - containerLayout->addItem(templateLayout); - - m_projectTemplateButtonGroup = new QButtonGroup(this); - m_projectTemplateButtonGroup->setObjectName("templateButtonGroup"); - auto templatesResult = PythonBindingsInterface::Get()->GetProjectTemplates(); - if (templatesResult.IsSuccess() && !templatesResult.GetValue().isEmpty()) + for (const ProjectTemplateInfo& projectTemplate : templatesResult.GetValue()) { - for (auto projectTemplate : templatesResult.GetValue()) - { - QRadioButton* radioButton = new QRadioButton(projectTemplate.m_name, this); - radioButton->setProperty(k_pathProperty, projectTemplate.m_path); - m_projectTemplateButtonGroup->addButton(radioButton); + QRadioButton* radioButton = new QRadioButton(projectTemplate.m_name, this); + radioButton->setProperty(k_pathProperty, projectTemplate.m_path); + m_projectTemplateButtonGroup->addButton(radioButton); - containerLayout->addWidget(radioButton); - } - - m_projectTemplateButtonGroup->buttons().first()->setChecked(true); + containerLayout->addWidget(radioButton); } - } - projectTemplateWidget->setLayout(containerLayout); - vLayout->addWidget(projectTemplateWidget); - } - projectSettingsFrame->setLayout(vLayout); - hLayout->addWidget(projectSettingsFrame); + m_projectTemplateButtonGroup->buttons().first()->setChecked(true); + } + } + projectTemplateWidget->setLayout(containerLayout); + m_verticalLayout->addWidget(projectTemplateWidget); QWidget* projectTemplateDetails = new QWidget(this); projectTemplateDetails->setObjectName("projectTemplateDetails"); - hLayout->addWidget(projectTemplateDetails); - - this->setLayout(hLayout); + m_horizontalLayout->addWidget(projectTemplateDetails); } QString NewProjectSettingsScreen::GetDefaultProjectPath() @@ -133,69 +107,13 @@ namespace O3DE::ProjectManager return ProjectManagerScreen::NewProjectSettings; } - void NewProjectSettingsScreen::ValidateProjectPath() - { - Validate(); - } - void NewProjectSettingsScreen::NotifyCurrentScreen() { Validate(); } - ProjectInfo NewProjectSettingsScreen::GetProjectInfo() - { - ProjectInfo projectInfo; - projectInfo.m_projectName = m_projectName->lineEdit()->text(); - projectInfo.m_path = m_projectPath->lineEdit()->text(); - return projectInfo; - } - QString NewProjectSettingsScreen::GetProjectTemplatePath() { return m_projectTemplateButtonGroup->checkedButton()->property(k_pathProperty).toString(); } - - bool NewProjectSettingsScreen::Validate() - { - bool projectPathIsValid = true; - if (m_projectPath->lineEdit()->text().isEmpty()) - { - projectPathIsValid = false; - m_projectPath->setErrorLabelText(tr("Please provide a valid location.")); - } - else - { - QDir path(m_projectPath->lineEdit()->text()); - if (path.exists() && !path.isEmpty()) - { - projectPathIsValid = false; - m_projectPath->setErrorLabelText(tr("This folder exists and isn't empty. Please choose a different location.")); - } - } - - bool projectNameIsValid = true; - if (m_projectName->lineEdit()->text().isEmpty()) - { - projectNameIsValid = false; - m_projectName->setErrorLabelText(tr("Please provide a project name.")); - } - else - { - // this validation should roughly match the utils.validate_identifier which the cli - // uses to validate project names - QRegExp validProjectNameRegex("[A-Za-z][A-Za-z0-9_-]{0,63}"); - const bool result = validProjectNameRegex.exactMatch(m_projectName->lineEdit()->text()); - if (!result) - { - projectNameIsValid = false; - m_projectName->setErrorLabelText(tr("Project names must start with a letter and consist of up to 64 letter, number, '_' or '-' characters")); - } - - } - - m_projectName->setErrorLabelVisible(!projectNameIsValid); - m_projectPath->setErrorLabelVisible(!projectPathIsValid); - return projectNameIsValid && projectPathIsValid; - } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.h b/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.h index 0560f8728d..6a4b6ec57d 100644 --- a/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.h +++ b/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.h @@ -12,41 +12,28 @@ #pragma once #if !defined(Q_MOC_RUN) -#include -#include +#include #endif QT_FORWARD_DECLARE_CLASS(QButtonGroup) namespace O3DE::ProjectManager { - QT_FORWARD_DECLARE_CLASS(FormLineEditWidget) - QT_FORWARD_DECLARE_CLASS(FormBrowseEditWidget) - class NewProjectSettingsScreen - : public ScreenWidget + : public ProjectSettingsScreen { public: explicit NewProjectSettingsScreen(QWidget* parent = nullptr); ~NewProjectSettingsScreen() = default; ProjectManagerScreen GetScreenEnum() override; - ProjectInfo GetProjectInfo(); QString GetProjectTemplatePath(); - bool Validate(); - void NotifyCurrentScreen() override; - protected slots: - void HandleBrowseButton(); - void ValidateProjectPath(); - private: QString GetDefaultProjectPath(); - FormLineEditWidget* m_projectName; - FormBrowseEditWidget* m_projectPath; QButtonGroup* m_projectTemplateButtonGroup; }; diff --git a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp index 72ffa686c1..b1dbd984fb 100644 --- a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp @@ -22,8 +22,6 @@ #include #include -//#define SHOW_ALL_PROJECT_ACTIONS - namespace O3DE::ProjectManager { inline constexpr static int s_projectImageWidth = 210; @@ -96,10 +94,6 @@ namespace O3DE::ProjectManager m_removeProjectAction = newProjectMenu->addAction(tr("Remove from O3DE")); m_deleteProjectAction = newProjectMenu->addAction(tr("Delete this Project")); -#ifdef SHOW_ALL_PROJECT_ACTIONS - m_editProjectGemsAction = newProjectMenu->addAction(tr("Cutomize Gems...")); -#endif - QFrame* footer = new QFrame(this); QHBoxLayout* hLayout = new QHBoxLayout(); hLayout->setContentsMargins(0, 0, 0, 0); @@ -121,10 +115,6 @@ namespace O3DE::ProjectManager connect(m_copyProjectAction, &QAction::triggered, [this]() { emit CopyProject(m_projectInfo.m_path); }); connect(m_removeProjectAction, &QAction::triggered, [this]() { emit RemoveProject(m_projectInfo.m_path); }); connect(m_deleteProjectAction, &QAction::triggered, [this]() { emit DeleteProject(m_projectInfo.m_path); }); - -#ifdef SHOW_ALL_PROJECT_ACTIONS - connect(m_editProjectGemsAction, &QAction::triggered, [this]() { emit EditProjectGems(m_projectInfo.m_path); }); -#endif } void ProjectButton::SetButtonEnabled(bool enabled) diff --git a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h index e82b56b3fa..3ac69b7603 100644 --- a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h +++ b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h @@ -62,7 +62,6 @@ namespace O3DE::ProjectManager signals: void OpenProject(const QString& projectName); void EditProject(const QString& projectName); - void EditProjectGems(const QString& projectName); void CopyProject(const QString& projectName); void RemoveProject(const QString& projectName); void DeleteProject(const QString& projectName); @@ -73,7 +72,6 @@ namespace O3DE::ProjectManager ProjectInfo m_projectInfo; LabelButton* m_projectImageLabel; QAction* m_editProjectAction; - QAction* m_editProjectGemsAction; QAction* m_copyProjectAction; QAction* m_removeProjectAction; QAction* m_deleteProjectAction; diff --git a/Code/Tools/ProjectManager/Source/ProjectInfo.cpp b/Code/Tools/ProjectManager/Source/ProjectInfo.cpp index b0b740fad9..f0dc05cc62 100644 --- a/Code/Tools/ProjectManager/Source/ProjectInfo.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectInfo.cpp @@ -25,6 +25,19 @@ namespace O3DE::ProjectManager { } + bool ProjectInfo::operator==(const ProjectInfo& rhs) + { + return m_path == rhs.m_path + && m_projectName == rhs.m_projectName + && m_imagePath == rhs.m_imagePath + && m_backgroundImagePath == rhs.m_backgroundImagePath; + } + + bool ProjectInfo::operator!=(const ProjectInfo& rhs) + { + return !operator==(rhs); + } + bool ProjectInfo::IsValid() const { return !m_path.isEmpty() && !m_projectName.isEmpty(); diff --git a/Code/Tools/ProjectManager/Source/ProjectInfo.h b/Code/Tools/ProjectManager/Source/ProjectInfo.h index 92a7459d78..71fa12b344 100644 --- a/Code/Tools/ProjectManager/Source/ProjectInfo.h +++ b/Code/Tools/ProjectManager/Source/ProjectInfo.h @@ -25,6 +25,8 @@ namespace O3DE::ProjectManager ProjectInfo() = default; ProjectInfo(const QString& path, const QString& projectName, const QString& displayName, const QString& imagePath, const QString& backgroundImagePath, bool isNew); + bool operator==(const ProjectInfo& rhs); + bool operator!=(const ProjectInfo& rhs); bool IsValid() const; diff --git a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp index 76aa1d2897..26711753d4 100644 --- a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp @@ -11,45 +11,131 @@ */ #include +#include +#include +#include +#include -#include +#include +#include +#include +#include +#include +#include +#include namespace O3DE::ProjectManager { ProjectSettingsScreen::ProjectSettingsScreen(QWidget* parent) : ScreenWidget(parent) - , m_ui(new Ui::ProjectSettingsClass()) { - m_ui->setupUi(this); + m_horizontalLayout = new QHBoxLayout(this); + m_horizontalLayout->setAlignment(Qt::AlignLeft); + m_horizontalLayout->setContentsMargins(0, 0, 0, 0); - connect(m_ui->gemsButton, &QPushButton::pressed, this, &ProjectSettingsScreen::HandleGemsButton); + // if we don't provide a parent for this box layout the stylesheet doesn't take + // if we don't set this in a frame (just use a sub-layout) all the content will align incorrectly horizontally + QFrame* projectSettingsFrame = new QFrame(this); + projectSettingsFrame->setObjectName("projectSettings"); + m_verticalLayout = new QVBoxLayout(this); + + // you cannot remove content margins in qss + m_verticalLayout->setContentsMargins(0, 0, 0, 0); + m_verticalLayout->setAlignment(Qt::AlignTop); + + m_projectName = new FormLineEditWidget(tr("Project name"), "", this); + connect(m_projectName->lineEdit(), &QLineEdit::textChanged, this, &ProjectSettingsScreen::ValidateProjectName); + m_verticalLayout->addWidget(m_projectName); + + m_projectPath = new FormBrowseEditWidget(tr("Project Location"), "", this); + m_projectPath->lineEdit()->setReadOnly(true); + connect(m_projectPath->lineEdit(), &QLineEdit::textChanged, this, &ProjectSettingsScreen::Validate); + m_verticalLayout->addWidget(m_projectPath); + + projectSettingsFrame->setLayout(m_verticalLayout); + + m_horizontalLayout->addWidget(projectSettingsFrame); + + setLayout(m_horizontalLayout); } ProjectManagerScreen ProjectSettingsScreen::GetScreenEnum() { - return ProjectManagerScreen::ProjectSettings; + return ProjectManagerScreen::Invalid; + } + + QString ProjectSettingsScreen::GetDefaultProjectPath() + { + QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + AZ::Outcome engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo(); + if (engineInfoResult.IsSuccess()) + { + QDir path(QDir::toNativeSeparators(engineInfoResult.GetValue().m_defaultProjectsFolder)); + if (path.exists()) + { + defaultPath = path.absolutePath(); + } + } + return defaultPath; } ProjectInfo ProjectSettingsScreen::GetProjectInfo() { - // Impl pending next PR - return ProjectInfo(); + ProjectInfo projectInfo; + projectInfo.m_projectName = m_projectName->lineEdit()->text(); + projectInfo.m_path = m_projectPath->lineEdit()->text(); + return projectInfo; } - void ProjectSettingsScreen::SetProjectInfo() + bool ProjectSettingsScreen::ValidateProjectName() { - // Impl pending next PR + bool projectNameIsValid = true; + if (m_projectName->lineEdit()->text().isEmpty()) + { + projectNameIsValid = false; + m_projectName->setErrorLabelText(tr("Please provide a project name.")); + } + else + { + // this validation should roughly match the utils.validate_identifier which the cli + // uses to validate project names + QRegExp validProjectNameRegex("[A-Za-z][A-Za-z0-9_-]{0,63}"); + const bool result = validProjectNameRegex.exactMatch(m_projectName->lineEdit()->text()); + if (!result) + { + projectNameIsValid = false; + m_projectName->setErrorLabelText( + tr("Project names must start with a letter and consist of up to 64 letter, number, '_' or '-' characters")); + } + } + + m_projectName->setErrorLabelVisible(!projectNameIsValid); + return projectNameIsValid; + } + bool ProjectSettingsScreen::ValidateProjectPath() + { + bool projectPathIsValid = true; + if (m_projectPath->lineEdit()->text().isEmpty()) + { + projectPathIsValid = false; + m_projectPath->setErrorLabelText(tr("Please provide a valid location.")); + } + else + { + QDir path(m_projectPath->lineEdit()->text()); + if (path.exists() && !path.isEmpty()) + { + projectPathIsValid = false; + m_projectPath->setErrorLabelText(tr("This folder exists and isn't empty. Please choose a different location.")); + } + } + + m_projectPath->setErrorLabelVisible(!projectPathIsValid); + return projectPathIsValid; } bool ProjectSettingsScreen::Validate() { - // Impl pending next PR - return true; + return ValidateProjectName() && ValidateProjectPath(); } - - void ProjectSettingsScreen::HandleGemsButton() - { - emit ChangeScreenRequest(ProjectManagerScreen::GemCatalog); - } - } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.h b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.h index a4cafcd93a..0d75bbbc64 100644 --- a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.h +++ b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.h @@ -12,17 +12,18 @@ #pragma once #if !defined(Q_MOC_RUN) -#include #include +#include #endif -namespace Ui -{ - class ProjectSettingsClass; -} +QT_FORWARD_DECLARE_CLASS(QHBoxLayout) +QT_FORWARD_DECLARE_CLASS(QVBoxLayout) namespace O3DE::ProjectManager { + QT_FORWARD_DECLARE_CLASS(FormLineEditWidget) + QT_FORWARD_DECLARE_CLASS(FormBrowseEditWidget) + class ProjectSettingsScreen : public ScreenWidget { @@ -32,15 +33,20 @@ namespace O3DE::ProjectManager ProjectManagerScreen GetScreenEnum() override; ProjectInfo GetProjectInfo(); - void SetProjectInfo(); bool Validate(); protected slots: - void HandleGemsButton(); + virtual bool ValidateProjectName(); + virtual bool ValidateProjectPath(); - private: - QScopedPointer m_ui; + protected: + QString GetDefaultProjectPath(); + + QHBoxLayout* m_horizontalLayout; + QVBoxLayout* m_verticalLayout; + FormLineEditWidget* m_projectName; + FormBrowseEditWidget* m_projectPath; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.ui b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.ui deleted file mode 100644 index 934238a257..0000000000 --- a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.ui +++ /dev/null @@ -1,113 +0,0 @@ - - - ProjectSettingsClass - - - - 0 - 0 - 782 - 579 - - - - Form - - - - - - - - Project Settings - - - - - - - Gems - - - - - - - Qt::Horizontal - - - - 761 - 20 - - - - - - - - - - - - - - Project Name - - - - - - - - - - Project Location - - - - - - - - - - Project Image Location - - - - - - - - - - Project Background Image Location - - - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - diff --git a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp index 7b9e3ecb9d..425aa8514d 100644 --- a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp @@ -182,10 +182,6 @@ namespace O3DE::ProjectManager connect(projectButton, &ProjectButton::CopyProject, this, &ProjectsScreen::HandleCopyProject); connect(projectButton, &ProjectButton::RemoveProject, this, &ProjectsScreen::HandleRemoveProject); connect(projectButton, &ProjectButton::DeleteProject, this, &ProjectsScreen::HandleDeleteProject); - -#ifdef SHOW_ALL_PROJECT_ACTIONS - connect(projectButton, &ProjectButton::EditProjectGems, this, &ProjectsScreen::HandleEditProjectGems); -#endif } layout->addWidget(projectsScrollArea); @@ -293,14 +289,8 @@ namespace O3DE::ProjectManager void ProjectsScreen::HandleEditProject(const QString& projectPath) { emit NotifyCurrentProject(projectPath); - emit ResetScreenRequest(ProjectManagerScreen::UpdateProject); emit ChangeScreenRequest(ProjectManagerScreen::UpdateProject); } - void ProjectsScreen::HandleEditProjectGems(const QString& projectPath) - { - emit NotifyCurrentProject(projectPath); - emit ChangeScreenRequest(ProjectManagerScreen::GemCatalog); - } void ProjectsScreen::HandleCopyProject(const QString& projectPath) { // Open file dialog and choose location for copied project then register copy with O3DE diff --git a/Code/Tools/ProjectManager/Source/ProjectsScreen.h b/Code/Tools/ProjectManager/Source/ProjectsScreen.h index d88ba8398d..e02b34525b 100644 --- a/Code/Tools/ProjectManager/Source/ProjectsScreen.h +++ b/Code/Tools/ProjectManager/Source/ProjectsScreen.h @@ -41,7 +41,6 @@ namespace O3DE::ProjectManager void HandleAddProjectButton(); void HandleOpenProject(const QString& projectPath); void HandleEditProject(const QString& projectPath); - void HandleEditProjectGems(const QString& projectPath); void HandleCopyProject(const QString& projectPath); void HandleRemoveProject(const QString& projectPath); void HandleDeleteProject(const QString& projectPath); diff --git a/Code/Tools/ProjectManager/Source/ScreenDefs.h b/Code/Tools/ProjectManager/Source/ScreenDefs.h index 43ed303461..198f1b5d03 100644 --- a/Code/Tools/ProjectManager/Source/ScreenDefs.h +++ b/Code/Tools/ProjectManager/Source/ScreenDefs.h @@ -26,7 +26,7 @@ namespace O3DE::ProjectManager GemCatalog, Projects, UpdateProject, - ProjectSettings, + UpdateProjectSettings, EngineSettings }; @@ -37,7 +37,7 @@ namespace O3DE::ProjectManager { "GemCatalog", ProjectManagerScreen::GemCatalog}, { "Projects", ProjectManagerScreen::Projects}, { "UpdateProject", ProjectManagerScreen::UpdateProject}, - { "ProjectSettings", ProjectManagerScreen::ProjectSettings}, + { "UpdateProjectSettings", ProjectManagerScreen::UpdateProjectSettings}, { "EngineSettings", ProjectManagerScreen::EngineSettings} }; diff --git a/Code/Tools/ProjectManager/Source/ScreenFactory.cpp b/Code/Tools/ProjectManager/Source/ScreenFactory.cpp index b2b4376e14..a85f44080d 100644 --- a/Code/Tools/ProjectManager/Source/ScreenFactory.cpp +++ b/Code/Tools/ProjectManager/Source/ScreenFactory.cpp @@ -16,7 +16,7 @@ #include #include #include -#include +#include #include namespace O3DE::ProjectManager @@ -42,8 +42,8 @@ namespace O3DE::ProjectManager case (ProjectManagerScreen::UpdateProject): newScreen = new UpdateProjectCtrl(parent); break; - case (ProjectManagerScreen::ProjectSettings): - newScreen = new ProjectSettingsScreen(parent); + case (ProjectManagerScreen::UpdateProjectSettings): + newScreen = new UpdateProjectSettingsScreen(parent); break; case (ProjectManagerScreen::EngineSettings): newScreen = new EngineSettingsScreen(parent); diff --git a/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp b/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp index 6206d4cee9..52fcbf354a 100644 --- a/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/ScreensCtrl.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp index b3180966ce..dd130d804d 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp @@ -10,15 +10,20 @@ * */ -#include -#include +#include #include -#include +#include +#include +#include +#include +#include #include -#include -#include #include +#include +#include +#include +#include namespace O3DE::ProjectManager { @@ -26,31 +31,57 @@ namespace O3DE::ProjectManager : ScreenWidget(parent) { QVBoxLayout* vLayout = new QVBoxLayout(); - setLayout(vLayout); + vLayout->setContentsMargins(0, 0, 0, 0); - m_screensCtrl = new ScreensCtrl(); - vLayout->addWidget(m_screensCtrl); + m_header = new ScreenHeader(this); + m_header->setTitle(tr("")); + m_header->setSubTitle(tr("Edit Project Settings:")); + connect(m_header->backButton(), &QPushButton::clicked, this, &UpdateProjectCtrl::HandleBackButton); + vLayout->addWidget(m_header); + + m_updateSettingsScreen = new UpdateProjectSettingsScreen(); + m_gemCatalogScreen = new GemCatalogScreen(); + + m_stack = new QStackedWidget(this); + m_stack->setObjectName("body"); + m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding)); + vLayout->addWidget(m_stack); + + QFrame* topBarFrameWidget = new QFrame(this); + topBarFrameWidget->setObjectName("projectSettingsTopFrame"); + QHBoxLayout* topBarHLayout = new QHBoxLayout(); + topBarHLayout->setContentsMargins(0, 0, 0, 0); + topBarFrameWidget->setLayout(topBarHLayout); + + QTabWidget* tabWidget = new QTabWidget(); + tabWidget->setObjectName("projectSettingsTab"); + tabWidget->tabBar()->setObjectName("projectSettingsTabBar"); + tabWidget->addTab(m_updateSettingsScreen, tr("General")); + + QPushButton* gemsButton = new QPushButton(tr("Add More Gems"), this); + topBarHLayout->addWidget(gemsButton); + tabWidget->setCornerWidget(gemsButton); + + topBarHLayout->addWidget(tabWidget); + + m_stack->addWidget(topBarFrameWidget); + m_stack->addWidget(m_gemCatalogScreen); QDialogButtonBox* backNextButtons = new QDialogButtonBox(); + backNextButtons->setObjectName("footer"); vLayout->addWidget(backNextButtons); m_backButton = backNextButtons->addButton(tr("Back"), QDialogButtonBox::RejectRole); + m_backButton->setProperty("secondary", true); m_nextButton = backNextButtons->addButton(tr("Next"), QDialogButtonBox::ApplyRole); - connect(m_backButton, &QPushButton::pressed, this, &UpdateProjectCtrl::HandleBackButton); - connect(m_nextButton, &QPushButton::pressed, this, &UpdateProjectCtrl::HandleNextButton); + connect(gemsButton, &QPushButton::clicked, this, &UpdateProjectCtrl::HandleGemsButton); + connect(m_backButton, &QPushButton::clicked, this, &UpdateProjectCtrl::HandleBackButton); + connect(m_nextButton, &QPushButton::clicked, this, &UpdateProjectCtrl::HandleNextButton); connect(reinterpret_cast(parent), &ScreensCtrl::NotifyCurrentProject, this, &UpdateProjectCtrl::UpdateCurrentProject); - m_screensOrder = - { - ProjectManagerScreen::ProjectSettings, - ProjectManagerScreen::GemCatalog - }; - m_screensCtrl->BuildScreens(m_screensOrder); - m_screensCtrl->ForceChangeToScreen(ProjectManagerScreen::ProjectSettings, false); - - UpdateNextButtonText(); - + Update(); + setLayout(vLayout); } ProjectManagerScreen UpdateProjectCtrl::GetScreenEnum() @@ -58,63 +89,70 @@ namespace O3DE::ProjectManager return ProjectManagerScreen::UpdateProject; } + void UpdateProjectCtrl::NotifyCurrentScreen() + { + m_stack->setCurrentIndex(ScreenOrder::Settings); + Update(); + } + + void UpdateProjectCtrl::HandleGemsButton() + { + m_stack->setCurrentWidget(m_gemCatalogScreen); + Update(); + } + void UpdateProjectCtrl::HandleBackButton() { - if (!m_screensCtrl->GotoPreviousScreen()) + if (m_stack->currentIndex() > 0) { - emit GotoPreviousScreenRequest(); + m_stack->setCurrentIndex(m_stack->currentIndex() - 1); + Update(); } else { - UpdateNextButtonText(); + emit GotoPreviousScreenRequest(); } } void UpdateProjectCtrl::HandleNextButton() { - ScreenWidget* currentScreen = m_screensCtrl->GetCurrentScreen(); - ProjectManagerScreen screenEnum = currentScreen->GetScreenEnum(); - auto screenOrderIter = m_screensOrder.begin(); - for (; screenOrderIter != m_screensOrder.end(); ++screenOrderIter) + if (m_stack->currentIndex() == ScreenOrder::Settings) { - if (*screenOrderIter == screenEnum) + if (m_updateSettingsScreen) { - ++screenOrderIter; - break; - } - } - - if (screenEnum == ProjectManagerScreen::ProjectSettings) - { - auto projectScreen = reinterpret_cast(currentScreen); - if (projectScreen) - { - if (!projectScreen->Validate()) + if (!m_updateSettingsScreen->Validate()) { QMessageBox::critical(this, tr("Invalid project settings"), tr("Invalid project settings")); return; } - m_projectInfo = projectScreen->GetProjectInfo(); + ProjectInfo newProjectSettings = m_updateSettingsScreen->GetProjectInfo(); + + // Update project if settings changed + if (m_projectInfo != newProjectSettings) + { + bool result = PythonBindingsInterface::Get()->UpdateProject(newProjectSettings); + if (!result) + { + QMessageBox::critical(this, tr("Project update failed"), tr("Failed to update project.")); + return; + } + } + + // Check if project path has changed and move it + if (newProjectSettings.m_path != m_projectInfo.m_path) + { + if (!ProjectUtils::MoveProject(m_projectInfo.m_path, newProjectSettings.m_path)) + { + QMessageBox::critical(this, tr("Project move failed"), tr("Failed to move project.")); + return; + } + } + + m_projectInfo = newProjectSettings; } } - if (screenOrderIter != m_screensOrder.end()) - { - m_screensCtrl->ChangeToScreen(*screenOrderIter); - UpdateNextButtonText(); - } - else - { - auto result = PythonBindingsInterface::Get()->UpdateProject(m_projectInfo); - if (result) - { - emit ChangeScreenRequest(ProjectManagerScreen::Projects); - } - else - { - QMessageBox::critical(this, tr("Project update failed"), tr("Failed to update project.")); - } - } + emit ChangeScreenRequest(ProjectManagerScreen::Projects); } void UpdateProjectCtrl::UpdateCurrentProject(const QString& projectPath) @@ -124,16 +162,28 @@ namespace O3DE::ProjectManager { m_projectInfo = projectResult.GetValue(); } + + Update(); + UpdateSettingsScreen(); } - void UpdateProjectCtrl::UpdateNextButtonText() + void UpdateProjectCtrl::Update() { - QString nextButtonText = tr("Continue"); - if (m_screensCtrl->GetCurrentScreen()->GetScreenEnum() == ProjectManagerScreen::GemCatalog) + if (m_stack->currentIndex() == ScreenOrder::Gems) { - nextButtonText = tr("Update Project"); + m_header->setSubTitle(QString(tr("Add More Gems to \"%1\"")).arg(m_projectInfo.m_projectName)); + m_nextButton->setText(tr("Confirm")); } - m_nextButton->setText(nextButtonText); + else + { + m_header->setSubTitle(QString(tr("Edit Project Settings: \"%1\"")).arg(m_projectInfo.m_projectName)); + m_nextButton->setText(tr("Save")); + } + } + + void UpdateProjectCtrl::UpdateSettingsScreen() + { + m_updateSettingsScreen->SetProjectInfo(m_projectInfo); } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h index ee871e7bb2..231bfb8f19 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h +++ b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.h @@ -12,40 +12,57 @@ #pragma once #if !defined(Q_MOC_RUN) -#include "ProjectInfo.h" +#include #include -#include -#include #endif +QT_FORWARD_DECLARE_CLASS(QStackedWidget) +QT_FORWARD_DECLARE_CLASS(QTabWidget) +QT_FORWARD_DECLARE_CLASS(QPushButton) +QT_FORWARD_DECLARE_CLASS(QFrame) namespace O3DE::ProjectManager { - class UpdateProjectCtrl - : public ScreenWidget + QT_FORWARD_DECLARE_CLASS(ScreenHeader) + QT_FORWARD_DECLARE_CLASS(UpdateProjectSettingsScreen) + QT_FORWARD_DECLARE_CLASS(GemCatalogScreen) + + class UpdateProjectCtrl : public ScreenWidget { public: explicit UpdateProjectCtrl(QWidget* parent = nullptr); ~UpdateProjectCtrl() = default; ProjectManagerScreen GetScreenEnum() override; + protected: + void NotifyCurrentScreen() override; protected slots: void HandleBackButton(); void HandleNextButton(); + void HandleGemsButton(); void UpdateCurrentProject(const QString& projectPath); private: - void UpdateNextButtonText(); + void Update(); + void UpdateSettingsScreen(); - ScreensCtrl* m_screensCtrl; - QPushButton* m_backButton; - QPushButton* m_nextButton; + enum ScreenOrder + { + Settings, + Gems + }; + + ScreenHeader* m_header = nullptr; + QStackedWidget* m_stack = nullptr; + UpdateProjectSettingsScreen* m_updateSettingsScreen = nullptr; + GemCatalogScreen* m_gemCatalogScreen = nullptr; + + QPushButton* m_backButton = nullptr; + QPushButton* m_nextButton = nullptr; QVector m_screensOrder; ProjectInfo m_projectInfo; - - ProjectManagerScreen m_screenEnum; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp new file mode 100644 index 0000000000..c29be3c7fd --- /dev/null +++ b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp @@ -0,0 +1,51 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include +#include + +#include +#include + +namespace O3DE::ProjectManager +{ + UpdateProjectSettingsScreen::UpdateProjectSettingsScreen(QWidget* parent) + : ProjectSettingsScreen(parent) + { + } + + ProjectManagerScreen UpdateProjectSettingsScreen::GetScreenEnum() + { + return ProjectManagerScreen::UpdateProjectSettings; + } + + void UpdateProjectSettingsScreen::SetProjectInfo(const ProjectInfo& projectInfo) + { + m_projectName->lineEdit()->setText(projectInfo.m_projectName); + m_projectPath->lineEdit()->setText(projectInfo.m_path); + } + + bool UpdateProjectSettingsScreen::ValidateProjectPath() + { + bool projectPathIsValid = true; + if (m_projectPath->lineEdit()->text().isEmpty()) + { + projectPathIsValid = false; + m_projectPath->setErrorLabelText(tr("Please provide a valid location.")); + } + + m_projectPath->setErrorLabelVisible(!projectPathIsValid); + return projectPathIsValid; + } + +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.h b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.h new file mode 100644 index 0000000000..95bbceb9c6 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.h @@ -0,0 +1,34 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#endif + +namespace O3DE::ProjectManager +{ + class UpdateProjectSettingsScreen + : public ProjectSettingsScreen + { + public: + explicit UpdateProjectSettingsScreen(QWidget* parent = nullptr); + ~UpdateProjectSettingsScreen() = default; + ProjectManagerScreen GetScreenEnum() override; + + void SetProjectInfo(const ProjectInfo& projectInfo); + + protected: + bool ValidateProjectPath() override; + }; + +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/project_manager_files.cmake b/Code/Tools/ProjectManager/project_manager_files.cmake index feaea4c172..a7a36f26ab 100644 --- a/Code/Tools/ProjectManager/project_manager_files.cmake +++ b/Code/Tools/ProjectManager/project_manager_files.cmake @@ -38,6 +38,8 @@ set(FILES Source/ProjectInfo.cpp Source/ProjectUtils.h Source/ProjectUtils.cpp + Source/UpdateProjectSettingsScreen.h + Source/UpdateProjectSettingsScreen.cpp Source/NewProjectSettingsScreen.h Source/NewProjectSettingsScreen.cpp Source/CreateProjectCtrl.h @@ -48,7 +50,6 @@ set(FILES Source/ProjectsScreen.cpp Source/ProjectSettingsScreen.h Source/ProjectSettingsScreen.cpp - Source/ProjectSettingsScreen.ui Source/EngineSettingsScreen.h Source/EngineSettingsScreen.cpp Source/ProjectButtonWidget.h From 5dff21239894201c1257968a45e85ee7376b4643 Mon Sep 17 00:00:00 2001 From: Benjamin Jillich Date: Wed, 2 Jun 2021 19:49:07 +0200 Subject: [PATCH 39/45] Fixing non-unity build --- Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp index 2c94c621bb..a383a0f93b 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include From 3549db295e3732fa0e70652973881f0e801d96ab Mon Sep 17 00:00:00 2001 From: Junbo Liang <68558268+junbo75@users.noreply.github.com> Date: Wed, 2 Jun 2021 11:36:20 -0700 Subject: [PATCH 40/45] [LYN-4184] AWSClientAuth, AWSCore and AWSMetrics don't have the expected target or alias defined (#1089) [LYN-4184] AWSClientAuth, AWSCore and AWSMetrics don't have the expected target or alias defined --- Gems/AWSClientAuth/Code/CMakeLists.txt | 6 +++++- Gems/AWSCore/Code/CMakeLists.txt | 6 ++---- Gems/AWSCore/Code/Include/Private/AWSCoreEditorModule.h | 6 +++--- Gems/AWSCore/Code/Source/AWSCoreEditorModule.cpp | 8 +++----- Gems/AWSCore/Code/Source/AWSCoreModule.cpp | 2 -- Gems/AWSCore/Code/awscore_editor_shared_files.cmake | 2 -- Gems/AWSMetrics/Code/CMakeLists.txt | 6 +++++- 7 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Gems/AWSClientAuth/Code/CMakeLists.txt b/Gems/AWSClientAuth/Code/CMakeLists.txt index 40f6e0fe36..3f85b6453e 100644 --- a/Gems/AWSClientAuth/Code/CMakeLists.txt +++ b/Gems/AWSClientAuth/Code/CMakeLists.txt @@ -56,9 +56,13 @@ ly_add_target( Gem::HttpRequestor ) -# servers and clients use the above module. +# Load the "Gem::AWSClientAuth" module in all types of applications. ly_create_alias(NAME AWSClientAuth.Servers NAMESPACE Gem TARGETS Gem::AWSClientAuth) ly_create_alias(NAME AWSClientAuth.Clients NAMESPACE Gem TARGETS Gem::AWSClientAuth) +if (PAL_TRAIT_BUILD_HOST_TOOLS) + ly_create_alias(NAME AWSClientAuth.Tools NAMESPACE Gem TARGETS Gem::AWSClientAuth) + ly_create_alias(NAME AWSClientAuth.Builders NAMESPACE Gem TARGETS Gem::AWSClientAuth) +endif() ################################################################################ # Tests diff --git a/Gems/AWSCore/Code/CMakeLists.txt b/Gems/AWSCore/Code/CMakeLists.txt index 46046c0791..7edb22124f 100644 --- a/Gems/AWSCore/Code/CMakeLists.txt +++ b/Gems/AWSCore/Code/CMakeLists.txt @@ -79,14 +79,12 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) INCLUDE_DIRECTORIES PRIVATE Include/Private - COMPILE_DEFINITIONS - PRIVATE - AWSCORE_EDITOR BUILD_DEPENDENCIES PRIVATE AZ::AzCore - Gem::AWSCore.Static Gem::AWSCore.Editor.Static + RUNTIME_DEPENDENCIES + Gem::AWSCore ) ly_add_target( diff --git a/Gems/AWSCore/Code/Include/Private/AWSCoreEditorModule.h b/Gems/AWSCore/Code/Include/Private/AWSCoreEditorModule.h index 91a1af00d9..45a2c1f9f7 100644 --- a/Gems/AWSCore/Code/Include/Private/AWSCoreEditorModule.h +++ b/Gems/AWSCore/Code/Include/Private/AWSCoreEditorModule.h @@ -11,15 +11,15 @@ #pragma once -#include +#include namespace AWSCore { class AWSCoreEditorModule - : public AWSCoreModule + :public AZ::Module { public: - AZ_RTTI(AWSCoreEditorModule, "{C1C9B898-848B-4C2F-A7AA-69642D12BCB5}", AWSCoreModule); + AZ_RTTI(AWSCoreEditorModule, "{C1C9B898-848B-4C2F-A7AA-69642D12BCB5}", AZ::Module); AZ_CLASS_ALLOCATOR(AWSCoreEditorModule, AZ::SystemAllocator, 0); AWSCoreEditorModule(); diff --git a/Gems/AWSCore/Code/Source/AWSCoreEditorModule.cpp b/Gems/AWSCore/Code/Source/AWSCoreEditorModule.cpp index d8df1695c2..69e45bfd68 100644 --- a/Gems/AWSCore/Code/Source/AWSCoreEditorModule.cpp +++ b/Gems/AWSCore/Code/Source/AWSCoreEditorModule.cpp @@ -15,7 +15,6 @@ namespace AWSCore { AWSCoreEditorModule::AWSCoreEditorModule() - : AWSCoreModule() { // Push results of [MyComponent]::CreateDescriptor() into m_descriptors here. m_descriptors.insert(m_descriptors.end(), { @@ -28,10 +27,9 @@ namespace AWSCore */ AZ::ComponentTypeList AWSCoreEditorModule::GetRequiredSystemComponents() const { - AZ::ComponentTypeList requiredComponents = AWSCoreModule::GetRequiredSystemComponents(); - requiredComponents.push_back(azrtti_typeid()); - - return requiredComponents; + return AZ::ComponentTypeList{ + azrtti_typeid() + }; } } diff --git a/Gems/AWSCore/Code/Source/AWSCoreModule.cpp b/Gems/AWSCore/Code/Source/AWSCoreModule.cpp index 19a3ad4383..a80ca62b89 100644 --- a/Gems/AWSCore/Code/Source/AWSCoreModule.cpp +++ b/Gems/AWSCore/Code/Source/AWSCoreModule.cpp @@ -40,9 +40,7 @@ namespace AWSCore } -#if !defined(AWSCORE_EDITOR) // DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM // The first parameter should be GemName_GemIdLower // The second should be the fully qualified name of the class above AZ_DECLARE_MODULE_CLASS(Gem_AWSCore, AWSCore::AWSCoreModule) -#endif diff --git a/Gems/AWSCore/Code/awscore_editor_shared_files.cmake b/Gems/AWSCore/Code/awscore_editor_shared_files.cmake index 61ff9c3bf2..42cebe8dc4 100644 --- a/Gems/AWSCore/Code/awscore_editor_shared_files.cmake +++ b/Gems/AWSCore/Code/awscore_editor_shared_files.cmake @@ -11,7 +11,5 @@ set(FILES Include/Private/AWSCoreEditorModule.h - Include/Private/AWSCoreModule.h Source/AWSCoreEditorModule.cpp - Source/AWSCoreModule.cpp ) diff --git a/Gems/AWSMetrics/Code/CMakeLists.txt b/Gems/AWSMetrics/Code/CMakeLists.txt index aa790371d2..3e7118cb58 100644 --- a/Gems/AWSMetrics/Code/CMakeLists.txt +++ b/Gems/AWSMetrics/Code/CMakeLists.txt @@ -46,9 +46,13 @@ ly_add_target( Gem::AWSCore ) -# Servers and Clients use the above metrics module +# Load the "Gem::AWSMetrics" module in all types of applications. ly_create_alias(NAME AWSMetrics.Servers NAMESPACE Gem TARGETS Gem::AWSMetrics) ly_create_alias(NAME AWSMetrics.Clients NAMESPACE Gem TARGETS Gem::AWSMetrics) +if (PAL_TRAIT_BUILD_HOST_TOOLS) + ly_create_alias(NAME AWSMetrics.Tools NAMESPACE Gem TARGETS Gem::AWSMetrics) + ly_create_alias(NAME AWSMetrics.Builders NAMESPACE Gem TARGETS Gem::AWSMetrics) +endif() ################################################################################ # Tests From 7631cdc11e6e0b23dba10c16b774bb0dc6825757 Mon Sep 17 00:00:00 2001 From: Qing Tao <55564570+VickyAtAZ@users.noreply.github.com> Date: Wed, 2 Jun 2021 14:06:04 -0700 Subject: [PATCH 41/45] ATOM-15659 Add changing StencilState support to DynamicDrawContext (#1009) --- .../DynamicDraw/DynamicDrawContext.h | 8 +-- .../DynamicDraw/DynamicDrawContext.cpp | 54 ++++++++----------- 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/DynamicDraw/DynamicDrawContext.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/DynamicDraw/DynamicDrawContext.h index 2dd0865688..81b23068a1 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/DynamicDraw/DynamicDrawContext.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/DynamicDraw/DynamicDrawContext.h @@ -43,7 +43,7 @@ namespace AZ { PrimitiveType = AZ_BIT(0), DepthState = AZ_BIT(1), - EnableStencil = AZ_BIT(2), + StencilState = AZ_BIT(2), FaceCullMode = AZ_BIT(3), BlendMode = AZ_BIT(4) }; @@ -110,8 +110,8 @@ namespace AZ //! Set DepthState if DrawStateOptions::DepthState option is enabled void SetDepthState(RHI::DepthState depthState); - //! Enable/disable stencil if DrawStateOptions::EnableStencil option is enabled - void SetEnableStencil(bool enable); + //! Set StencilState if DrawStateOptions::StencilState option is enabled + void SetStencilState(RHI::StencilState stencilState); //! Set CullMode if DrawStateOptions::FaceCullMode option is enabled void SetCullMode(RHI::CullMode cullMode); //! Set TargetBlendState for target 0 if DrawStateOptions::BlendMode option is enabled @@ -188,7 +188,7 @@ namespace AZ // states available for change RHI::CullMode m_cullMode; RHI::DepthState m_depthState; - bool m_enableStencil; + RHI::StencilState m_stencilState; RHI::PrimitiveTopology m_topology; RHI::TargetBlendState m_blendState0; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/DynamicDraw/DynamicDrawContext.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/DynamicDraw/DynamicDrawContext.cpp index 4e4a7a5e71..00c2122825 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/DynamicDraw/DynamicDrawContext.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/DynamicDraw/DynamicDrawContext.cpp @@ -30,25 +30,7 @@ namespace AZ constexpr const char* PerContextSrgName = "PerContextSrg"; constexpr const char* PerDrawSrgName = "PerDrawSrg"; }; - - bool CompareTargetBlendState(const RHI::TargetBlendState& firstState, const RHI::TargetBlendState& secondState) - { - return !(firstState.m_enable != secondState.m_enable - || firstState.m_blendOp != secondState.m_blendOp - || firstState.m_blendDest != secondState.m_blendDest - || firstState.m_blendSource != secondState.m_blendSource - || firstState.m_blendAlphaDest != secondState.m_blendAlphaDest - || firstState.m_blendAlphaOp != secondState.m_blendAlphaOp - || firstState.m_blendAlphaSource != secondState.m_blendAlphaSource); - } - - bool CompareDepthState(const RHI::DepthState& firstState, const RHI::DepthState& secondState) - { - return !(firstState.m_enable != secondState.m_enable - || firstState.m_func != secondState.m_func - || firstState.m_writeMask != secondState.m_writeMask); - } - + void DynamicDrawContext::MultiStates::UpdateHash(const DrawStateOptions& drawStateOptions) { if (!m_isDirty) @@ -70,9 +52,19 @@ namespace AZ seed = TypeHash64(m_depthState.m_writeMask, seed); } - if (RHI::CheckBitsAny(drawStateOptions, DrawStateOptions::EnableStencil)) + if (RHI::CheckBitsAny(drawStateOptions, DrawStateOptions::StencilState)) { - seed = TypeHash64(m_enableStencil, seed); + seed = TypeHash64(m_stencilState.m_enable, seed); + seed = TypeHash64(m_stencilState.m_readMask, seed); + seed = TypeHash64(m_stencilState.m_writeMask, seed); + seed = TypeHash64(m_stencilState.m_frontFace.m_failOp, seed); + seed = TypeHash64(m_stencilState.m_frontFace.m_depthFailOp, seed); + seed = TypeHash64(m_stencilState.m_frontFace.m_passOp, seed); + seed = TypeHash64(m_stencilState.m_frontFace.m_func, seed); + seed = TypeHash64(m_stencilState.m_backFace.m_failOp, seed); + seed = TypeHash64(m_stencilState.m_backFace.m_depthFailOp, seed); + seed = TypeHash64(m_stencilState.m_backFace.m_passOp, seed); + seed = TypeHash64(m_stencilState.m_backFace.m_func, seed); } if (RHI::CheckBitsAny(drawStateOptions, DrawStateOptions::FaceCullMode)) @@ -203,7 +195,7 @@ namespace AZ m_currentStates.m_cullMode = m_pipelineState->ConstDescriptor().m_renderStates.m_rasterState.m_cullMode; m_currentStates.m_topology = m_pipelineState->ConstDescriptor().m_inputStreamLayout.GetTopology(); m_currentStates.m_depthState = m_pipelineState->ConstDescriptor().m_renderStates.m_depthStencilState.m_depth; - m_currentStates.m_enableStencil = m_pipelineState->ConstDescriptor().m_renderStates.m_depthStencilState.m_stencil.m_enable; + m_currentStates.m_stencilState = m_pipelineState->ConstDescriptor().m_renderStates.m_depthStencilState.m_stencil; m_currentStates.m_blendState0 = m_pipelineState->ConstDescriptor().m_renderStates.m_blendState.m_targets[0]; m_currentStates.UpdateHash(m_drawStateOptions); @@ -291,7 +283,7 @@ namespace AZ { if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::DepthState)) { - if (!CompareDepthState(m_currentStates.m_depthState, depthState)) + if (!(m_currentStates.m_depthState == depthState)) { m_currentStates.m_depthState = depthState; m_currentStates.m_isDirty = true; @@ -303,19 +295,19 @@ namespace AZ } } - void DynamicDrawContext::SetEnableStencil(bool enable) + void DynamicDrawContext::SetStencilState(RHI::StencilState stencilState) { - if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::EnableStencil)) + if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::StencilState)) { - if (m_currentStates.m_enableStencil != enable) + if (!(m_currentStates.m_stencilState == stencilState)) { - m_currentStates.m_enableStencil = enable; + m_currentStates.m_stencilState = stencilState; m_currentStates.m_isDirty = true; } } else { - AZ_Warning("RHI", false, "Can't set SetEnableStencil if DrawVariation::EnableStencil wasn't enabled"); + AZ_Warning("RHI", false, "Can't set SetStencilState if DrawVariation::StencilState wasn't enabled"); } } @@ -340,7 +332,7 @@ namespace AZ { if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::BlendMode)) { - if (!CompareTargetBlendState(m_currentStates.m_blendState0, blendState)) + if (!(m_currentStates.m_blendState0 == blendState)) { m_currentStates.m_blendState0 = blendState; m_currentStates.m_isDirty = true; @@ -695,9 +687,9 @@ namespace AZ { m_pipelineState->RenderStatesOverlay().m_depthStencilState.m_depth = m_currentStates.m_depthState; } - if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::EnableStencil)) + if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::StencilState)) { - m_pipelineState->RenderStatesOverlay().m_depthStencilState.m_stencil.m_enable = m_currentStates.m_enableStencil; + m_pipelineState->RenderStatesOverlay().m_depthStencilState.m_stencil = m_currentStates.m_stencilState; } if (RHI::CheckBitsAny(m_drawStateOptions, DrawStateOptions::FaceCullMode)) { From 8a7f156e2c66235546589744d07622da69483d1a Mon Sep 17 00:00:00 2001 From: Danilo Aimini <82231674+AMZN-daimini@users.noreply.github.com> Date: Wed, 2 Jun 2021 14:45:43 -0700 Subject: [PATCH 42/45] LYN-4133 | Prefab Container Transform stores non-default values to template on Create Prefab (#1038) * Show container transforms, reset container transform to zero before saving a prefab after create. * Fix order of operations to prevent patching issues * Reset the entity to the Identity Transform instead of the default constructor to correctly set the scale to 1.0 --- .../Prefab/PrefabPublicHandler.cpp | 26 +++++++++++++++++-- .../PropertyEditor/EntityPropertyEditor.cpp | 3 ++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index fadcc1b81f..6501986231 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -243,11 +243,33 @@ namespace AzToolsFramework 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 + PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast(containerEntityId))); + state->SetParent(undoBatch.GetUndoBatch()); + state->Capture(containerBeforeReset, containerAfterReset, containerEntityId); + + state->Redo(); + } + // 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 enities are not queued up twice for propagation. + // 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 { auto selectionUndo = aznew SelectionCommand({containerEntityId}, "Select Prefab Container Entity"); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp index d0f3fa452a..60a53aca42 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.cpp @@ -969,7 +969,8 @@ namespace AzToolsFramework { // Build up components to display SharedComponentArray sharedComponentArray; - BuildSharedComponentArray(sharedComponentArray, selectionEntityTypeInfo != SelectionEntityTypeInfo::OnlyStandardEntities); + BuildSharedComponentArray(sharedComponentArray, + !(selectionEntityTypeInfo == SelectionEntityTypeInfo::OnlyStandardEntities || selectionEntityTypeInfo == SelectionEntityTypeInfo::OnlyPrefabEntities)); if (sharedComponentArray.size() == 0) { From f1dbeb584af8e2671a056baee74e645bab5382f0 Mon Sep 17 00:00:00 2001 From: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> Date: Wed, 2 Jun 2021 16:50:18 -0700 Subject: [PATCH 43/45] LYN-4206 CMake bakes install prefix during configure (#1100) --- cmake/Platform/Common/Install_common.cmake | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmake/Platform/Common/Install_common.cmake b/cmake/Platform/Common/Install_common.cmake index b18aed6fb4..710a8b266f 100644 --- a/cmake/Platform/Common/Install_common.cmake +++ b/cmake/Platform/Common/Install_common.cmake @@ -15,7 +15,11 @@ ly_set(LY_DEFAULT_INSTALL_COMPONENT Core) file(RELATIVE_PATH runtime_output_directory ${CMAKE_BINARY_DIR} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) file(RELATIVE_PATH library_output_directory ${CMAKE_BINARY_DIR} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}) -set(install_output_folder "${CMAKE_INSTALL_PREFIX}/${runtime_output_directory}/${PAL_PLATFORM_NAME}/$") +# Anywhere CMAKE_INSTALL_PREFIX is used, it has to be escaped so it is baked into the cmake_install.cmake script instead +# of baking the path. This is needed so `cmake --install --prefix ` works regardless of the CMAKE_INSTALL_PREFIX +# used to generate the solution. +# CMAKE_INSTALL_PREFIX is still used when building the INSTALL target +set(install_output_folder "\${CMAKE_INSTALL_PREFIX}/${runtime_output_directory}/${PAL_PLATFORM_NAME}/$") #! ly_setup_target: Setup the data needed to re-create the cmake target commands for a single target From 312c704ba65d2b2d447ba332fc948e301e5a34c5 Mon Sep 17 00:00:00 2001 From: Qing Tao <55564570+VickyAtAZ@users.noreply.github.com> Date: Wed, 2 Jun 2021 17:39:50 -0700 Subject: [PATCH 44/45] ATOM-15352 Find a solution to modify a render pipeline when enable a feature gem (#960) * Added atom_rpi_tools python module in Atom_RPI gem. The tool includes functions to modify pass template data and some utility functions. * Added pytest tests for atom_rpi_tools --- Gems/Atom/RPI/CMakeLists.txt | 2 + Gems/Atom/RPI/Tools/CMakeLists.txt | 23 ++ Gems/Atom/RPI/Tools/README.txt | 39 +++ Gems/Atom/RPI/Tools/__init__.py | 10 + .../RPI/Tools/atom_rpi_tools/pass_data.py | 210 ++++++++++++ .../Tools/atom_rpi_tools/tests/__init__.py | 10 + .../tests/test_pass_template.py | 303 ++++++++++++++++++ .../Tools/atom_rpi_tools/tests/test_utils.py | 53 +++ .../tests/testdata/pass_requests.json | 116 +++++++ .../tests/testdata/pass_slots.json | 21 ++ .../tests/testdata/pass_test_bad.json | 6 + Gems/Atom/RPI/Tools/atom_rpi_tools/utils.py | 31 ++ Gems/Atom/RPI/Tools/setup.py | 33 ++ 13 files changed, 857 insertions(+) create mode 100644 Gems/Atom/RPI/Tools/CMakeLists.txt create mode 100644 Gems/Atom/RPI/Tools/README.txt create mode 100644 Gems/Atom/RPI/Tools/__init__.py create mode 100644 Gems/Atom/RPI/Tools/atom_rpi_tools/pass_data.py create mode 100644 Gems/Atom/RPI/Tools/atom_rpi_tools/tests/__init__.py create mode 100644 Gems/Atom/RPI/Tools/atom_rpi_tools/tests/test_pass_template.py create mode 100644 Gems/Atom/RPI/Tools/atom_rpi_tools/tests/test_utils.py create mode 100644 Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_requests.json create mode 100644 Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_slots.json create mode 100644 Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_test_bad.json create mode 100644 Gems/Atom/RPI/Tools/atom_rpi_tools/utils.py create mode 100644 Gems/Atom/RPI/Tools/setup.py diff --git a/Gems/Atom/RPI/CMakeLists.txt b/Gems/Atom/RPI/CMakeLists.txt index 20a680bce9..8cea783633 100644 --- a/Gems/Atom/RPI/CMakeLists.txt +++ b/Gems/Atom/RPI/CMakeLists.txt @@ -10,3 +10,5 @@ # add_subdirectory(Code) +add_subdirectory(Tools) + diff --git a/Gems/Atom/RPI/Tools/CMakeLists.txt b/Gems/Atom/RPI/Tools/CMakeLists.txt new file mode 100644 index 0000000000..35df664ab2 --- /dev/null +++ b/Gems/Atom/RPI/Tools/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +if (PAL_TRAIT_BUILD_HOST_TOOLS) + ly_pip_install_local_package_editable(${CMAKE_CURRENT_LIST_DIR} atom_rpi_tools) + + if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) + ly_add_pytest( + NAME RPI::atom_rpi_tools_tests + PATH ${CMAKE_CURRENT_LIST_DIR}/atom_rpi_tools/tests/ + TIMEOUT 30 + ) + endif() +endif() + diff --git a/Gems/Atom/RPI/Tools/README.txt b/Gems/Atom/RPI/Tools/README.txt new file mode 100644 index 0000000000..ee2e9264da --- /dev/null +++ b/Gems/Atom/RPI/Tools/README.txt @@ -0,0 +1,39 @@ +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + + +INTRODUCTION +------------ + +atom_rpi_tools is a Python project that contains a collection of tools +developed by the Atom team. The project contains the following tools: + + * Render pipeline merge tool: + A library to manipulate .pass asset files and help gems create scripts to update render pipeline + + +REQUIREMENTS +------------ + + * Python 3.7.5 (64-bit) + +It is recommended that you completely remove any other versions of Python +installed on your system. + + +INSTALL +----------- +It is recommended to set up these these tools with Lumberyard's CMake build commands. + + +UNINSTALLATION +-------------- + +The preferred way to uninstall the project is: +(engine install root)/python/python -m pip uninstall atom_rpi_tools diff --git a/Gems/Atom/RPI/Tools/__init__.py b/Gems/Atom/RPI/Tools/__init__.py new file mode 100644 index 0000000000..79f8fa4422 --- /dev/null +++ b/Gems/Atom/RPI/Tools/__init__.py @@ -0,0 +1,10 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" diff --git a/Gems/Atom/RPI/Tools/atom_rpi_tools/pass_data.py b/Gems/Atom/RPI/Tools/atom_rpi_tools/pass_data.py new file mode 100644 index 0000000000..e5e7a839bd --- /dev/null +++ b/Gems/Atom/RPI/Tools/atom_rpi_tools/pass_data.py @@ -0,0 +1,210 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +""" + +import sys, os +import json +import shutil + +class PassTemplate: + # This class provide necessary functions for insert pass requests and update connections + # which are common functions required for adding features. + # It doesn't include the remove/delete furnctions since that's not common case for merging render pipeline + def __init__(self, filePath: str): + self.initialized = False + self.file_path: str = filePath + #load the json file + json_data = open(filePath, "r") + self.file_data = json.load(json_data) + + if 'ClassName' not in self.file_data or 'ClassData' not in self.file_data or self.file_data['ClassName']!='PassAsset' or 'PassTemplate' not in self.file_data['ClassData']: + raise KeyError('the json file is not a PassAsset file') + return + + if 'PassRequests' in self.file_data['ClassData']['PassTemplate']: + self.passRequests = self.file_data['ClassData']['PassTemplate']['PassRequests'] + + if 'Slots' in self.file_data['ClassData']['PassTemplate']: + self.slots = self.file_data['ClassData']['PassTemplate']['Slots'] + + self.initialized = True + print('PassTemplate is loaded from ', filePath) + + def find_pass(self, passName): + # return pass's index in PassRequests if a PassRequest with input passName exists + if not hasattr(self, 'passRequests'): + return -1 + index = 0 + for passRequest in self.passRequests: + if passRequest['Name'] == passName: + return index + index += 1 + return -1 + + def get_pass_count(self): + if not hasattr(self, 'passRequests'): + return 0 + return len(self.passRequests) + + def __validate_pass_request_data(self, passRequest): + if ('Name' not in passRequest or 'TemplateName' not in passRequest): + raise KeyError('invalid pass request data') + + def __ensure_pass_requests_key(self): + if not hasattr(self, 'passRequests'): + self.file_data['ClassData']['PassTemplate']['PassRequests'] = [] + self.passRequests = self.file_data['ClassData']['PassTemplate']['PassRequests'] + + def __ensure_pass_slots_key(self): + if not hasattr(self, 'slots'): + self.file_data['ClassData']['PassTemplate']['Slots'] = [] + self.slots = self.file_data['ClassData']['PassTemplate']['Slots'] + + def insert_pass_request(self, location, passRequest): + self.__validate_pass_request_data(passRequest) + + if (self.find_pass(passRequest['Name']) >= 0): + raise ValueError('pass request ', passRequest['Name'], ' is already exist') + # insert a passRequest before the specified location + self.__ensure_pass_requests_key() + self.passRequests.insert(location, passRequest) + + def replace_references_after(self, startPassRequest, oldPass, oldSlot, newPass, newSlot): + if not hasattr(self, 'passRequests'): + return 0 + # from all pass requests after startPassRequest + # replace all attachment references which uses oldPass and oldSlot + # with newPass and newSlot + started = False + replaced_count = 0 + for request in self.passRequests: + if started: + if ('Connections' in request): + for connection in request['Connections']: + if connection['AttachmentRef']['Pass'] == oldPass and connection['AttachmentRef']['Attachment'] == oldSlot: + connection['AttachmentRef']['Pass'] = newPass + connection['AttachmentRef']['Attachment'] = newSlot + replaced_count += 1 + if request['Name'] == startPassRequest and not started: + started = True + return replaced_count + + def replace_references_for(self, passRequest, oldPass, oldSlot, newPass, newSlot): + if not hasattr(self, 'passRequests'): + return 0 + #replace pass reference for the specified passRequest + replaced_count = 0 + for request in self.passRequests: + if request['Name'] == passRequest: + if ('Connections' in request): + for connection in request['Connections']: + if connection['AttachmentRef']['Pass'] == oldPass and connection['AttachmentRef']['Attachment'] == oldSlot: + connection['AttachmentRef']['Pass'] = newPass + connection['AttachmentRef']['Attachment'] = newSlot + replaced_count += 1 + return replaced_count #return when the specified pass request is updated. + return replaced_count + + def __validate_slot_data(self, slotData): + if ('Name' not in slotData or 'SlotType' not in slotData): + raise KeyError('invalid slot data') + + def get_slot_count(self): + if not hasattr(self, 'slots'): + return 0 + return len(self.slots) + + def find_slot(self, slotName): + # return slot's index in Slots if a PassRequest with input passName exists + if not hasattr(self, 'slots'): + return -1 + index = 0 + for slot in self.slots: + if slot['Name'] == slotName: + return index + index += 1 + return -1 + + def insert_slot(self, location, newSlotData): + # insert a new slot at specified location + self.__validate_slot_data(newSlotData) + # check if the slot already exist + if (self.find_slot(newSlotData['Name']) >= 0): + raise ValueError('Slot ', newSlotData['Name'], ' is already exist') + + self.__ensure_pass_slots_key() + self.slots.insert(location, newSlotData) + + def add_slot(self, newSlotData): + # append a new slot to slots + self.__validate_slot_data(newSlotData) + # check if the slot already exist + if (self.find_slot(newSlotData['Name']) >= 0): + raise ValueError('Slot ', newSlotData['Name'], ' is already exist') + + self.__ensure_pass_slots_key() + self.slots.append(newSlotData) + + def get_pass_request(self, passName): + if not hasattr(self, 'passRequests'): + return + # Get the pass request from PassRequests with matching pass name + for passRequest in self.passRequests: + if passRequest['Name'] == passName: + return passRequest + + def save(self): + # backup the original file + backupFilePath = self.file_path +'.backup' + shutil.copyfile(self.file_path, backupFilePath) + # save and overwrite file + with open(self.file_path, 'w') as json_file: + json.dump(self.file_data, json_file, indent = 4) + print('File [', self.file_path, '] is updated. Old version is saved in [', backupFilePath, ']') + + +class PassRequest: + + def __init__(self, passRequest: object): + self.pass_request = passRequest + if 'Connections' in passRequest: + self.connections = passRequest['Connections'] + + def __validate_connection(self, connection): + if ('LocalSlot' not in connection or 'AttachmentRef' not in connection): + raise KeyError('invalid connection data') + + def __ensure_connections_key(self): + if not hasattr(self, 'connections'): + self.pass_request['Connections'] = [] + self.connections = self.pass_request['Connections'] + + def get_connection_count(self): + if not hasattr(self, 'connections'): + return 0 + return len(self.connections) + + def find_connection(self, localSlotName): + if not hasattr(self, 'connections'): + return -1 + index = 0 + for connection in self.connections: + if connection['LocalSlot'] == localSlotName: + return index + index += 1 + return -1 + + def add_connection(self, newConnection): + self.__validate_connection(newConnection) + if self.find_connection(newConnection['LocalSlot']) >= 0: + raise ValueError('connection ', newConnection['LocalSlot'], ' already exists') + self.__ensure_connections_key() + self.connections.append(newConnection) \ No newline at end of file diff --git a/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/__init__.py b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/__init__.py new file mode 100644 index 0000000000..79f8fa4422 --- /dev/null +++ b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/__init__.py @@ -0,0 +1,10 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" diff --git a/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/test_pass_template.py b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/test_pass_template.py new file mode 100644 index 0000000000..d8cfc4840a --- /dev/null +++ b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/test_pass_template.py @@ -0,0 +1,303 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +Unit tests for pass_data.py +""" +import os +import pytest +import shutil +import json +from atom_rpi_tools.pass_data import PassTemplate +from atom_rpi_tools.pass_data import PassRequest + +good_pass_requests_file = os.path.join(os.path.dirname(__file__), 'testdata/pass_requests.json') +good_pass_slots_file = os.path.join(os.path.dirname(__file__), 'testdata/pass_slots.json') +bad_test_data_file = os.path.join(os.path.dirname(__file__), 'testdata/pass_test_bad.json') + +@pytest.fixture +def pass_requests_template(tmpdir): + filename = 'pass_requests.json' + source_path = os.path.join(os.path.dirname(__file__), 'testdata/', filename) + destFilePath = os.path.join(tmpdir, 'pass_requests.json') + shutil.copyfile(source_path, destFilePath) + return PassTemplate(destFilePath) + +@pytest.fixture +def pass_slots_template(tmpdir): + filename = 'pass_slots.json' + source_path = os.path.join(os.path.dirname(__file__), 'testdata/', filename) + destFilePath = os.path.join(tmpdir, 'pass_requests.json') + shutil.copyfile(source_path, destFilePath) + return PassTemplate(destFilePath) + +@pytest.fixture +def new_pass_request(): + pass_request = json.loads('{\"Name\": \"InsertPass\",\"TemplateName\": \"InsertPassTemplate\"}') + return pass_request + +@pytest.fixture +def new_slot(): + slot = json.loads('{\"Name\": \"NewSlot\",\"SlotType\": \"Input\"}') + return slot + +@pytest.fixture +def new_connection(): + connection = json.loads('{\"LocalSlot\": \"color\", \"AttachmentRef\": { \"Pass\": \"Parent\", \"Attachment\": \"DepthStencil\"}}') + return connection + +def test_PassTemplate_Initialize_BadPassTemplateData_ExceptionThrown(): + with pytest.raises(KeyError): + PassTemplate(bad_test_data_file) + +def test_PassTemplate_FindPass_Success(pass_requests_template): + assert pass_requests_template.find_pass('OpaquePass') == 0 + assert pass_requests_template.find_pass('ImGuiPass') == 4 + assert pass_requests_template.find_pass('NotExistPass') == -1 + +def test_PassTemplate_InsertPassRequest_AtBegining_Success(pass_requests_template, new_pass_request): + template = pass_requests_template + pass_count = template.get_pass_count() + template.insert_pass_request(0, new_pass_request) + assert template.find_pass(new_pass_request['Name']) == 0 + assert template.get_pass_count() == pass_count+1 + + # verify the change is saved + template.save() + saved_tamplate = PassTemplate(template.file_path) + assert saved_tamplate.find_pass(new_pass_request['Name'])== 0 + assert saved_tamplate.get_pass_count() == pass_count+1 + +def test_PassTemplate_InsertPassRequest_AtEnd_Success(pass_requests_template, new_pass_request): + template = pass_requests_template + pass_count = template.get_pass_count() + template.insert_pass_request(pass_count, new_pass_request) + assert template.find_pass(new_pass_request['Name']) == pass_count + assert template.get_pass_count() == pass_count+1 + + # verify the change is saved + template.save() + saved_tamplate = PassTemplate(template.file_path) + assert saved_tamplate.find_pass(new_pass_request['Name']) == pass_count + assert saved_tamplate.get_pass_count() == pass_count+1 + +def test_PassTemplate_InsertPassRequest_WithDuplicatedName_ExceptionThrown(pass_requests_template, new_pass_request): + template = pass_requests_template + # insert new pass request + template.insert_pass_request(0, new_pass_request) + pass_count = template.get_pass_count() + # exception when insert the same pass again + with pytest.raises(ValueError): + template.insert_pass_request(2, new_pass_request) + # pass count doesn't change + assert template.get_pass_count() == pass_count + +def test_PassTemplate_InsertPassRequest_WithBadData_ExceptionThrown(pass_requests_template): + template = pass_requests_template + pass_count = template.get_pass_count() + bad_pass_request = json.loads('{\"name\":\"value\"}') + with pytest.raises(KeyError): + template.insert_pass_request(2, bad_pass_request) + assert template.get_pass_count() == pass_count + +def test_PassTemplate_InsertPassRequest_AtOutOfRange_AppendSuccess(pass_requests_template, new_pass_request): + template = pass_requests_template + pass_count = template.get_pass_count() + template.insert_pass_request(pass_count+2, new_pass_request) + assert template.find_pass(new_pass_request['Name']) == pass_count + assert template.get_pass_count() == pass_count+1 + +def test_PassTemplate_ReplaceReferencesAfter_Success(pass_requests_template): + # replace OpaquePass.DepthStencil with Parent.DepthStencil' + refPass = 'OpaquePass' + # there are 2 passes after OpaquePass which use OpaquePass.DepthStencil as attachment reference + assert pass_requests_template.replace_references_after(refPass, 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 2 + # after the previous replacement, there it no OpaquePass.DepthStencil reference + refPass = 'TransparentPass' + assert pass_requests_template.replace_references_after(refPass, 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 0 + + # verify changes are saved + pass_requests_template.save() + saved_tamplate = PassTemplate(pass_requests_template.file_path) + assert saved_tamplate.replace_references_after('OpaquePass', 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 0 + +def test_PassTemplate_ReplaceReferencesFor_Success(pass_requests_template): + refPass = 'TransparentPass' + assert pass_requests_template.replace_references_for(refPass, 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 1 + refPass = '2DPass' + assert pass_requests_template.replace_references_for(refPass, 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 0 + + # verify changes are saved + pass_requests_template.save() + saved_tamplate = PassTemplate(pass_requests_template.file_path) + # no reference of OpaquePass.DepthStencil in TransparentPass + assert saved_tamplate.replace_references_for('TransparentPass', 'OpaquePass', 'DepthStencil', 'Parent', 'DepthStencil') == 0 + +def test_PassTemplate_FindSlot_Success(pass_slots_template): + assert pass_slots_template.find_slot('Color') == -1 + assert pass_slots_template.find_slot('DepthStencil') == 0 + assert pass_slots_template.find_slot('ColorInputOutput') == 1 + +def test_PassTemplate_InsertSlot_AtBegining_Success(pass_slots_template, new_slot): + template = pass_slots_template + slot_count = template.get_slot_count() + depth_stencil_slot = template.find_slot('DepthStencil') + template.insert_slot(0, new_slot) + assert template.find_slot(new_slot['Name']) == 0 + assert template.find_slot('DepthStencil') == depth_stencil_slot+1 # DepthStencil moved back by 1 + assert template.get_slot_count() == slot_count+1 + + # verify the change is saved + template.save() + saved_tamplate = PassTemplate(template.file_path) + assert saved_tamplate.find_slot(new_slot['Name']) == 0 + assert saved_tamplate.get_slot_count() == slot_count+1 + +def test_PassTemplate_InsertSlot_AtEnd_Success(pass_slots_template, new_slot): + template = pass_slots_template + slot_count = template.get_slot_count() + depth_stencil_slot = template.find_slot('DepthStencil') + template.insert_slot(slot_count, new_slot) + assert template.find_slot(new_slot['Name']) == slot_count + assert template.find_slot('DepthStencil') == depth_stencil_slot + assert template.get_slot_count() == slot_count+1 + + # verify the change is saved + template.save() + saved_tamplate = PassTemplate(template.file_path) + assert saved_tamplate.find_slot(new_slot['Name']) == slot_count + assert saved_tamplate.get_slot_count() == slot_count+1 + +def test_PassTemplate_AddSlot_GoodSlotData_Success(pass_slots_template, new_slot): + template = pass_slots_template + slot_count = template.get_slot_count() + depth_stencil_slot = template.find_slot('DepthStencil') + template.add_slot(new_slot) + assert template.find_slot(new_slot['Name']) == slot_count + assert template.find_slot('DepthStencil') == depth_stencil_slot + assert template.get_slot_count() == slot_count+1 + + # verify the change is saved + template.save() + saved_tamplate = PassTemplate(template.file_path) + assert saved_tamplate.find_slot(new_slot['Name']) == slot_count + assert saved_tamplate.get_slot_count() == slot_count+1 + +def test_PassTemplate_InsertSlot_OutOfRange_AppendSuccess(pass_slots_template, new_slot): + template = pass_slots_template + slot_count = template.get_slot_count() + template.insert_slot(slot_count+3, new_slot) + assert template.find_slot(new_slot['Name']) == slot_count + assert template.get_slot_count() == slot_count+1 + +def test_PassTemplate_AddDuplicateSlot_ExceptionThrown(pass_slots_template, new_slot): + template = pass_slots_template + slot_count = template.get_slot_count() + template.add_slot(new_slot) + + with pytest.raises(ValueError): + template.insert_slot(0, new_slot) + with pytest.raises(ValueError): + template.add_slot(new_slot) + +def test_PassTemplate_InsertOrAddSlot_WithBadSlotData_ExceptionThrown(pass_slots_template): + template = pass_slots_template + slot_count = template.get_slot_count() + bad_slot = json.loads('{\"slot\": \"xxx\"}') + + with pytest.raises(KeyError): + template.insert_slot(0, bad_slot) + with pytest.raises(KeyError): + template.add_slot(bad_slot) + +def test_PassReqeuest_Initialize_WithExistPassReqeuestFromPassTemplate_Success(pass_requests_template): + template = pass_requests_template + request = PassRequest(template.get_pass_request('OpaquePass')) + connection_count = request.get_connection_count() + assert connection_count == 2 + +def test_PassTemplate_GetPassRequest_NotExist_ReturnNull(pass_requests_template): + assert not pass_requests_template.get_pass_request('NotExistPass') + +def test_PassReqeuest_AddConnection_WithExistingConnections_Success(pass_requests_template, new_connection): + template = pass_requests_template + request = PassRequest(template.get_pass_request('OpaquePass')) + connection_count = request.get_connection_count() + request.add_connection(new_connection) + connection_count += 1 + assert request.get_connection_count() == connection_count + + # verify changes are saved + template.save() + saved_tamplate = PassTemplate(template.file_path) + saved_request = PassRequest(saved_tamplate.get_pass_request('OpaquePass')) + assert saved_request.get_connection_count() == connection_count + +def test_PassReqeuest_AddConnection_WithNoExistingConnections_Success(pass_requests_template, new_connection): + template = pass_requests_template + request = PassRequest(template.get_pass_request('ImGuiPass')) + assert request.get_connection_count() == 0 + request.add_connection(new_connection) + assert request.get_connection_count() == 1 + + # verify changes are saved + template.save() + saved_tamplate = PassTemplate(template.file_path) + saved_request = PassRequest(saved_tamplate.get_pass_request('ImGuiPass')) + assert saved_request.get_connection_count() == 1 + +def test_PassReqeuest_AddConnection_WithDuplicatedName_ExceptionThrown(pass_requests_template, new_connection): + template = pass_requests_template + request = PassRequest(template.get_pass_request('OpaquePass')) + request.add_connection(new_connection) + with pytest.raises(ValueError): + request.add_connection(new_connection) + +def test_PassReqeuest_AddConnect_BadConnectionData_ExceptionThrown(pass_requests_template, new_connection): + template = pass_requests_template + request = PassRequest(template.get_pass_request('OpaquePass')) + bad_connection = json.loads('{\"xxx\": \"xxx\"}') + with pytest.raises(KeyError): + request.add_connection(bad_connection) + +def test_PassTemplate_InsertSlot_ToEmptyList_Success(pass_requests_template, new_slot): + template = pass_requests_template + # test insert slot function to pass template which doesn't have any slots + slot_count = template.get_slot_count() + assert slot_count == 0 + + assert template.find_slot(new_slot['Name'])==-1 + pass_requests_template.insert_slot(0, new_slot) + assert template.find_slot(new_slot['Name']) == 0 + assert template.get_slot_count() == 1 + + # verify changes are saved + template.save() + saved_tamplate = PassTemplate(template.file_path) + assert saved_tamplate.get_slot_count() == 1 + +def test_PassTempalte_InsertPassRequest_ToEmptyList_Success(pass_slots_template, new_pass_request): + template = pass_slots_template + # test insert pass function to pass template which doesn't have any pass requests + pass_count = template.get_pass_count() + assert pass_count == 0 + template.insert_pass_request(0, new_pass_request) + assert template.find_pass(new_pass_request['Name']) == 0 + assert template.get_pass_count() == 1 + + # verify changes are saved + template.save() + saved_tamplate = PassTemplate(template.file_path) + assert saved_tamplate.get_pass_count() == 1 + +def test_PassTemplate_Save_Success(pass_requests_template): + pass_requests_template.save() + saved_tamplate = PassTemplate(pass_requests_template.file_path) + assert os.path.exists(pass_requests_template.file_path) + assert os.path.exists(pass_requests_template.file_path +'.backup') diff --git a/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/test_utils.py b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/test_utils.py new file mode 100644 index 0000000000..ac1bb6edf9 --- /dev/null +++ b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/test_utils.py @@ -0,0 +1,53 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +Unit tests for utils.py +""" +import pytest +import os +import atom_rpi_tools.utils as utils + + +def test_FindOrCopyFile_DestFileNotExist_CopySuccess(tmpdir): + # created dir and copied + filename = 'pass_requests.json' + source_path = os.path.join(os.path.dirname(__file__), 'testdata/', filename) + dest_path = os.path.join(tmpdir, 'testdata/', 'pass_requests.json') + assert not os.path.exists(dest_path) + utils.find_or_copy_file(dest_path, source_path) + assert os.path.exists(dest_path) + source_size = os.path.getsize(source_path) + dest_size = os.path.getsize(dest_path) + assert source_size == dest_size + +def test_FindOrCopyFile_DestFileAlreadyExists_Skip(tmpdir): + # copy %cur_dir%/testdata/pass_requests.json to tempdir/testdata/pass_requests.json + filename = 'pass_requests.json' + source_path = os.path.join(os.path.dirname(__file__), 'testdata/', filename) + dest_path = os.path.join(tmpdir, 'testdata/', 'pass_requests.json') + utils.find_or_copy_file(dest_path, source_path) + + # skip if dest_path already exists + assert os.path.exists(dest_path) + before_size = os.path.getsize(dest_path) + source_path = os.path.join(os.path.dirname(__file__), 'testdata/', 'pass_slots.json') + before_source_size = os.path.getsize(source_path) + assert before_size != source_path + utils.find_or_copy_file(dest_path, source_path) + after_size = os.path.getsize(dest_path) + assert before_size == after_size + + +def test_FindOrCopyFile_SourceFileNotExists_ExceptionThrown(tmpdir): + # report error if source doesn't exist + bad_source_path = 'notexist.dat' + dest_path = os.path.join(tmpdir, 'notexist.dat') + with pytest.raises(ValueError): + utils.find_or_copy_file(dest_path, bad_source_path) diff --git a/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_requests.json b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_requests.json new file mode 100644 index 0000000000..f3dd3f7a19 --- /dev/null +++ b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_requests.json @@ -0,0 +1,116 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "PipelineTemplate", + "PassClass": "ParentPass", + "PassRequests": [ + { + "Name": "OpaquePass", + "TemplateName": "OpaquePassTemplate", + "Connections": [ + { + "LocalSlot": "DepthStencil", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "DepthStencil" + } + }, + { + "LocalSlot": "ColorInputOutput", + "AttachmentRef": { + "Pass": "Parent", + "Attachment": "ColorInputOutput" + } + } + ] + }, + { + "Name": "TransparentPass", + "TemplateName": "TransparentPassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "DepthStencil", + "AttachmentRef": { + "Pass": "OpaquePass", + "Attachment": "DepthStencil" + } + }, + { + "LocalSlot": "ColorInputOutput", + "AttachmentRef": { + "Pass": "OpaquePass", + "Attachment": "Color" + } + } + ], + "PassData": { + "$type": "RasterPassData", + "DrawListTag": "transparent", + "DrawListSortType": "KeyThenReverseDepth", + "PipelineViewTag": "MainCamera", + "PassSrgAsset": { + "FilePath": "shaderlib/atom/features/pbr/transparentpasssrg.azsli:PassSrg" + } + } + }, + { + "Name": "AuxGeomPass", + "TemplateName": "AuxGeomPassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "DepthStencil", + "AttachmentRef": { + "Pass": "OpaquePass", + "Attachment": "DepthStencil" + } + }, + { + "LocalSlot": "ColorInputOutput", + "AttachmentRef": { + "Pass": "TransparentPass", + "Attachment": "ColorInputOutput" + } + } + ], + "PassData": { + "$type": "RasterPassData", + "DrawListTag": "auxgeom", + "PipelineViewTag": "MainCamera" + } + }, + { + "Name": "2DPass", + "TemplateName": "UIPassTemplate", + "Enabled": true, + "Connections": [ + { + "LocalSlot": "ColorInputOutput", + "AttachmentRef": { + "Pass": "TransparentPass", + "Attachment": "ColorInputOutput" + } + } + ], + "PassData": { + "$type": "RasterPassData", + "DrawListTag": "2dpass", + "PipelineViewTag": "MainCamera" + } + }, + { + "Name": "ImGuiPass", + "TemplateName": "ImGuiPassTemplate", + "PassData": { + "$type": "ImGuiPassData", + "IsDefaultImGui": true + } + } + ] + } + } +} diff --git a/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_slots.json b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_slots.json new file mode 100644 index 0000000000..7274b0968a --- /dev/null +++ b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_slots.json @@ -0,0 +1,21 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassName": "PassAsset", + "ClassData": { + "PassTemplate": { + "Name": "PipelineTemplate", + "PassClass": "ParentPass", + "Slots": [ + { + "Name": "DepthStencil", + "SlotType": "InputOutput" + }, + { + "Name": "ColorInputOutput", + "SlotType": "InputOutput" + } + ] + } + } +} diff --git a/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_test_bad.json b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_test_bad.json new file mode 100644 index 0000000000..a0e5ffcc90 --- /dev/null +++ b/Gems/Atom/RPI/Tools/atom_rpi_tools/tests/testdata/pass_test_bad.json @@ -0,0 +1,6 @@ +{ + "Type": "JsonSerialization", + "Version": 1, + "ClassData": { + } +} \ No newline at end of file diff --git a/Gems/Atom/RPI/Tools/atom_rpi_tools/utils.py b/Gems/Atom/RPI/Tools/atom_rpi_tools/utils.py new file mode 100644 index 0000000000..90037a8287 --- /dev/null +++ b/Gems/Atom/RPI/Tools/atom_rpi_tools/utils.py @@ -0,0 +1,31 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" +import os.path +from os import path +import shutil +import json + + +def find_or_copy_file(destFilePath, sourceFilePath): + if path.exists(destFilePath): + return + if not path.exists(sourceFilePath): + raise ValueError('find_or_copy_file: source file [', sourceFilePath, '] doesn\'t exist') + return + + dstDir = path.dirname(destFilePath) + if not path.isdir(dstDir): + os.makedirs(dstDir) + shutil.copyfile(sourceFilePath, destFilePath) + +def load_json_file(filePath): + file_stream = open(filePath, "r") + return json.load(file_stream) diff --git a/Gems/Atom/RPI/Tools/setup.py b/Gems/Atom/RPI/Tools/setup.py new file mode 100644 index 0000000000..ab494ec773 --- /dev/null +++ b/Gems/Atom/RPI/Tools/setup.py @@ -0,0 +1,33 @@ +""" +All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +its licensors. + +For complete copyright and license terms please see the LICENSE at the root of this +distribution (the "License"). All use of this software is governed by the License, +or, if provided, by the license below or the license accompanying this file. Do not +remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +""" +import os +import platform + +from setuptools import setup, find_packages + +PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__)) + +PYTHON_64 = platform.architecture()[0] == '64bit' + +if __name__ == '__main__': + if not PYTHON_64: + raise RuntimeError("32-bit Python is not a supported platform.") + + with open(os.path.join(PROJECT_ROOT, 'README.txt')) as f: + long_description = f.read() + + setup( + name="atom_rpi_tools", + version="1.0.0", + description='Python interface to Atom RPI tools', + long_description=long_description, + packages=find_packages(exclude=['tests']) + ) From bb92e4c0b86963d59916bbfc844d5d998215ae1f Mon Sep 17 00:00:00 2001 From: rgba16f <82187279+rgba16f@users.noreply.github.com> Date: Wed, 2 Jun 2021 19:49:38 -0500 Subject: [PATCH 45/45] Font update to dynamic draw per view (#1085) * Move AtomFont over to use the new per viewport dynamic draw context. Remove scene tracking and listening for bootstrap scene created. Remove build dependency on the Bootstrap gem. Add build dependency on the AtomBridge gem. FFont's are now initialized with a viewport Id. Remove previous DynamicDraw context per scene system. Verify FFont can get a dynamic draw context before attempting initialization. Ensure a render scene exists before attempting font initialization (as a proxy for rendering has begun) * Move AtomFont FFont to use ShaderInputNameIndex's This allowed removing all of the InitFont function as no longer need to query compiled shader info for constant data offsets. * cache the AZ::Name used to find the dynamic draw context rather than recreate it each use --- .../AtomFont/Code/CMakeLists.txt | 3 +- .../AtomLyIntegration/AtomFont/AtomFont.h | 15 +-- .../AtomLyIntegration/AtomFont/FFont.h | 22 +--- .../AtomFont/Code/Source/AtomFont.cpp | 74 +++--------- .../AtomFont/Code/Source/FFont.cpp | 105 ++++-------------- 5 files changed, 51 insertions(+), 168 deletions(-) diff --git a/Gems/AtomLyIntegration/AtomFont/Code/CMakeLists.txt b/Gems/AtomLyIntegration/AtomFont/Code/CMakeLists.txt index d3b8c8cde7..0045b37687 100644 --- a/Gems/AtomLyIntegration/AtomFont/Code/CMakeLists.txt +++ b/Gems/AtomLyIntegration/AtomFont/Code/CMakeLists.txt @@ -29,7 +29,8 @@ ly_add_target( Legacy::CryCommon Gem::Atom_RHI.Reflect Gem::Atom_RPI.Public - Gem::Atom_Bootstrap.Headers + PUBLIC + Gem::Atom_AtomBridge.Static ) ################################################################################ diff --git a/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/AtomFont.h b/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/AtomFont.h index d5463fc15c..d21796cf17 100644 --- a/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/AtomFont.h +++ b/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/AtomFont.h @@ -27,11 +27,14 @@ #include #include +#include namespace AZ { class FFont; + static constexpr char AtomFontDynamicDrawContextName[] = "AtomFont"; + //! AtomFont is the font system manager. //! AtomFont manages the lifetime of FFont instances, each of which represents an individual font (e.g Courier New Italic) @@ -90,13 +93,6 @@ namespace AZ AzFramework::FontDrawInterface* GetFontDrawInterface(AzFramework::FontId fontId) const override; AzFramework::FontDrawInterface* GetDefaultFontDrawInterface() const override; - void SceneAboutToBeRemoved(AzFramework::Scene& scene); - - - // Atom DynamicDraw interface management - AZ::RHI::Ptr GetOrCreateDynamicDrawForScene(AZ::RPI::Scene* scene); - - public: void UnregisterFont(const char* fontName); @@ -108,8 +104,6 @@ namespace AZ using FontFamilyMap = AZStd::unordered_map>; using FontFamilyReverseLookupMap = AZStd::unordered_map; - using SceneToDynamicDrawMap = AZStd::unordered_map>; - private: //! Convenience method for loading fonts IFFont* LoadFont(const char* fontName); @@ -145,9 +139,6 @@ namespace AZ int r_persistFontFamilies = 1; //!< Persist fonts for application lifetime to prevent unnecessary work; enabled by default. AZStd::vector m_persistedFontFamilies; //!< Stores persisted fonts (if "persist font families" is enabled) - - SceneToDynamicDrawMap m_sceneToDynamicDrawMap; - AZStd::shared_mutex m_sceneToDynamicDrawMutex; }; } #endif diff --git a/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/FFont.h b/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/FFont.h index 1e224d6090..78d74a784a 100644 --- a/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/FFont.h +++ b/Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/FFont.h @@ -42,11 +42,9 @@ #include #include #include +#include #include -#include -#include - struct ISystem; namespace AZ @@ -68,7 +66,6 @@ namespace AZ : public IFFont , public AZStd::intrusive_refcount , public AzFramework::FontDrawInterface - , private AZ::Render::Bootstrap::NotificationBus::Handler { using ref_count = AZStd::intrusive_refcount; friend FontDeleter; @@ -168,8 +165,8 @@ namespace AZ struct FontShaderData { - AZ::RHI::ShaderInputImageIndex m_imageInputIndex; - AZ::RHI::ShaderInputConstantIndex m_viewProjInputIndex; + AZ::RHI::ShaderInputNameIndex m_imageInputIndex = "m_texture"; + AZ::RHI::ShaderInputNameIndex m_viewProjInputIndex = "m_worldToProj"; }; public: @@ -230,7 +227,6 @@ namespace AZ private: virtual ~FFont(); - bool InitFont(AZ::RPI::Scene* renderScene); bool InitTexture(); bool InitCache(); @@ -281,8 +277,6 @@ namespace AZ void ScaleCoord(const RHI::Viewport& viewport, float& x, float& y) const; - void OnBootstrapSceneReady(AZ::RPI::Scene* bootstrapScene) override; - RPI::WindowContextSharedPtr GetDefaultWindowContext() const; RPI::ViewportContextPtr GetDefaultViewportContext() const; @@ -303,6 +297,8 @@ namespace AZ string m_name; string m_curPath; + AZ::Name m_dynamicDrawContextName = AZ::Name(AZ::AtomFontDynamicDrawContextName); + FontTexture* m_fontTexture = nullptr; size_t m_fontBufferSize = 0; @@ -315,13 +311,6 @@ namespace AZ AtomFont* m_atomFont = nullptr; bool m_fontTexDirty = false; - enum class InitializationState : AZ::u8 - { - Uninitialized, - Initializing, - Initialized - }; - AZStd::atomic m_fontInitializationState = InitializationState::Uninitialized; FontEffects m_effects; @@ -356,6 +345,7 @@ namespace AZ if (font && font->m_atomFont) { font->m_atomFont->UnregisterFont(font->m_name); + font->m_atomFont = nullptr; } delete font; diff --git a/Gems/AtomLyIntegration/AtomFont/Code/Source/AtomFont.cpp b/Gems/AtomLyIntegration/AtomFont/Code/Source/AtomFont.cpp index 623a931ac9..fd8a4f26ed 100644 --- a/Gems/AtomLyIntegration/AtomFont/Code/Source/AtomFont.cpp +++ b/Gems/AtomLyIntegration/AtomFont/Code/Source/AtomFont.cpp @@ -354,17 +354,26 @@ AZ::AtomFont::AtomFont(ISystem* system) #endif AZ::Interface::Register(this); - m_sceneEventHandler = AzFramework::ISceneSystem::SceneEvent::Handler( - [this](AzFramework::ISceneSystem::EventType eventType, const AZStd::shared_ptr& scene) + // register font per viewport dynamic draw context. + static const char* shaderFilepath = "Shaders/SimpleTextured.azshader"; + AZ::AtomBridge::PerViewportDynamicDraw::Get()->RegisterDynamicDrawContext( + AZ::Name(AZ::AtomFontDynamicDrawContextName), + [](RPI::Ptr drawContext) { - if (eventType == AzFramework::ISceneSystem::EventType::ScenePendingRemoval) - { - SceneAboutToBeRemoved(*scene); - } + Data::Instance shader = AZ::RPI::LoadShader(shaderFilepath); + AZ::RPI::ShaderOptionList shaderOptions; + shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_useColorChannels"), AZ::Name("false"))); + shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_clamp"), AZ::Name("true"))); + drawContext->InitShaderWithVariant(shader, &shaderOptions); + drawContext->InitVertexFormat( + { + {"POSITION", RHI::Format::R32G32B32_FLOAT}, + {"COLOR", RHI::Format::B8G8R8A8_UNORM}, + {"TEXCOORD0", RHI::Format::R32G32_FLOAT} + }); + drawContext->EndInit(); }); - auto sceneSystem = AzFramework::SceneSystemInterface::Get(); - AZ_Assert(sceneSystem, "Font created before the scene system is available."); - sceneSystem->ConnectToEvents(m_sceneEventHandler); + } AZ::AtomFont::~AtomFont() @@ -860,52 +869,5 @@ XmlNodeRef AZ::AtomFont::LoadFontFamilyXml(const char* fontFamilyName, string& o return root; } - -void AZ::AtomFont::SceneAboutToBeRemoved(AzFramework::Scene& scene) -{ - AZ::RPI::ScenePtr* rpiScene = scene.FindSubsystem(); - if (rpiScene) - { - AZStd::lock_guard lock(m_sceneToDynamicDrawMutex); - if (auto it = m_sceneToDynamicDrawMap.find(rpiScene->get()); it != m_sceneToDynamicDrawMap.end()) - { - m_sceneToDynamicDrawMap.erase(it); - } - } -} - -AZ::RHI::Ptr AZ::AtomFont::GetOrCreateDynamicDrawForScene(AZ::RPI::Scene* scene) -{ - static const char* shaderFilepath = "Shaders/SimpleTextured.azshader"; - - { - // shared lock while reading - AZStd::shared_lock lock(m_sceneToDynamicDrawMutex); - - if (auto it = m_sceneToDynamicDrawMap.find(scene); it != m_sceneToDynamicDrawMap.end()) - { - return it->second; - } - } - - // Create and initialize DynamicDrawContext for font draw - AZ::RHI::Ptr dynamicDraw = RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext(scene); - - Data::Instance shader = AZ::RPI::LoadShader(shaderFilepath); - AZ::RPI::ShaderOptionList shaderOptions; - shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_useColorChannels"), AZ::Name("false"))); - shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_clamp"), AZ::Name("true"))); - dynamicDraw->InitShaderWithVariant(shader, &shaderOptions); - dynamicDraw->InitVertexFormat({{"POSITION", RHI::Format::R32G32B32_FLOAT}, {"COLOR", RHI::Format::B8G8R8A8_UNORM}, {"TEXCOORD0", RHI::Format::R32G32_FLOAT}}); - dynamicDraw->EndInit(); - - // exclusive lock while writing - AZStd::lock_guard lock(m_sceneToDynamicDrawMutex); - m_sceneToDynamicDrawMap.insert(AZStd::make_pair(scene, dynamicDraw)); - - return dynamicDraw; -} - - #endif diff --git a/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp b/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp index 31eb089803..1976c69b45 100644 --- a/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp +++ b/Gems/AtomLyIntegration/AtomFont/Code/Source/FFont.cpp @@ -60,14 +60,7 @@ static const size_t MaxVerts = 8 * 1024; // 2048 quads static const size_t MaxIndices = (MaxVerts * 6) / 4; // 6 indices per quad, 6/4 * MaxVerts static const char DrawList2DPassName[] = "2dpass"; -namespace ShaderInputs -{ - static const char TextureIndexName[] = "m_texture"; - static const char WorldToProjIndexName[] = "m_worldToProj"; - static const char SamplerIndexName[] = "m_sampler"; -} - -AZ::FFont::FFont(AtomFont* atomFont, const char* fontName) +AZ::FFont::FFont(AZ::AtomFont* atomFont, const char* fontName) : m_name(fontName) , m_atomFont(atomFont) { @@ -78,9 +71,14 @@ AZ::FFont::FFont(AtomFont* atomFont, const char* fontName) FontEffect* effect = AddEffect("default"); effect->AddPass(); - AddRef(); + // Create cpu memory to cache the font draw data before submit + m_vertexBuffer = new SVF_P3F_C4B_T2F[MaxVerts]; + m_indexBuffer = new u16[MaxIndices]; - AZ::Render::Bootstrap::NotificationBus::Handler::BusConnect(); + m_vertexCount = 0; + m_indexCount = 0; + + AddRef(); } AZ::RPI::ViewportContextPtr AZ::FFont::GetDefaultViewportContext() const @@ -98,55 +96,10 @@ AZ::RPI::WindowContextSharedPtr AZ::FFont::GetDefaultWindowContext() const return {}; } -bool AZ::FFont::InitFont(AZ::RPI::Scene* renderScene) -{ - if (!renderScene) - { - return false; - } - - auto initializationState = InitializationState::Uninitialized; - // Do an atomic transition to Initializing if we're in the Uninitialized state. - // Otherwise, check the current state. - // If we're Initialized, there's no more work to be done, return true to indicate we're good to go. - // If we're Initializing (on another thread), return false to let the consumer know it's not safe for us to be used yet. - if (!m_fontInitializationState.compare_exchange_strong(initializationState, InitializationState::Initializing)) - { - return initializationState == InitializationState::Initialized; - } - - // Create and initialize DynamicDrawContext for font draw - AZ::RPI::Ptr dynamicDraw = m_atomFont->GetOrCreateDynamicDrawForScene(renderScene); - - // Save draw srg input indices for later use - Data::Instance drawSrg = dynamicDraw->NewDrawSrg(); - const RHI::ShaderResourceGroupLayout* layout = drawSrg->GetAsset()->GetLayout(); - - m_fontShaderData.m_imageInputIndex = layout->FindShaderInputImageIndex(AZ::Name(ShaderInputs::TextureIndexName)); - AZ_Error("AtomFont::FFont", m_fontShaderData.m_imageInputIndex.IsValid(), "Failed to find shader input constant %s.", - ShaderInputs::TextureIndexName); - - m_fontShaderData.m_viewProjInputIndex = layout->FindShaderInputConstantIndex(AZ::Name(ShaderInputs::WorldToProjIndexName)); - AZ_Error("AtomFont::FFont", m_fontShaderData.m_viewProjInputIndex.IsValid(), "Failed to find shader input constant %s.", - ShaderInputs::WorldToProjIndexName); - - // Create cpu memory to cache the font draw data before submit - m_vertexBuffer = new SVF_P3F_C4B_T2F[MaxVerts]; - m_indexBuffer = new u16[MaxIndices]; - - m_vertexCount = 0; - m_indexCount = 0; - - m_fontInitializationState = InitializationState::Initialized; - return true; -} - AZ::FFont::~FFont() { AZ_Assert(m_atomFont == nullptr, "The font should already be unregistered through a call to AZ::FFont::Release()"); - AZ::Render::Bootstrap::NotificationBus::Handler::BusDisconnect(); - delete[] m_vertexBuffer; delete[] m_indexBuffer; @@ -303,7 +256,8 @@ void AZ::FFont::DrawStringUInternal( const TextDrawContext& ctx) { // Lazily ensure we're initialized before attempting to render. - if (!viewportContext || !InitFont(viewportContext->GetRenderScene().get())) + // Validate that there is a render scene before attempting to init. + if (!viewportContext || !viewportContext->GetRenderScene()) { return; } @@ -323,12 +277,6 @@ void AZ::FFont::DrawStringUInternal( return; } - // if the font is about to be deleted then m_atomFont can be nullptr - if (!m_atomFont) - { - return; - } - const bool orthoMode = ctx.m_overrideViewProjMatrices; const float viewX = viewport.m_minX; @@ -406,14 +354,17 @@ void AZ::FFont::DrawStringUInternal( if (numQuads) { - auto dynamicDraw = m_atomFont->GetOrCreateDynamicDrawForScene(viewportContext->GetRenderScene().get()); - //setup per draw srg - auto drawSrg = dynamicDraw->NewDrawSrg(); - drawSrg->SetConstant(m_fontShaderData.m_viewProjInputIndex, modelViewProjMat); - drawSrg->SetImageView(m_fontShaderData.m_imageInputIndex, m_fontStreamingImage->GetImageView()); - drawSrg->Compile(); + AZ::RPI::Ptr dynamicDraw = AZ::AtomBridge::PerViewportDynamicDraw::Get()->GetDynamicDrawContextForViewport(m_dynamicDrawContextName, viewportContext->GetId()); + if (dynamicDraw) + { + //setup per draw srg + auto drawSrg = dynamicDraw->NewDrawSrg(); + drawSrg->SetConstant(m_fontShaderData.m_viewProjInputIndex, modelViewProjMat); + drawSrg->SetImageView(m_fontShaderData.m_imageInputIndex, m_fontStreamingImage->GetImageView()); + drawSrg->Compile(); - dynamicDraw->DrawIndexed(m_vertexBuffer, m_vertexCount, m_indexBuffer, m_indexCount, RHI::IndexFormat::Uint16, drawSrg); + dynamicDraw->DrawIndexed(m_vertexBuffer, m_vertexCount, m_indexBuffer, m_indexCount, RHI::IndexFormat::Uint16, drawSrg); + } m_indexCount = 0; m_vertexCount = 0; } @@ -694,12 +645,6 @@ uint32_t AZ::FFont::WriteTextQuadsToBuffers(SVF_P2F_C4B_T2F_F4B* verts, uint16_t return numQuadsWritten; } - // if the font is about to be deleted then m_atomFont can be nullptr - if (!m_atomFont) - { - return numQuadsWritten; - } - SVF_P2F_C4B_T2F_F4B* vertexData = verts; uint16_t* indexData = indices; size_t vertexOffset = 0; @@ -1523,7 +1468,7 @@ bool AZ::FFont::UpdateTexture() { using namespace AZ; - if (m_fontInitializationState != InitializationState::Initialized || !m_fontImage) + if (!m_fontImage) { return false; } @@ -1591,7 +1536,7 @@ void AZ::FFont::Prepare(const char* str, bool updateTexture, const AtomFont::Gly const bool rerenderGlyphs = m_sizeBehavior == SizeBehavior::Rerender; const AtomFont::GlyphSize usedGlyphSize = rerenderGlyphs ? glyphSize : AtomFont::defaultGlyphSize; bool texUpdateNeeded = m_fontTexture->PreCacheString(str, nullptr, m_sizeRatio, usedGlyphSize, m_fontHintParams) == 1 || m_fontTexDirty; - if (m_fontInitializationState == InitializationState::Initialized && updateTexture && texUpdateNeeded && m_fontImage) + if (updateTexture && texUpdateNeeded && m_fontImage) { UpdateTexture(); m_fontTexDirty = false; @@ -1625,12 +1570,6 @@ void AZ::FFont::ScaleCoord(const RHI::Viewport& viewport, float& x, float& y) co y *= height / WindowScaleHeight; } - -void AZ::FFont::OnBootstrapSceneReady([[maybe_unused]] AZ::RPI::Scene* bootstrapScene) -{ - InitFont(bootstrapScene); -} - static void SetCommonContextFlags(AZ::TextDrawContext& ctx, const AzFramework::TextDrawParameters& params) { if (params.m_hAlign == AzFramework::TextHorizontalAlignment::Center)