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/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py b/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py index 5dbde29b9d..9254c3d4eb 100644 --- a/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py +++ b/AutomatedTesting/Gem/PythonTests/AWS/Windows/cdk/cdk.py @@ -97,16 +97,19 @@ class Cdk: env=self._cdk_env, shell=True) - def deploy(self, context_variable: str = '') -> List[str]: + def deploy(self, context_variable: str = '', additonal_params: List[str] = None) -> List[str]: """ Deploys all the CDK stacks. :param context_variable: Context variable for enabling optional features. + :param additonal_params: Additonal parameters like --all can be passed in this way. :return List of deployed stack arns. """ if not self._cdk_path: return [] deploy_cdk_application_cmd = ['cdk', 'deploy', '--require-approval', 'never'] + if additonal_params: + deploy_cdk_application_cmd.extend(additonal_params) if context_variable: deploy_cdk_application_cmd.extend(['-c', f'{context_variable}']) diff --git a/Code/CryEngine/CryCommon/Maestro/Types/AnimParamType.h b/Code/CryEngine/CryCommon/Maestro/Types/AnimParamType.h index f7caf2db7f..4614b1638d 100644 --- a/Code/CryEngine/CryCommon/Maestro/Types/AnimParamType.h +++ b/Code/CryEngine/CryCommon/Maestro/Types/AnimParamType.h @@ -137,5 +137,6 @@ enum class AnimParamType Invalid = static_cast(0xFFFFFFFF) }; +static const int OLD_APARAM_USER = 100; #endif // CRYINCLUDE_CRYCOMMON_MAESTRO_TYPES_ANIMPARAMTYPE_H diff --git a/Code/CryEngine/CrySystem/DebugCallStack.cpp b/Code/CryEngine/CrySystem/DebugCallStack.cpp index 2a219ce674..eea4725639 100644 --- a/Code/CryEngine/CrySystem/DebugCallStack.cpp +++ b/Code/CryEngine/CrySystem/DebugCallStack.cpp @@ -561,7 +561,7 @@ void DebugCallStack::LogExceptionInfo(EXCEPTION_POINTERS* pex) if (pex) { - MINIDUMP_TYPE mdumpValue; + MINIDUMP_TYPE mdumpValue = MiniDumpNormal; bool bDump = true; switch (g_cvars.sys_dump_type) { diff --git a/Code/CryEngine/CrySystem/System.cpp b/Code/CryEngine/CrySystem/System.cpp index 6fcbc32b72..bf34700c39 100644 --- a/Code/CryEngine/CrySystem/System.cpp +++ b/Code/CryEngine/CrySystem/System.cpp @@ -66,7 +66,7 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) } if (pSystem && !pSystem->IsQuitting()) { - LRESULT result; + LRESULT result = 0; bool bAny = false; for (std::vector::const_iterator it = pSystem->m_windowMessageHandlers.begin(); it != pSystem->m_windowMessageHandlers.end(); ++it) { diff --git a/Code/CryEngine/CrySystem/SystemInit.cpp b/Code/CryEngine/CrySystem/SystemInit.cpp index 52744519bb..4a4296bbb5 100644 --- a/Code/CryEngine/CrySystem/SystemInit.cpp +++ b/Code/CryEngine/CrySystem/SystemInit.cpp @@ -634,7 +634,7 @@ ICVar* CSystem::attachVariable (const char* szVarName, int* pContainer, const ch IConsole* pConsole = GetIConsole(); ICVar* pOldVar = pConsole->GetCVar (szVarName); - int nDefault; + int nDefault = 0; if (pOldVar) { nDefault = pOldVar->GetIVal(); diff --git a/Code/Framework/AzCore/Tests/AZStd/String.cpp b/Code/Framework/AzCore/Tests/AZStd/String.cpp index 5b8c176f02..a48725309a 100644 --- a/Code/Framework/AzCore/Tests/AZStd/String.cpp +++ b/Code/Framework/AzCore/Tests/AZStd/String.cpp @@ -1914,7 +1914,7 @@ namespace UnitTest TEST_F(String, StringView_CompareIsConstexpr) { using TypeParam = char; - auto MakeCompileTimeString1 = []() constexpr -> const TypeParam* + auto ThisTestMakeCompileTimeString1 = []() constexpr -> const TypeParam* { return "HelloWorld"; }; @@ -1922,7 +1922,7 @@ namespace UnitTest { return "HelloPearl"; }; - constexpr const TypeParam* compileTimeString1 = MakeCompileTimeString1(); + constexpr const TypeParam* compileTimeString1 = ThisTestMakeCompileTimeString1(); constexpr const TypeParam* compileTimeString2 = MakeCompileTimeString2(); constexpr basic_string_view lhsView(compileTimeString1); constexpr basic_string_view rhsView(compileTimeString2); @@ -1937,11 +1937,11 @@ namespace UnitTest TEST_F(String, StringView_CompareOperatorsAreConstexpr) { using TypeParam = char; - auto MakeCompileTimeString1 = []() constexpr -> const TypeParam* + auto TestMakeCompileTimeString1 = []() constexpr -> const TypeParam* { return "HelloWorld"; }; - constexpr const TypeParam* compileTimeString1 = MakeCompileTimeString1(); + constexpr const TypeParam* compileTimeString1 = TestMakeCompileTimeString1(); constexpr basic_string_view compareView(compileTimeString1); static_assert(compareView == "HelloWorld", "string_view operator== comparison has failed"); static_assert(compareView != "MadWorld", "string_view operator!= comparison has failed"); @@ -1955,7 +1955,7 @@ namespace UnitTest { auto swap_test_func = []() constexpr -> basic_string_view { - constexpr auto MakeCompileTimeString1 = []() constexpr -> const TypeParam* + constexpr auto ThisTestMakeCompileTimeString1 = []() constexpr -> const TypeParam* { if constexpr (AZStd::is_same_v) { @@ -1977,7 +1977,7 @@ namespace UnitTest return L"InuWorld"; } }; - constexpr const TypeParam* compileTimeString1 = MakeCompileTimeString1(); + constexpr const TypeParam* compileTimeString1 = ThisTestMakeCompileTimeString1(); constexpr const TypeParam* compileTimeString2 = MakeCompileTimeString2(); basic_string_view lhsView(compileTimeString1); basic_string_view rhsView(compileTimeString2); @@ -2001,7 +2001,7 @@ namespace UnitTest TYPED_TEST(BasicStringViewConstexprFixture, HashString_FunctionIsConstexpr) { - auto MakeCompileTimeString1 = []() constexpr -> const TypeParam* + auto ThisTestMakeCompileTimeString1 = []() constexpr -> const TypeParam* { if constexpr (AZStd::is_same_v) { @@ -2012,7 +2012,7 @@ namespace UnitTest return L"HelloWorld"; } }; - constexpr const TypeParam* compileTimeString1 = MakeCompileTimeString1(); + constexpr const TypeParam* compileTimeString1 = ThisTestMakeCompileTimeString1(); constexpr basic_string_view hashView(compileTimeString1); constexpr size_t compileHash = AZStd::hash>{}(hashView); static_assert(compileHash != 0, "Hash of \"HelloWorld\" should not be 0"); diff --git a/Code/Framework/AzCore/Tests/Jobs.cpp b/Code/Framework/AzCore/Tests/Jobs.cpp index 553123496e..664b163417 100644 --- a/Code/Framework/AzCore/Tests/Jobs.cpp +++ b/Code/Framework/AzCore/Tests/Jobs.cpp @@ -395,7 +395,8 @@ namespace UnitTest } else { - int result1, result2; + int result1 = 0; + int result2 = 0; Job* job1 = aznew FibonacciJob2(m_n - 1, &result1, m_context); Job* job2 = aznew FibonacciJob2(m_n - 2, &result2, m_context); StartAsChild(job1); diff --git a/Code/Framework/AzCore/Tests/Math/Matrix4x4Tests.cpp b/Code/Framework/AzCore/Tests/Math/Matrix4x4Tests.cpp index a9baa3ddea..8c4fac86c2 100644 --- a/Code/Framework/AzCore/Tests/Math/Matrix4x4Tests.cpp +++ b/Code/Framework/AzCore/Tests/Math/Matrix4x4Tests.cpp @@ -59,7 +59,7 @@ namespace UnitTest TEST(MATH_Matrix4x4, TestCreateFrom) { - float testFloats[] = + float thisTestFloats[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, @@ -67,20 +67,20 @@ namespace UnitTest 13.0f, 14.0f, 15.0f, 16.0f }; float testFloatMtx[16]; - Matrix4x4 m1 = Matrix4x4::CreateFromRowMajorFloat16(testFloats); + Matrix4x4 m1 = Matrix4x4::CreateFromRowMajorFloat16(thisTestFloats); AZ_TEST_ASSERT(m1.GetRow(0) == Vector4(1.0f, 2.0f, 3.0f, 4.0f)); AZ_TEST_ASSERT(m1.GetRow(1) == Vector4(5.0f, 6.0f, 7.0f, 8.0f)); AZ_TEST_ASSERT(m1.GetRow(2) == Vector4(9.0f, 10.0f, 11.0f, 12.0f)); AZ_TEST_ASSERT(m1.GetRow(3) == Vector4(13.0f, 14.0f, 15.0f, 16.0f)); m1.StoreToRowMajorFloat16(testFloatMtx); - AZ_TEST_ASSERT(memcmp(testFloatMtx, testFloats, sizeof(testFloatMtx)) == 0); - m1 = Matrix4x4::CreateFromColumnMajorFloat16(testFloats); + AZ_TEST_ASSERT(memcmp(testFloatMtx, thisTestFloats, sizeof(testFloatMtx)) == 0); + m1 = Matrix4x4::CreateFromColumnMajorFloat16(thisTestFloats); AZ_TEST_ASSERT(m1.GetRow(0) == Vector4(1.0f, 5.0f, 9.0f, 13.0f)); AZ_TEST_ASSERT(m1.GetRow(1) == Vector4(2.0f, 6.0f, 10.0f, 14.0f)); AZ_TEST_ASSERT(m1.GetRow(2) == Vector4(3.0f, 7.0f, 11.0f, 15.0f)); AZ_TEST_ASSERT(m1.GetRow(3) == Vector4(4.0f, 8.0f, 12.0f, 16.0f)); m1.StoreToColumnMajorFloat16(testFloatMtx); - AZ_TEST_ASSERT(memcmp(testFloatMtx, testFloats, sizeof(testFloatMtx)) == 0); + AZ_TEST_ASSERT(memcmp(testFloatMtx, thisTestFloats, sizeof(testFloatMtx)) == 0); } TEST(MATH_Matrix4x4, TestCreateFromMatrix3x4) diff --git a/Code/Framework/AzCore/Tests/Math/ObbTests.cpp b/Code/Framework/AzCore/Tests/Math/ObbTests.cpp index de267b4265..5eb9057761 100644 --- a/Code/Framework/AzCore/Tests/Math/ObbTests.cpp +++ b/Code/Framework/AzCore/Tests/Math/ObbTests.cpp @@ -119,10 +119,10 @@ namespace UnitTest TEST(MATH_Obb, Contains) { - const Vector3 position(1.0f, 2.0f, 3.0f); - const Quaternion rotation = Quaternion::CreateRotationZ(DegToRad(30.0f)); - const Vector3 halfLengths(2.0f, 1.0f, 2.5f); - const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(position, rotation, halfLengths); + const Vector3 testPosition(1.0f, 2.0f, 3.0f); + const Quaternion testRotation = Quaternion::CreateRotationZ(DegToRad(30.0f)); + const Vector3 testHalfLengths(2.0f, 1.0f, 2.5f); + const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(testPosition, testRotation, testHalfLengths); // test some pairs of points which should be just either side of the Obb boundary EXPECT_TRUE(obb.Contains(Vector3(1.35f, 3.35f, 3.5f))); EXPECT_FALSE(obb.Contains(Vector3(1.35f, 3.4f, 3.5f))); @@ -134,10 +134,10 @@ namespace UnitTest TEST(MATH_Obb, GetDistance) { - const Vector3 position(5.0f, 3.0f, 2.0f); - const Quaternion rotation = Quaternion::CreateRotationX(DegToRad(60.0f)); - const Vector3 halfLengths(0.5f, 2.0f, 1.5f); - const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(position, rotation, halfLengths); + const Vector3 testPosition(5.0f, 3.0f, 2.0f); + const Quaternion testRotation = Quaternion::CreateRotationX(DegToRad(60.0f)); + const Vector3 testHalfLengths(0.5f, 2.0f, 1.5f); + const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(testPosition, testRotation, testHalfLengths); EXPECT_NEAR(obb.GetDistance(Vector3(5.3f, 3.2f, 1.8f)), 0.0f, 1e-3f); EXPECT_NEAR(obb.GetDistance(Vector3(5.1f, 1.1f, 3.7f)), 0.9955f, 1e-3f); EXPECT_NEAR(obb.GetDistance(Vector3(4.7f, 4.5f, 4.2f)), 0.6553f, 1e-3f); @@ -146,10 +146,10 @@ namespace UnitTest TEST(MATH_Obb, GetDistanceSq) { - const Vector3 position(1.0f, 4.0f, 3.0f); - const Quaternion rotation = Quaternion::CreateRotationY(DegToRad(45.0f)); - const Vector3 halfLengths(1.5f, 3.0f, 1.0f); - const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(position, rotation, halfLengths); + const Vector3 testPosition(1.0f, 4.0f, 3.0f); + const Quaternion testRotation = Quaternion::CreateRotationY(DegToRad(45.0f)); + const Vector3 testHalfLengths(1.5f, 3.0f, 1.0f); + const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(testPosition, testRotation, testHalfLengths); EXPECT_NEAR(obb.GetDistanceSq(Vector3(1.1f, 4.3f, 2.7f)), 0.0f, 1e-3f); EXPECT_NEAR(obb.GetDistanceSq(Vector3(-0.7f, 3.5f, 2.0f)), 0.8266f, 1e-3f); EXPECT_NEAR(obb.GetDistanceSq(Vector3(2.4f, 0.5f, 1.5f)), 0.5532f, 1e-3f); diff --git a/Code/Framework/AzFramework/Platform/Windows/AzFramework/Windowing/NativeWindow_Windows.cpp b/Code/Framework/AzFramework/Platform/Windows/AzFramework/Windowing/NativeWindow_Windows.cpp index fd49f37dc8..b96ec81b5f 100644 --- a/Code/Framework/AzFramework/Platform/Windows/AzFramework/Windowing/NativeWindow_Windows.cpp +++ b/Code/Framework/AzFramework/Platform/Windows/AzFramework/Windowing/NativeWindow_Windows.cpp @@ -353,6 +353,7 @@ namespace AzFramework // Get the dimensions of the display device on which the window is currently displayed. MONITORINFO monitorInfo; + memset(&monitorInfo, 0, sizeof(MONITORINFO)); // C4701 potentially uninitialized local variable 'monitorInfo' used monitorInfo.cbSize = sizeof(MONITORINFO); const BOOL success = monitor ? GetMonitorInfo(monitor, &monitorInfo) : FALSE; if (!success) diff --git a/Code/Framework/AzNetworking/AzNetworking/Utilities/TimedThread.cpp b/Code/Framework/AzNetworking/AzNetworking/Utilities/TimedThread.cpp index d078149996..7e1f41f745 100644 --- a/Code/Framework/AzNetworking/AzNetworking/Utilities/TimedThread.cpp +++ b/Code/Framework/AzNetworking/AzNetworking/Utilities/TimedThread.cpp @@ -46,7 +46,7 @@ namespace AzNetworking } else if (m_updateRate < updateTimeMs) { - AZLOG_INFO("TimedThread bled %d ms", aznumeric_cast(updateTimeMs - m_updateRate)); + AZLOG(NET_TimedThread, "TimedThread bled %d ms", aznumeric_cast(updateTimeMs - m_updateRate)); } } OnStop(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp index c4cb3e0316..8f483ec818 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.cpp @@ -613,6 +613,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..68bc395012 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h @@ -48,6 +48,7 @@ namespace AzToolsFramework using EntityAliasOptionalReference = AZStd::optional>; using InstanceOptionalReference = AZStd::optional>; using InstanceOptionalConstReference = AZStd::optional>; + using InstanceSet = AZStd::unordered_set; using InstanceSetConstReference = AZStd::optional>; using EntityOptionalReference = AZStd::optional>; @@ -85,6 +86,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); @@ -182,7 +184,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/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index fadcc1b81f..90c3dd10ae 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 @@ -191,24 +192,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()); + 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 @@ -243,11 +227,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"); @@ -385,8 +391,8 @@ 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); } return AZ::Success(); @@ -989,6 +995,123 @@ namespace AzToolsFramework return AZ::Success(); } + PrefabOperationResult PrefabPublicHandler::DetachPrefab(const AZ::EntityId& containerEntityId) + { + if (!containerEntityId.IsValid()) + { + return AZ::Failure(AZStd::string("Cannot detach Prefab Instance with invalid container entity.")); + } + + if (IsLevelInstanceContainerEntity(containerEntityId)) + { + return AZ::Failure(AZStd::string("Cannot detach level Prefab Instance.")); + } + + InstanceOptionalReference owningInstance = GetOwnerInstanceByEntityId(containerEntityId); + if (owningInstance->get().GetContainerEntityId() != containerEntityId) + { + return AZ::Failure(AZStd::string("Input entity should be its owning Instance's container entity.")); + } + + AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework); + + { + AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DetachPrefab:UndoCapture"); + + ScopedUndoBatch undoBatch("Detach Prefab"); + + InstanceOptionalReference getParentInstanceResult = owningInstance->get().GetParentInstance(); + AZ_Assert(getParentInstanceResult.has_value(), "Can't get parent Instance from Instance of given container entity."); + + auto& parentInstance = getParentInstanceResult->get(); + const auto parentTemplateId = parentInstance.GetTemplateId(); + + { + auto instancePtr = parentInstance.DetachNestedInstance(owningInstance->get().GetInstanceAlias()); + AZ_Assert(instancePtr, "Can't detach selected Instance from its parent Instance."); + + RemoveLink(instancePtr, parentTemplateId, undoBatch.GetUndoBatch()); + + Prefab::PrefabDom instanceDomBefore; + m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, parentInstance); + + AZStd::unordered_map oldEntityAliases; + oldEntityAliases.emplace(containerEntityId, instancePtr->GetEntityAlias(containerEntityId)->get()); + + auto containerEntityPtr = instancePtr->DetachContainerEntity(); + auto& containerEntity = *containerEntityPtr.release(); + auto editorPrefabComponent = containerEntity.FindComponent(); + containerEntity.Deactivate(); + const bool editorPrefabComponentRemoved = containerEntity.RemoveComponent(editorPrefabComponent); + AZ_Assert(editorPrefabComponentRemoved, "Remove EditorPrefabComponent failed."); + delete editorPrefabComponent; + containerEntity.Activate(); + + const bool containerEntityAdded = parentInstance.AddEntity(containerEntity); + AZ_Assert(containerEntityAdded, "Add target Instance's container entity to its parent Instance failed."); + + EntityIdList entityIds; + entityIds.emplace_back(containerEntity.GetId()); + + instancePtr->GetEntities( + [&](AZStd::unique_ptr& entityPtr) + { + oldEntityAliases.emplace(entityPtr->GetId(), instancePtr->GetEntityAlias(entityPtr->GetId())->get()); + return true; + }); + + instancePtr->DetachEntities( + [&](AZStd::unique_ptr entityPtr) + { + auto& entity = *entityPtr.release(); + const bool entityAdded = parentInstance.AddEntity(entity); + AZ_Assert(entityAdded, "Add target Instance's entity to its parent Instance failed."); + + entityIds.emplace_back(entity.GetId()); + }); + + Prefab::PrefabDom instanceDomAfter; + m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, parentInstance); + + PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance detachment"); + command->Capture(instanceDomBefore, instanceDomAfter, parentTemplateId); + command->SetParent(undoBatch.GetUndoBatch()); + { + AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "Internal::DetachPrefab:RunRedo"); + command->RunRedo(); + } + + const auto instanceTemplateId = instancePtr->GetTemplateId(); + auto parentContainerEntityId = parentInstance.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, undoBatch.GetUndoBatch()); + + UpdateLinkPatchesWithNewEntityAliases(linkPatchesCopy, oldEntityAliases, parentInstance); + + CreateLink(*nestedInstancePtr, parentTemplateId, undoBatch.GetUndoBatch(), + AZStd::move(linkPatchesCopy), true); + }); + } + + AzToolsFramework::ToolsApplicationRequestBus::Broadcast( + &AzToolsFramework::ToolsApplicationRequestBus::Events::ClearDirtyEntities); + } + + return AZ::Success(); + } + void PrefabPublicHandler::GenerateContainerEntityTransform(const EntityList& topLevelEntities, AZ::Vector3& translation, AZ::Quaternion& rotation) { @@ -1252,5 +1375,30 @@ namespace AzToolsFramework stringToReplace.replace(oldAliasPathRef, newAliasPathRef); } + + void PrefabPublicHandler::UpdateLinkPatchesWithNewEntityAliases( + PrefabDom& linkPatch, + const AZStd::unordered_map& oldEntityAliases, + Instance& newParent) + { + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + linkPatch.Accept(writer); + QString previousPatchString(buffer.GetString()); + + for (const auto& [entityId, oldEntityAlias] : oldEntityAliases) + { + EntityAliasOptionalReference newEntityAlias = newParent.GetEntityAlias(entityId); + AZ_Assert( + newEntityAlias.has_value(), + "Could not fetch entity alias for entity with id '%llu' during prefab creation.", + static_cast(entityId)); + + ReplaceOldAliases(previousPatchString, oldEntityAlias, newEntityAlias->get()); + } + + linkPatch.Parse(previousPatchString.toUtf8().constData()); + } + } // namespace Prefab } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h index e68a3e0b1e..e7b6f8c932 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 DetachPrefab(const AZ::EntityId& containerEntityId) override; + private: PrefabOperationResult DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants); bool RetrieveAndSortPrefabEntitiesAndInstances(const EntityList& inputEntities, Instance& commonRootEntityOwningInstance, @@ -132,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); + void UpdateLinkPatchesWithNewEntityAliases( + 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); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h index 2e9152fd1b..1dbed53223 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h @@ -150,6 +150,17 @@ 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; + + /** + * 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 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& containerEntityId) = 0; }; } // namespace Prefab diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp index 0136f791b3..c42dbe792a 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(); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp index 021b97a7dd..9d01911532 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp @@ -237,6 +237,24 @@ namespace AzToolsFramework { deleteAction->setDisabled(true); } + + // Detach Prefab + if (selectedEntities.size() == 1) + { + AZ::EntityId selectedEntity = selectedEntities[0]; + + if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity) && + !s_prefabPublicInterface->IsLevelInstanceContainerEntity(selectedEntity)) + { + QAction* detachPrefabAction = menu->addAction(QObject::tr("Detach Prefab...")); + QObject::connect( + detachPrefabAction, &QAction::triggered, detachPrefabAction, + [this, selectedEntity] + { + ContextMenu_DetachPrefab(selectedEntity); + }); + } + } } void PrefabIntegrationManager::HandleSourceFileType(AZStd::string_view sourceFilePath, AZ::EntityId parentId, AZ::Vector3 position) const @@ -392,6 +410,17 @@ namespace AzToolsFramework } } + void PrefabIntegrationManager::ContextMenu_DetachPrefab(AZ::EntityId containerEntity) + { + PrefabOperationResult detachPrefabResult = + s_prefabPublicInterface->DetachPrefab(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( 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) { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.cpp index 88b92e8c41..d0143c5517 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.cpp @@ -124,4 +124,15 @@ namespace AzToolsFramework return cameraState; } + + float GetScreenDisplayScaling(const int viewportId) + { + float scaling = 1.0f; + ViewportInteraction::ViewportInteractionRequestBus::EventResult( + scaling, viewportId, + &ViewportInteraction::ViewportInteractionRequestBus::Events::DeviceScalingFactor); + + return scaling; + } + } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.h index 9936fb9afd..e904277078 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorSelectionUtil.h @@ -60,6 +60,9 @@ namespace AzToolsFramework /// Wrapper for EBus call to return the CameraState for a given viewport. AzFramework::CameraState GetCameraState(int viewportId); + /// Wrapper for EBus call to return the DPI scaling for a given viewport. + float GetScreenDisplayScaling(const int viewportId); + /// A utility to return the center of several points. /// Take several positions and store the min and max of each in /// turn - when all points have been added return the center/midpoint. diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp index 3507f532b5..5acfa5df59 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); } @@ -3563,9 +3573,10 @@ namespace AzToolsFramework debugDisplay.SetLineWidth(1.0f); const float labelOffset = cl_viewportGizmoAxisLabelOffset; - const auto labelXScreenPosition = (gizmoStart + (gizmoAxisX * labelOffset)) * editorCameraState.m_viewportSize; - const auto labelYScreenPosition = (gizmoStart + (gizmoAxisY * labelOffset)) * editorCameraState.m_viewportSize; - const auto labelZScreenPosition = (gizmoStart + (gizmoAxisZ * labelOffset)) * editorCameraState.m_viewportSize; + const float screenScale = GetScreenDisplayScaling(viewportId); + const auto labelXScreenPosition = (gizmoStart + (gizmoAxisX * labelOffset)) * editorCameraState.m_viewportSize * screenScale; + const auto labelYScreenPosition = (gizmoStart + (gizmoAxisY * labelOffset)) * editorCameraState.m_viewportSize * screenScale; + const auto labelZScreenPosition = (gizmoStart + (gizmoAxisZ * labelOffset)) * editorCameraState.m_viewportSize * screenScale; // draw the label of of each axis for the gizmo const float labelSize = cl_viewportGizmoAxisLabelSize; 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/LauncherUnified/Launcher.cpp b/Code/LauncherUnified/Launcher.cpp index 922397c325..c73cdd1981 100644 --- a/Code/LauncherUnified/Launcher.cpp +++ b/Code/LauncherUnified/Launcher.cpp @@ -9,6 +9,7 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ + #include #include @@ -22,6 +23,8 @@ #include #include #include +#include +#include #include @@ -45,6 +48,19 @@ extern "C" void CreateStaticModules(AZStd::vector& modulesOut); namespace { + void OnViewportResize(const AZ::Vector2& value); + + AZ_CVAR(AZ::Vector2, r_viewportSize, AZ::Vector2::CreateZero(), OnViewportResize, AZ::ConsoleFunctorFlags::DontReplicate, + "The default size for the launcher viewport, 0 0 means full screen"); + + void OnViewportResize(const AZ::Vector2& value) + { + AzFramework::NativeWindowHandle windowHandle = nullptr; + AzFramework::WindowSystemRequestBus::BroadcastResult(windowHandle, &AzFramework::WindowSystemRequestBus::Events::GetDefaultWindowHandle); + AzFramework::WindowSize newSize = AzFramework::WindowSize(aznumeric_cast(value.GetX()), aznumeric_cast(value.GetY())); + AzFramework::WindowRequestBus::Broadcast(&AzFramework::WindowRequestBus::Events::ResizeClientArea, newSize); + } + void ExecuteConsoleCommandFile(AzFramework::Application& application) { const AZStd::string_view customConCmdKey = "console-command-file"; 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 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/Sandbox/Editor/CryEdit.cpp b/Code/Sandbox/Editor/CryEdit.cpp index c60f32560b..c723e6049a 100644 --- a/Code/Sandbox/Editor/CryEdit.cpp +++ b/Code/Sandbox/Editor/CryEdit.cpp @@ -281,6 +281,8 @@ BOOL CCryDocManager::DoPromptFileName(QString& fileName, [[maybe_unused]] UINT n [[maybe_unused]] DWORD lFlags, BOOL bOpenFileDialog, [[maybe_unused]] CDocTemplate* pTemplate) { CLevelFileDialog levelFileDialog(bOpenFileDialog); + levelFileDialog.show(); + levelFileDialog.adjustSize(); if (levelFileDialog.exec() == QDialog::Accepted) { @@ -2079,6 +2081,8 @@ void CCryEditApp::OnDocumentationAWSSupport() void CCryEditApp::OnDocumentationFeedback() { FeedbackDialog dialog; + dialog.show(); + dialog.adjustSize(); dialog.exec(); } @@ -3347,6 +3351,8 @@ void CCryEditApp::OnCreateSlice() void CCryEditApp::OnOpenLevel() { CLevelFileDialog levelFileDialog(true); + levelFileDialog.show(); + levelFileDialog.adjustSize(); if (levelFileDialog.exec() == QDialog::Accepted) { diff --git a/Code/Sandbox/Editor/EditorViewportWidget.cpp b/Code/Sandbox/Editor/EditorViewportWidget.cpp index 24d1590808..667179e3cd 100644 --- a/Code/Sandbox/Editor/EditorViewportWidget.cpp +++ b/Code/Sandbox/Editor/EditorViewportWidget.cpp @@ -1233,7 +1233,7 @@ void EditorViewportWidget::SetViewportId(int id) auto controller = AZStd::make_shared(); controller->SetCameraListBuilderCallback( - [](AzFramework::Cameras& cameras) + [id](AzFramework::Cameras& cameras) { auto firstPersonRotateCamera = AZStd::make_shared(AzFramework::CameraFreeLookButton); auto firstPersonPanCamera = @@ -1243,17 +1243,17 @@ void EditorViewportWidget::SetViewportId(int id) auto orbitCamera = AZStd::make_shared(); orbitCamera->SetLookAtFn( - [](const AZ::Vector3& position, const AZ::Vector3& direction) -> AZStd::optional + [id](const AZ::Vector3& position, const AZ::Vector3& direction) -> AZStd::optional { - AZStd::optional manipulatorTransform; - AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult( - manipulatorTransform, AzToolsFramework::GetEntityContextId(), - &AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform); + AZStd::optional lookAtAfterInterpolation; + AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult( + lookAtAfterInterpolation, id, + &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::LookAtAfterInterpolation); - // initially attempt to use manipulator transform if one exists (there is a selection) - if (manipulatorTransform) + // initially attempt to use the last set look at point after an interpolation has finished + if (lookAtAfterInterpolation.has_value()) { - return manipulatorTransform->GetTranslation(); + return *lookAtAfterInterpolation; } const float RayDistance = 1000.0f; diff --git a/Code/Sandbox/Editor/LogFile.cpp b/Code/Sandbox/Editor/LogFile.cpp index 0841a6b288..0bbfd09a21 100644 --- a/Code/Sandbox/Editor/LogFile.cpp +++ b/Code/Sandbox/Editor/LogFile.cpp @@ -553,7 +553,7 @@ void CLogFile::OnWriteToConsole(const char* sText, bool bNewLine) // remember selection and the top row int len = m_hWndEditBox->document()->toPlainText().length(); - int top; + int top = 0; int from = m_hWndEditBox->textCursor().selectionStart(); int to = from + m_hWndEditBox->textCursor().selectionEnd(); bool keepPos = false; diff --git a/Code/Sandbox/Editor/QtViewPaneManager.cpp b/Code/Sandbox/Editor/QtViewPaneManager.cpp index b242f3d914..3949f0516e 100644 --- a/Code/Sandbox/Editor/QtViewPaneManager.cpp +++ b/Code/Sandbox/Editor/QtViewPaneManager.cpp @@ -121,7 +121,7 @@ protected: }; #endif -Q_GLOBAL_STATIC(QtViewPaneManager, s_instance) +Q_GLOBAL_STATIC(QtViewPaneManager, s_viewPaneManagerInstance) QWidget* QtViewPane::CreateWidget() @@ -611,12 +611,12 @@ void QtViewPaneManager::UnregisterPane(const QString& name) QtViewPaneManager* QtViewPaneManager::instance() { - return s_instance(); + return s_viewPaneManagerInstance(); } bool QtViewPaneManager::exists() { - return s_instance.exists(); + return s_viewPaneManagerInstance.exists(); } void QtViewPaneManager::SetMainWindow(AzQtComponents::DockMainWindow* mainWindow, QSettings* settings, const QByteArray& lastMainWindowState) 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 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/TrackView/SequenceBatchRenderDialog.cpp b/Code/Sandbox/Editor/TrackView/SequenceBatchRenderDialog.cpp index 69322c481c..0abe3ced75 100644 --- a/Code/Sandbox/Editor/TrackView/SequenceBatchRenderDialog.cpp +++ b/Code/Sandbox/Editor/TrackView/SequenceBatchRenderDialog.cpp @@ -59,7 +59,7 @@ namespace { int fps; const char* fpsDesc; - } fps[] = { + } fpsOptions[] = { {24, "Film(24)"}, {25, "PAL(25)"}, {30, "NTSC(30)"}, {48, "Show(48)"}, {50, "PAL Field(50)"}, {60, "NTSC Field(60)"} }; @@ -213,9 +213,9 @@ void CSequenceBatchRenderDialog::OnInitDialog() m_ui->m_resolutionCombo->setCurrentIndex(0); // Fill the FPS combo box. - for (int i = 0; i < AZStd::size(fps); ++i) + for (int i = 0; i < AZStd::size(fpsOptions); ++i) { - m_ui->m_fpsCombo->addItem(fps[i].fpsDesc); + m_ui->m_fpsCombo->addItem(fpsOptions[i].fpsDesc); } m_ui->m_fpsCombo->setCurrentIndex(0); @@ -306,9 +306,9 @@ void CSequenceBatchRenderDialog::OnRenderItemSelChange() m_ui->m_destinationEdit->setText(item.folder); // fps bool bFound = false; - for (int i = 0; i < arraysize(fps); ++i) + for (int i = 0; i < arraysize(fpsOptions); ++i) { - if (item.fps == fps[i].fps) + if (item.fps == fpsOptions[i].fps) { m_ui->m_fpsCombo->setCurrentIndex(i); bFound = true; @@ -621,7 +621,7 @@ void CSequenceBatchRenderDialog::OnFPSEditChange() void CSequenceBatchRenderDialog::OnFPSChange(int itemIndex) { - m_customFPS = fps[itemIndex].fps; + m_customFPS = fpsOptions[itemIndex].fps; CheckForEnableUpdateButton(); } @@ -1543,13 +1543,13 @@ bool CSequenceBatchRenderDialog::SetUpNewRenderItem(SRenderItem& item) item.frameRange = Range(m_ui->m_startFrame->value() / m_fpsForTimeToFrameConversion, m_ui->m_endFrame->value() / m_fpsForTimeToFrameConversion); // fps - if (m_ui->m_fpsCombo->currentIndex() == -1 || m_ui->m_fpsCombo->currentText() != fps[m_ui->m_fpsCombo->currentIndex()].fpsDesc) + if (m_ui->m_fpsCombo->currentIndex() == -1 || m_ui->m_fpsCombo->currentText() != fpsOptions[m_ui->m_fpsCombo->currentIndex()].fpsDesc) { item.fps = m_customFPS; } else { - item.fps = fps[m_ui->m_fpsCombo->currentIndex()].fps; + item.fps = fpsOptions[m_ui->m_fpsCombo->currentIndex()].fps; } // prefix item.prefix = m_ui->BATCH_RENDER_FILE_PREFIX->text(); diff --git a/Code/Sandbox/Editor/Util/AffineParts.cpp b/Code/Sandbox/Editor/Util/AffineParts.cpp index e294f93089..139519c078 100644 --- a/Code/Sandbox/Editor/Util/AffineParts.cpp +++ b/Code/Sandbox/Editor/Util/AffineParts.cpp @@ -157,7 +157,7 @@ static Quatern Qt_FromMatrix(HMatrix mat) * |w| is greater than 1/2, which is as small as a largest component can be. * Otherwise, the largest diagonal entry corresponds to the largest of |x|, * |y|, or |z|, one of which must be larger than |w|, and at least 1/2. */ - Quatern qu; + Quatern qu = { 0.0f, 0.0f, 0.0f, 1.0f }; double tr, s; tr = mat[X][X] + mat[Y][Y] + mat[Z][Z]; @@ -531,7 +531,7 @@ Quatern snuggle(Quatern q, HVect* k) #define swap(a, i, j) {a[3] = a[i]; a[i] = a[j]; a[j] = a[3]; } #define cycle(a, p) if (p) {a[3] = a[0]; a[0] = a[1]; a[1] = a[2]; a[2] = a[3]; } \ else {a[3] = a[2]; a[2] = a[1]; a[1] = a[0]; a[0] = a[3]; } - Quatern p; + Quatern p = { 0.0f, 0.0f, 0.0f, 1.0f }; float ka[4]; int i, turn = -1; ka[X] = k->x; diff --git a/Code/Sandbox/Editor/Util/FileUtil.cpp b/Code/Sandbox/Editor/Util/FileUtil.cpp index 8dd379f096..ece516659e 100644 --- a/Code/Sandbox/Editor/Util/FileUtil.cpp +++ b/Code/Sandbox/Editor/Util/FileUtil.cpp @@ -2239,7 +2239,8 @@ uint32 CFileUtil::GetAttributes(const char* filename, bool bUseSourceControl /*= bool CFileUtil::CompareFiles(const QString& strFilePath1, const QString& strFilePath2) { // Get the size of both files. If either fails we say they are different (most likely one doesn't exist) - uint64 size1, size2; + uint64 size1 = 0; + uint64 size2 = 0; if (!GetDiskFileSize(strFilePath1.toUtf8().data(), size1) || !GetDiskFileSize(strFilePath2.toUtf8().data(), size2)) { return false; diff --git a/Code/Sandbox/Editor/Util/ImageBT.cpp b/Code/Sandbox/Editor/Util/ImageBT.cpp index 30c4911cb8..79ce4bba35 100644 --- a/Code/Sandbox/Editor/Util/ImageBT.cpp +++ b/Code/Sandbox/Editor/Util/ImageBT.cpp @@ -116,6 +116,7 @@ bool CImageBT::Load(const QString& fileName, CFloatImage& image) // Get the BT header data BtHeader header; + memset(&header, 0, sizeof(BtHeader)); // C4701 potentially uninitialized local variable 'header' used bool validData = true; validData = validData && (fread(&header, sizeof(BtHeader), 1, file) != 0); diff --git a/Code/Sandbox/Editor/Util/StringHelpers.cpp b/Code/Sandbox/Editor/Util/StringHelpers.cpp index 5e44c3b0bd..12865dfe73 100644 --- a/Code/Sandbox/Editor/Util/StringHelpers.cpp +++ b/Code/Sandbox/Editor/Util/StringHelpers.cpp @@ -419,7 +419,7 @@ static inline bool MatchesWildcardsIgnoreCaseExt_Tpl(const TS& str, const TS& wi const typename TS::value_type* savedStrBegin = 0; const typename TS::value_type* savedStrEnd = 0; const typename TS::value_type* savedWild = 0; - size_t savedWildCount; + size_t savedWildCount = 0; const typename TS::value_type* pStr = str.c_str(); const typename TS::value_type* pWild = wildcards.c_str(); 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 diff --git a/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/ComponentEntityEditorPlugin.cpp b/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/ComponentEntityEditorPlugin.cpp index 54ac71db16..ed21b5b6e9 100644 --- a/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/ComponentEntityEditorPlugin.cpp +++ b/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/ComponentEntityEditorPlugin.cpp @@ -179,11 +179,11 @@ ComponentEntityEditorPlugin::ComponentEntityEditorPlugin([[maybe_unused]] IEdito LyViewPane::EntityOutliner, LyViewPane::CategoryTools, outlinerOptions); - } - AzToolsFramework::ViewPaneOptions options; - options.preferedDockingArea = Qt::NoDockWidgetArea; - RegisterViewPane(LyViewPane::SliceRelationships, LyViewPane::CategoryTools, options); + AzToolsFramework::ViewPaneOptions options; + options.preferedDockingArea = Qt::NoDockWidgetArea; + RegisterViewPane(LyViewPane::SliceRelationships, LyViewPane::CategoryTools, options); + } RegisterModuleResourceSelectors(GetIEditor()->GetResourceSelectorHost()); diff --git a/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp b/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp index 5ff2debe3d..8161d07547 100644 --- a/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp +++ b/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp @@ -1732,13 +1732,14 @@ void SandboxIntegrationManager::GoToEntitiesInViewports(const AzToolsFramework:: // compute new camera transform const float fov = AzFramework::RetrieveFov(viewportContext->GetCameraProjectionMatrix()); const float fovScale = (1.0f / AZStd::tan(fov * 0.5f)); - const float distanceToTarget = selectionSize * fovScale * centerScale; + const float distanceToLookAt = selectionSize * fovScale * centerScale; const AZ::Transform nextCameraTransform = - AZ::Transform::CreateLookAt(aabb.GetCenter() - (forward * distanceToTarget), aabb.GetCenter()); + AZ::Transform::CreateLookAt(aabb.GetCenter() - (forward * distanceToLookAt), aabb.GetCenter()); AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event( viewportContext->GetId(), - &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::InterpolateToTransform, nextCameraTransform); + &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::InterpolateToTransform, nextCameraTransform, + distanceToLookAt); } } } 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/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/CreateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp index 60e351cdb4..85e34aeced 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,6 +88,7 @@ namespace O3DE::ProjectManager emit GotoPreviousScreenRequest(); } } + void CreateProjectCtrl::HandleNextButton() { ScreenWidget* currentScreen = reinterpret_cast(m_stack->currentWidget()); @@ -106,6 +107,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 +133,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 diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index 2d243e7f8b..aa36c1b0ab 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, 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, 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, tr("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 diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp index bc44928868..c96d31c3c2 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp @@ -79,4 +79,9 @@ namespace O3DE::ProjectManager { return (m_platforms & platform); } + + bool GemInfo::operator<(const GemInfo& gemInfo) const + { + return (m_displayName < gemInfo.m_displayName); + } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h index 1032ca5eaf..722783ece1 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h @@ -61,6 +61,8 @@ namespace O3DE::ProjectManager bool IsValid() const; + bool operator<(const GemInfo& gemInfo) const; + QString m_path; QString m_name = "Unknown Gem Name"; QString m_displayName = "Unknown Gem Name"; 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); } 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; 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/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index c0481d6c87..7062d886da 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -283,12 +283,16 @@ 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"); m_enableGemProject = pybind11::module::import("o3de.enable_gem"); m_disableGemProject = pybind11::module::import("o3de.disable_gem"); + // make sure the engine is registered + RegisterThisEngine(); + return result == 0 && !PyErr_Occurred(); } catch ([[maybe_unused]] const std::exception& e) { @@ -311,7 +315,37 @@ namespace O3DE::ProjectManager return !PyErr_Occurred(); } - bool PythonBindings::ExecuteWithLock(AZStd::function executionCallback) + bool PythonBindings::RegisterThisEngine() + { + bool registrationResult = true; // already registered is considered successful + bool pythonResult = ExecuteWithLock( + [&] + { + // check current engine path against all other registered engines + // to see if we are already registered + auto allEngines = m_manifest.attr("get_engines")(); + if (pybind11::isinstance(allEngines)) + { + for (auto engine : allEngines) + { + AZ::IO::FixedMaxPath enginePath(Py_To_String(engine["path"])); + if (enginePath.Compare(m_enginePath) == 0) + { + return; + } + } + } + + auto result = m_register.attr("register")(m_enginePath.c_str()); + registrationResult = (result.cast() == 0); + }); + + bool finalResult = (registrationResult && pythonResult); + AZ_Assert(finalResult, "Registration of this engine failed!"); + return finalResult; + } + + AZ::Outcome PythonBindings::ExecuteWithLockErrorHandling(AZStd::function executionCallback) { AZStd::lock_guard lock(m_lock); pybind11::gil_scoped_release release; @@ -320,13 +354,19 @@ namespace O3DE::ProjectManager try { executionCallback(); - return true; } catch ([[maybe_unused]] const std::exception& e) { AZ_Warning("PythonBindings", false, "Python exception %s", e.what()); - return false; + return AZ::Failure(e.what()); } + + return AZ::Success(); + } + + bool PythonBindings::ExecuteWithLock(AZStd::function executionCallback) + { + return ExecuteWithLockErrorHandling(executionCallback).IsSuccess(); } AZ::Outcome PythonBindings::GetEngineInfo() @@ -419,7 +459,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 +472,79 @@ namespace O3DE::ProjectManager } } - AZ::Outcome> PythonBindings::GetGems() + AZ::Outcome, AZStd::string> PythonBindings::GetEngineGemInfos() { QVector gems; - bool result = ExecuteWithLock([&] { - // external gems - for (auto path : m_manifest.attr("get_gems")()) + auto result = ExecuteWithLockErrorHandling([&] { - gems.push_back(GemInfoFromPath(path)); - } + for (auto path : m_manifest.attr("get_engine_gems")()) + { + 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::GetAllGemInfos(const QString& projectPath) + { + QVector 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()); + } - if (!result) + 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([&] + { + 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(); + return AZ::Failure(result.GetError().c_str()); } - else + + // 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::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 +724,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..278aa2d5d7 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -39,8 +39,10 @@ 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> GetEngineGemInfos() 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 +51,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,16 +60,20 @@ 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); 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; + 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..09d9187dbd 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h +++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h @@ -57,13 +57,27 @@ 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 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. + * @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 +128,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 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..a383a0f93b 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,80 @@ namespace O3DE::ProjectManager return ProjectManagerScreen::UpdateProject; } + void UpdateProjectCtrl::NotifyCurrentScreen() + { + m_stack->setCurrentIndex(ScreenOrder::Settings); + Update(); + } + + void UpdateProjectCtrl::HandleGemsButton() + { + // The next page is the gem catalog. Gather the available gems that will be shown in the gem catalog. + m_gemCatalogScreen->ReinitForProject(m_projectInfo.m_path, /*isNewProject=*/false); + + 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()) + if (m_stack->currentIndex() == ScreenOrder::Gems && m_gemCatalogScreen) { - 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.")); - } + // Enable or disable the gems that got adjusted in the gem catalog and apply them to the given project. + m_gemCatalogScreen->EnableDisableGemsForProject(m_projectInfo.m_path); } + + emit ChangeScreenRequest(ProjectManagerScreen::Projects); } void UpdateProjectCtrl::UpdateCurrentProject(const QString& projectPath) @@ -124,16 +172,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 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, 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/Tools/ResourceMappingTool/manager/configuration_manager.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/manager/configuration_manager.py index b679921196..fc188582c7 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/manager/configuration_manager.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/manager/configuration_manager.py @@ -48,7 +48,8 @@ class ConfigurationManager(object): def configuration(self, new_configuration: ConfigurationManager) -> None: self._configuration = new_configuration - def setup(self, config_path: str) -> None: + def setup(self, config_path: str) -> bool: + result: bool = True logger.info("Setting up default configuration ...") try: normalized_config_path: str = file_utils.normalize_file_path(config_path); @@ -63,5 +64,7 @@ class ConfigurationManager(object): self._configuration.account_id = aws_utils.get_default_account_id() self._configuration.region = aws_utils.get_default_region() except (RuntimeError, FileNotFoundError) as e: - logger.exception(e) + logger.error(e) + result = False logger.debug(self._configuration) + return result diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/resource_mapping_tool.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/resource_mapping_tool.py index e99bf5d441..6a56491b24 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/resource_mapping_tool.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/resource_mapping_tool.py @@ -74,11 +74,18 @@ if __name__ == "__main__": logger.warning("Failed to load style sheet for resource mapping tool") logger.info("Initializing boto3 default session ...") - aws_utils.setup_default_session(arguments.profile) + try: + aws_utils.setup_default_session(arguments.profile) + except RuntimeError as error: + logger.error(error) + environment_utils.cleanup_qt_environment() + exit(-1) logger.info("Initializing configuration manager ...") configuration_manager: ConfigurationManager = ConfigurationManager() - configuration_manager.setup(arguments.config_path) + if not configuration_manager.setup(arguments.config_path): + environment_utils.cleanup_qt_environment() + exit(-1) logger.info("Initializing thread manager ...") thread_manager: ThreadManager = ThreadManager() diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/style/editormainwindow_resources.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/style/editormainwindow_resources.py index fb819cac34..67181fa36d 100644 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/style/editormainwindow_resources.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/style/editormainwindow_resources.py @@ -3649,968 +3649,1082 @@ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ Z\x80d\xdf%\x00\x7f\x12T\x1b\x97qJ\x10\x92\xa7\ \x22:BG\x84z\xfa\x9d{\x88\xac\x1d\xf5-\x8f\xc3\ r\xe1\x95\x00\x00\x00\x00IEND\xaeB`\x82\ -\x00\x00;\xfb\ +\x00\x00C\x13\ \x00\ -\x01\xa2\x08x\x9c\xed]\x09\x5cL\xdf\x17\x7f-\xb4 \ -\xc9\xde\xa2\xc5RD%\x7f\x89HB\x96(;E\x14\ -\xd1\x0fQ\xb4\x92(B\xd6V*d/E*[Q\ -hAE\x08\xadZ\x94\x92\xd4\xb4\xafS\xcd\xf4\xfe\xf7\ -\xbey\xed\x13M\xcd\xd4\xc4\xdc\xcf\xe7+\xaff\xde;\ -\xf7\x9e\xfb\xce=\xf7l\x17A\xd8\x90\xfe\x08ll\x88\ -\x18rI\x18Av\x80\xff\xdb\xd8P\xae\xa5\xb8\xd9\x10\ -D\x04ATT\xf0kU\x04\x91\x1a\xcf\x86\xc8\xc9Q\ -\xae}\xc6#\xc8\x0a\x03\xf0?1\xfc\x9a\x1fA\x88g\ -\xd9\x10~~\xca\xf5\x7f\x9c\x08r\xdd\x93\x0d9\xb5B\ -c\xd1 ^A^p\xebAK\x16/X\x05\xff\x0a\ -\xc1\x0d\x1f\xbdi\xbf\x0fx\xa6\xb0\xde\x92\x05\xf3\xd6X\ -\xa6\x16e\x1c\xb8\xe2\x9a\xe8YR\xa69E\xe7E\xc0\ -\x0a[\xb5\xc9*Zk9\xfb\xb1\x9f\x97\xf2\xd9\xf6\xfa\ -\xbf\xb3[\xcfN\x10/\x1eU5)w\x94\xe4\x0c\x0f\ -\xb9%\x13.]~\x9b%\xad]p\xc1naN\x22\ -\xff\xca[Q*\xe3.}\x9e\xa72o\xdeW\xd7\xbd\ -*\xb6\xec;\x9f\x5c\xcf\xb7\xf6\xf5\xcfmX\x99\xb8A\ -\xc5(qm\xdeL\xcd\xc4\x0a\xab\xb0\xcd\xa4\xb1\xab\xcc\ -\xae\xde)8D0\x8c\x9b\xa90\xf9u\xce\xa0\x1f\x1c\ -96\xabm\x87\x84\x1bg\xe9\xeb\xaaD\x8a\x8e\x5c\xe1\ -\x17\xea\xa7zJM\x8c[X\x83\xf8H4e@!\ -\x9br\x7fs\xa2u\xf1\xba\x8c~\xd3O/\xb7`[\ -Sf\xc9\xf1,|\x9c\xee,\xb9\xd1\xee\xc8Y\xce\xc2\ -\x90\xd8\x09\xcb\xa6\xb0\x17\xf6\x0fB\x84l\x9f\x84O\x08\ -\x8b\xcb\xf0\x0f\xd5\x9d\xa4\xbf\x1c\xb1|\xce&\xf4`\xe9\ -\xe1\x89r\x03\xdcy\x7f\x14\xc8\xb8\xc5\xf0\x16\x85\xf7\xd7\ -\x1d^:)\xf0M\x89\xc9\x22\xb3\x91\x93D\xe4g!\ -\xb3\xbf\xd9\x88(\x8e|:\xc2}\xd0\x9a\x02\x19\x97@\ -;\xc1\xac\x85\xba3\x03?\x82\x0f\x98\x8e\xf4\x09\x93\x97\ -\x07\x1f\x08\xff/k\x9d.\xdf\xaa\xfc\xe3\x9b\xa7\x87O\ -\x0b\x17\x08\x8b{\xb1\xdaB\xcf\xf9\xc2T\xb6\xac\xb2\xfe\ -W\xc2\x13\xc3E\xc2\x07f\xe4\x0e\xb99\xe0\x9e\xed\xf1\ -\xf0kde\x8b\xc5\x16zu\x91\x95\xf0\xaf\xa7m\x9e\ -\xd9\x8c\x1c\xe5\x1d:[\xb52B9\x9ckru}\ -\xfa\xf2\x90\x01\xf1C\xe3\xfb\xab<\xcf\xce\xecG\x92^\ -q\x03\xfem\xfe\x94@\xc4\xf2\xbd\xdf<\xdd\x10\xa4\xd4\ -\x92Cf\xeb\x07Rm\xb8\x0a\xb8\xa5\xc4 >\xb6\xc1\ -\xc8\x00p\xcb\x15\x16z\x02\x1e\x93 -\xb9\xe1F\xb6\ -+Cb%\xec\xd8Cm\xac\xc8\xca5\xbc\x19\xfd\xf2\ -\xf5O\xc3\xef\x8d\xb5\x89>\xf5!s\x80S\xd1:6\ -\x8b:\xeb\xe29!\x03nN\x87\xdd\xe3p_\xe4w\ -`\xcd\xf5\xfa\x1d\xdcz\xd7Gl\xcdH_DR\xb6\ -\xd8\xb0^\xfcq?\x9bh!\xc5\x0b)\xa4\xfc\xff\xe9\ -l\xe7\x8f\xe4L\xe20!)\xd7D\xcf\xfa\xb6)6\ -\xc4\xd2a\x86\xf1\xd8\xc0\xd1\xbe$\xd5e\x1c\x0a\xd1\x04\ -\xbb\xef\x03\xf7\xb0Y>\xb7U\xd5\x1dR\xaa#2R\ -\xda\xee\xed\x14\x8e\xbd\x15\xf5\xe9\x1f\xd4^\xdc\xd0\x83\xfd\ -T\x13\xd88w\xce7\x9f\x9aa\x12\x1c{8v\xd4\ -[\x17g\xa8=\xf7\x10\xd7A\xc0m\xa6\x96\x8eYU\ -{\xfc\xd7\x07\xcb\xac\x17\x96\x9e+5\x0e\x00\xae\xd8^\ -\xb6\xbd\xa0\xe21d\xe4\xb8\x05#\xa48\xd6E\x11\xce\ -\xafZ\xca\x11\x9e3\x88\xc4\x158,f\xa5\xb5\xb8\xaa\ -\xbe\x87\xb0\xaeo\xb4\x10\xd1\xeb\xdd\x8c\x95\xf2\xfd\xf93\ -\xaf\xe4\xe8m5!\xa9\xda'\xccfO\xbf\xb5u\xa8\ -\xf3c.\x95C\xfb\xe2J\xec\xe7\x04\xc8\xda-v\x18\ -\x1f^\x94\xbf\xf9\x7f\xaeI\x88=Z.\xab:)\x8c\ -\x7f\xd2dq\x85\xe5\xeeA\x17UB\xa2\x911j\xe0\ -\x17w\xd3\x97\xfa\x9d\xf7Z\x9e\xca.\xa6\xbcA\xdc\xfa\ -\xb0x\xc2%\xb7\x8f\xb6\x11\x8fM\xd9\xb2\xe3\x87\xab?\ -\xc9\x10\x10\xf7\xcb\x1b\x11\x16G^pW\xd6y?\xf2\ -z\xe4d\xbf\x05\xdeuvr\xab\xb8\x8c=2\xf3\x0e\ -\xcf\xb90r\xc9D$;\xd6*s\xfe\x88Z\xee\x1b\ -\xf3E\xe4\xc6_\xd8\xfe\x8a<\x86\xc3~nJ\x85\x04\ -\xe8\xda'\xaf\x81\x96S7\x8f\xac\x1f\x7fa^\xf8\xc2\ -\x98\xd4\xc3\xe2\xa6B\x9a~2\xd5\xeaa\xecb\x0d\x85\ -\x0fg\x7f\x91\xb6\x9by(8\xac\x9eg\xdc\x1c\xd0\x95\ -\xb7\x06\xd3V\xdc\xae\xbf{\x9c\x7f\xd1t\xb6\xa1.\x19\ -ZR\xbe#T\x0ey\x94L[\x9c;2\xd2\xf0\xb4\ -\x8dA\xb6\xd9\xe38\xe7\x22\xf6\xf7\xf3\x83\xca\x04\x22\xfd\ -\xbc\x9d~l}\xael\xc1\x96\xbd\xd1\xc2h\x91y\xc9\ -\xf0\xc8\xf7\xda6\xd3\xeaBr-\xe5\xa7\xd8D;\xbd\ -\x93\xb5\xfb:\x9c\xff\xde=\xeb/w\x10\xfbk)c\ -O\x9as;\x8d\xde\xf1\x86`\x18p\xb0\x88\xfdu\x5c\ -A\xday\xe1\xe4\xf1\x0b&|]\xe8'\xfd)G\xcb\ -\xcez'[\xba\xed\x9a\xc3\xe2\xf2\x1f\xa4\xb6\x9a\x05/\ -\x9a\x13\x84d/2-\x99\xa7\x91?\x5cb\xa8\x08\xbb\ -Z\xa5\xe9\xff\x0e\x18M\xb1\x09u|'\xbb\xc0[p\ -\xb1\x1f\x9f\xc5\xbd\xb4\x11*\xa4\xe4#\xa2+n\xd4O\ -\xb2\xe3'\x9bs\x96\x9a&\xf3\xfe\xf2\xed'7\x12\xcc\ -\x0b\xfd\x90\xfd[\xcd\x9e\xa9\xeb\x04\xc1\x196\x948\xaa\ -\xf4\x97\xcc\x0b~\x89\xed\x87\xb3\xd6\x84\xc5\x89\xccj\x90\ -\x0d\xd9\xcd\xb6\xe9\x1cx\xda\xc3\xe3'7x*\xcf\xb3\ -`\xfbD\xa8\xb4\xe5\x97\xb8e\xaauG\xf3\x17;\x98\ -\x03SU.\x14\x7f\xe1\x15\xff\xb2\xd7/3\xcf_\x83\ -4X\xe7\x09\xb2\xf7\xb3\xceRq;\x01\xc9\x84\xa2m\ -\xda\xc8\x18\xa3p>ad\x92\x99\xdb\x1an'\xf3\xe9\ -\xe1+\xc0m\xf3I\x83\xa3\x83\x91\x8f\x0b\x01K\xbf\xde\ -u\xde 2\xb7\x80\x88<\xd5\xb0\xe09\xce\xbfTd\ -u\xb4\x839[6\x97\xd5\xe1\xd3\x8f\x11\xbb\x98\x1d\xd3\ -\xc3\x97xr\x9d\x9d\x10\x84L\x10\xbb\xb0\xc5\xdep\xfd\ -\x12?\x7f\x8e\xace\xc43\xeafn~\xd2^\xfd\x14\ -\x9e\xbb\x8d\x0b\x1b\xf2\xd9h\xdd\xe3O\xdf\x9c\x82V\xbb\ -\x1e\x8f\x99\xe0&\x90\xf6\xb5\xde\x1d\x91\xcb=?w\xe1\ -\x01\x85\xbd\x1eN\xd7*\xc3\xac~%\x8eHU2u\ -\xdb\xcc\xbd\xfaR?\x92`6\x9c\xd0\x8e\x03\x96%\xea\ -L\x12\xb7\x13\xf7\x8e\x22\x90>O\xb5Y\xa2\xe2_\xbc\ -\x8aG|\xa0\x93\xba\xdfG\xd1A6\xf9!\xea\xe3\xec\ -\xe4\x12L\x89\xf2\xc8\x04\x82\xcc9\xfe\xc8\x15.\xdfc\ -\x11w\xc7\xc8\x01<\xe2\xf2q\x9cHh\xb4\x90\x14\xa2\ -V\x80\xfde\xec\x9co*\xb6\x22\xa3\xd4\xe7:\xf0\x8c\ -T\xd5\x9f\xac$\x97t\x8aT\xb71\xcaq\xbf\x92\xe4\ -\x05%\x9f\xc9\x8f\xa6=}\xff1k\x95\xd2\xb1\xa5\x85\ -?\x14\xf7\x0c\xcb\xfbj\x9crCQ6h\xd6la\ -\xdb\x9f\xd5R\xdf\xb9\x85w\x9e_\x91&\xec\xe5?3\ -\xd2\xa0j\x08\xe9\xe4+a\x9fs\x81a\x0a\xcb\xa4\xfc\ -\xea\xef.\xe0U\x9be\xfb=D]\xfc\x8b\x91\xa1\x97\ -\xcb\xa3E\x8a\xa3g\xbc~\x9b\x22\xce\x17\xb5n\xd6\xc4\ -\xb7%\x15Z\x81\xd6\x02\xbc\xaf\xa2\xb7\xd8\xf9\xce\x1a\x95\ -}\xeb\x98\xb8B\x11\xcf\x837\xa2\x12\xa3\x9c\x87\xbb\x8f\ -\xde\xfat\xe8\xa8\x1b\xa1\xc3$\xcc8\xce\x14\x1d\xb2>\ -z\xc6D-W\xae*A\xb8\xff0\xdf\x92\xc4\x99\x22\ -r\xde_\x22\xdc\x97re^\x19\xb6Q\xed\xcb\x90\x13\ -{\xea\xe7\xfb\xf9\xf2\x95\xff\xf6{\ -\x83\xd1\xf0|\x99\xfe3\xf9\x14\xad\x9e\xba<:\xa9T\ -\xb6yU\xb8\xd3O\xa9\x80=\xbb\x0dr\x7f\x8a\x11\xcc\ -\xfb\xf3,\x5cud\xb0\x93\xc2|\xcfp$\xc3\xeb\xd7\ -\x12u+\xd7\xbc\xcc=\xeau\x99f\x95\xb7\x8cBH\ -\xfe\xab\xbc\xcf\xbd\xffl~\xdf\xc4M\xa145H\xb7\ -@V_\x22\xf7\xb8\x82\xbf\xe7\xc5\xe8'\xf1\xe5>\xbb\ -O\x95\x97G)\x5c|\x11\xaffvn\xe0\x18\xf7p\ -$\x9e\x0b\xde\xbcf\xca\xee\xa5\xe4\xe3v\xe6\xdb\xfb)\ -\xec\x9d=\xf7\x9aP\x5c\x9c\xc1\xa3\xb9\xaf]\xeem\xd4\ -*\x9b\x17\xfa\x98\xb7r\xb4n\xf4\x0f6\xb1\x87\xf1\x0b\ -\x1c\x86f\xd6\x9c\xd1\xaax\xc4\xbb7T\xee\x1a\xfb\xfa\ -\x83\x5cR\x0b\xaf\x0d\x0c\xe4\x90\xc9\xf0\xe2O\x9a~\xfb\ -\xfdG\xcd\x00)\xfd\x8c\xb8\xd8\xda\x88\xe3\xb5?~.\ -\xe6\x1f\x15\xb3\x9d\xeb\xb4\x99\x9b\xaa\xfdiU%\xd4|\ -\xa1\xf8n\xf4\xc5\x95\xddwr\xf5\x5c\x84\xe5T\x1f\xf8\ -!\xc3\xbc\xcb/\x08(\xa1f:C\xa4\x0fo\x88\xfa\ -1\xa4\xae\xbf\xfe}Uv\x15\xf7/\xa3\xe7{>\x97\ -\x0bpE\x9f\x07\xfd\xfaU\xb0\xc0\xe9\xb0\x00\x12\xf9\xd8\ -P\x81\xf8q\xe6\x01R\x8ed.\xa9\x22_~\x8d\xd7\ -QN\x1b\xb9\x17\x11^\xe6\x17?N\xf9\xbe>y\x98\ -\x93\xe41\xbb\x9d\xe3\xd9\xc4\xe4\xa3\xae\x98Tl8\xb0\ -e\xf4\xb6I\x22!g\x9c\xa7\x8fC\x16\xa8\x8d\x9e\x19\ -\x13\x11\x8b\xae\x8a\xca5x;\xe2b\xbe\x18\xf8\x90\x86\ -ml\xd4\xb6\xe4\x91\x15\xf1\xd2\xbc\x0f\xd4\x1d\xc4\xc6\x0b\ - \x12c\xbf\x1a\x1a\xa7\x1d\xaf\x9d\xfc\xdcwAT\xbf\ -U\xf0C\xa9~\xd3t&\x87>\xb2;\xadZ;\xa9\ -\xe6d\xfat\xa7\xb2\x09\xe7b\xbekZ\x5c\x90\xf3Y\ -\xf3\xf2\xe3[g\xa5\x09\x0f#S\x96J\xc6e\xe8\xbc\ -\xd3\xe5\xb1\xbd\x1d\x93!\xa0\xb5\x8d\xa8\xc2\x15;\x9e\xd3\ -Y\xc2b\xa9\xfe\x85\xef\x1c\x22\xb3\xad\x17\xf9\x9e\xe5\xa9\ -tJ\x1f\xefd\xa66\xd8\xbc\xf4\xa4\xa8k\xcc\x18\x0e\ -\xeb\xace\xe4\xf1\xabf\xecw<&lP\xa7\xec\xb7\ -x\xdc\xa4\x8d\x1aU\xd7\x83fF\x16\xc7\xbd*\x1cd\ -W[\xb9\xd8Ne\xbe\xdd\xab\xe5CR\xe7\x7f\x12<\ -[\x94\xb5\xf3\xd1\x9b n\xd9\xb7\xc1+W\xea\x1b,\ -\xf6\xf0\xbc1\x9cOm\x90y\xd6\xe8,92\xa9\x9e\ -\xd3\xc1%z\xaf\xd4\xcf\xa4\xf1\x83\x1eN\xdc9x\xd9\ -\x10D\x93|\xdd\xee\xc6\xf0\xfc\x8d\xdf\xbe~\x12\xdcN\ -\xd2\xe4#\xbc\x9c\x1c\xef\xe3\xcf\xf3h\xaf\x81\xe3 Q\ -\x8b\xc0D\xd1l]\x9d]V\xc6\xb9\xa1\x86gE\x0b\ -\xf4\xbfpK\xe6\x9d7/K\x5c}\xc5;=&\xe7\ -f\xbc\xce\xfb\xb0\x87\x8e[^&\xf8\x85Jyo?\ -\x8d\xf0\xe7\x07\xef\xd4\xe4\x92V\xe0\xd2z\xe25'\x97\ -\xf7\xc8\xf3$]W\x8b\xb9\xf9Y\x19\x9a\xb36\x103\ -\xef\xff\xf8\x99\xf2Tw\x13\x9f\xdb\xf2\xf7\xb9\xd9\xd36\ -\xfd\xb8\xec\x93o\x1d{\xf6\xd0]\xbe\x13\x07\xf6\xce/\ -\x17\x8a\xd5\xb6\xe5T\x19\xe1kt\xaa\xbc\xb0N\xa1\xa1\ -RC\xc8\x8a=\xca\xcc6U\xe9\xa6_\xaa\xa0~b\ -\x92\xd8\xf1g\x0a\xfeW\x5cc\x07\xe5\xe5^\xfey\xc3\ -\xb3>lmL\x10\x87\xb8\xf9!\xc1/:\xc3\xe2\x9d\ -\x90\x18\x81\xddw\x95\xdc\xeb\xce\xa4\xde\xb2\xb6\xd4\xb9\x94\ -v\x8bTV\xa4\xd0@4\x17\x9a\x16\x15R\xba\xd99\ -n\xdc\x07\xa5\xfa\x1f\xb9)D\xf2\xf27\x87\xc8\x11\xb3\ -.\xa5\xea\xab\x889\xbdg\xd7?\x15G\x18\x9b*\x8d\ -\x96\xad[\xdf\xcf\xa3\xe8^\x9e\x95C\xf2\xa3\x9c\xeb\xdf\ -\xb6d\xeaq\x88\xa4\x1fy\xd2pl\xf5{5v\x15\ -\xa9`s\x03.7\x19\x9f\x9c;q3>\x7f\xba+\ -{{\x87\xe6\xdb\xdc\x87\xa7\xcaG\xed\x93\xe0A\xf4\xec\ -\xff\x1bvU+9\xaa\x1f\x9ag\x9f\x14\xefp\xd8l\ -\xb9\x93x\xde\x84\x1d\xbc\xcf\x86F\xae8)8\xf6\xbd\ -Nj\xb8\xc3\xd5=_\xbel\x9f(\xfb\xb8\xac\xe2\x1e\ -\xafYf\xffxG\x84_{\xf2\x96\xcc\xa8\x19\xa2w\ -\xa7\x99\x06\xdd\x8c\x9f!\x99+o\xfd\xd8p&\x9f\x00\ -\xa2z\xab\xe0\x92\xdczR\xd0\xf9`\x0b\xb3Q\x0d\xdf\ -j\xb6\xac\xb6[\xfd\x92\x0c~\x1fp\x22I*\x13\xd5\ -\x99#k\xb4kF\xa5\xb5\xe6>%\xf7is\xb6\xd4\ -\xe4\xee\x96d\x13{<\xe0xD\xbd\xe9X\xcd\x80i\ -\xe6O\xbe\xcdq\x1b\xb6\xa1\xe1\xd3\x10-n\xd7,\xef\ -\x85\x83\x14\xff\xa7v\xf3\xeb\xcf\xb2s\x0ahm\xca\xd4\ -\xd2\xcc\xb93\xe2\x1ck\xa4\x01!\xc7\x06T\x1c(5\ -\x9a\x12\x22\xab#\xc4c\xc39eD\x80\xcd\x0e9\xb4\ -v\xc2\xe9\x8a\xad\xb1\xa7\xce\x19\xbf:PKj8\xb7\ -NpJ\x88\xea\x8a\x10$\xd2\xf4\xf0S\xbf\xcf\x81\xb6\ -\x11\xce\x87\x84\x82\xdf?.\xde`\x03\xde%\xcf\x15\xa3\ -\xf6\x1ey\xc8\x86\xeam\xf3\xe66\x9a-\x9b\xf9p\xe3\ -\x19u\x84{\xf5]\xa3S\xf2\x16Kn\x89\xe7`b\ -\xe4\x92\xe1\x87\xf1\x91\xf0m\xbc\xbbK5\x1d}8\xf5\ -\xf2\x81\xf4\xd9\xaf.e `\xd4v\x1d9\x14\xfa\xc1\ -\xd1\x80\x0bLs9\xad\x8am\xb1\xb3\x16#\xdc\xf1\x97\ -\xd1\xa3S5\x87l\x9e\x96x0\x03Y\xbd\x8cC&\ -\xf3\x93\x86\xd6\xbc\xba\xfez\xa9g\xb7hM\x1aV\xf7\ -\x5c9\xf2\xc8\xfd\xff~\xda\xeeNP\x8eC\xcf=\xda\ -_\xfb\xe6\xcc\x87\x8b\xda\x0ez\x15\x96\x1cI\xcf\xb4\x9d\ -\x1e\x08\x9b\xc379\xe7\x84\x0f\xf9CH\xc5\xe0s~\ -\xe9\x86\x85\x0f\xf5=o\x0f\x10\x93WUd\x0f;\xbc\ -\x90|\xfa\x85\x96A\x88\xe9\x96\xbb7\xc0k\x1e\xb5A\ -g\xf8\x8f\x0c\xcd\x84\xea\x12\x99Y\x19\x0b\xcf\xcf\xac\x96\ -\x9fn\xc7i\x13#\xe5\xf0\x22\xe9ne\xd5\x00\x93=\ -\xcb\x84vz~\x05\xa4:\xad\x04\x93\xe3\x82\x91\xdc\xc1\ -\x86Q\x04S\x0e\xf8\xcd\x03\xbb\xce\xa5F\x9f\xb8\xe3\xf0\ -@c@\xf8\xb0x\x9b\x1d\xe5\xaf\xd3\x05xM\x12\xfd\ -\x068\xa8}\x0f\x92\x14|\x5c|\xf6\xf2\xa0\xcd\x07\xc2\ -<\x8f\x84=\x5c\xf15\xef\x7f{\xed]\x9c\x0f]\x1e\ -\xaf,\x09Fz\xfe\xbb\xa2M\xa6!!?3\xa3\xe6\ -\x88\x5c$\xae\xf2MJ\x1f\x93[\xb2\xf1\xd8\xfb\xab7\ -w|?\xff\xa1\xbf\xc1\xa0X=\xb3\x0b/3\xce\x9c\ -\x93\xd0\xb1\xe1\x1c:g\xd7\xe5\xc3\x01\xcf\x8d\xde\xe6f\xa2\x16;\ -_\xa6\xefX\xfb\x8e7\xb1\x9f\xcdps#g\xa3\x1d\ -\x9b\xceD\x0e\xda\xf1n\xb8\x8c\xfc\xc3\x10[\xded\xa3\ -\xffI\x93V\xca\x0d\xe0\xb1yz\xf0\xb8I\xd5\xfc\xd8\ -\xed\xa7\xe7]\x10\xcf}h#=\x5c\x00\xd9w-Q\ -z 2`\xf6\xba\xbbg\x0clB\x8c\xef\xef\xb98\ -\xc2f{\x82T\xba\xff\xeeJ\xdd\xdd\x85\xb2w^\xf9\ -D\x14\xb0\xab\xe4:\x1c\xd5\x02\x14\xf8\x92\x02\xd5\x07\x18\ -\xae\xd1\xce\xfa\xbc\xeaV\xae\xa2\xee\x0f1\xb3\xef\x07\xc3\ -\xd95\xf7\xbd\x0b\x96\xd7\xc8\xdd\xe6\x85\x18h\xeb\xbbi\ -\x8d\xf5<\xec\xbc:\x7f\xde\xd5\xc3<\x08\xe1?\xc33\ -\x93\xf6\xf4[6Z\xcf3\xc7\xfb\xac|\xed0\xb3\x9a\ -\xff\x09\x83\xd71\x9f}\xc3;\x99\xe3J5\xa4x)\ -\x1e\x1b\xd3\xcb\xca\xda\x19s2,\x14\xfdL\xf7\xb9\x83\ -\xa7\x8c\xf3\xf1_\x19\x1c\x96\xbeSr\xe6\x85\xbdJ;\ -\xa6?\x9b\xb9\xcb\xa1\xcc\x7f\xd5!\xf6\x07Z13\xe4\ -#N^\xbb[\xffi\x8f\xc2S\xfd\xe7f\xe7=\xd7\ -\x15=\xff\xcb\xc9\xb1\x13\xee\xce|\xff\xe5\xb4\xcc\ -\x5c\xf5\xa2\x17\xe9O\xd7gxp\xec\x90`s\xd4\xcd\ -\xbc!g\xdb\xdf\x8ccn\xbc\xda\xcf\x18S]\xc1a\ -?S\xdd\xcd\xbcC\x92\xd6H\xb1_\xaeY22\xdc\ -\xf9\xdbZ\xcb\xd4\xb3\xaf\xf82\xebv]-V\x7fo\ -\xb9^\xe3\xdd\xdaT\x12Z\xaa\x12#\xefpR\x1d\x11\ -6 \xef}\xb2<\xac\x84\xbcz\x8c\xc6\x81\xff\x89\x8d\ -\x9f\xe1y-\xa0\xb2*\xf3n\xd0\xc7\x99\xe7\x05\x90\xe8\ -\xda\x12\x87\x84\xc3\xc4\x0b\xdb\xcde\xdc\x1e\xd8\x1c\xbe<\ -\xf3\xa3\x97i\xadl\xf8F%\xbf\xcf(\x17\x8f\xcd\xd0\ -C\xdaBz'\xb3'^\x0f\x95\x91Yq\x90OO\ -!4\xcf\x99\xdd+ \x13,g.\x0b\xfb#\xf3\xd8\ -\xaf\xab\xa3\xc4\x15{\xa4u\x13t\xb9\xd6_\x99r\xdf\ -F_\xc7\xf8\xd2\x99G'\xee\xe9\xac\x22;\x14\xcd\x8b\ -\xfc\x1a\x86\x94\x82\xa1\x92y\x1e\xb4\xd6Z\xb4t\x91\xdb\ -p\xc7\xdd\xb6\x15y\xf1o\xf4N\x1e\x09\xbd\xb9\xa2\xb6\ -\xde/\xc5\xcf\xd8`\xd4Pg\xe4Wd\x12Y\xdc+\ -\xf6(\xdb\x89u\x8aO\x8b\xd8\x9f\x1c\xd3\xfa\xfa\xa3.\ -\xbf\xf6\xec\xba\xf4\x1a#\xc33\xe5\xeb.\x98aO\xdb\ -(\xfb<\xd9\x9b4\xa7\xf8\xa5\xe2\xb5\xd5\xe9\xf2\xe3?\ -]~'\xafTc\xad\xb3'S4\xcb\xeb\x5c\x91\xe9\ -\x86mlY+\x05\x90\x0c\xcb\x9d\xdbB\xcb_\xea\xee\ -=\x7f.\xf3\xd0\x1d\xee\xd0J\xce\x83\x95\xd7\x92G\xac\ -\xbcm\x5c\x93\xa9\x11\xe7\xbf^\xb4n\xc7X\xb6O\xe7\ -\xc0\xeaD\x8a\xe3\xab\xfa\x10\x11p\xd5\xf9\x95\xc8\xc8%\ -\xeb\xaf+%k\xc6m\xfb\xa9X\x15\xb7\xc20M4\ -\xfb\x15G\xd2\x06\xb2\xdbb\xf6\xeb\x5c\xea\xeb\x83\x05\xcb\ -\x95k6k\xc7\xe8\x7f\x95\xcd\xe2\xbc3\xd8L\x1e\xbd\ -\x5c\xc0\xd5\x804\x04F\xb9\xc7\xa5\x1a\x8eC\x0e\x06\xee\ -5\xe0\x12\x9dxH5\xd0%\x9e-,\xd4\xd7}\x9e\ -W\xf9>\xd3I\xb2\x8f\x1f\xfc\xfa\xb9u\xfe\x8e\xa5\xca\ -\x01\x0e\xa5\xe5\x13v\x9fk\xb0p6\xda$;\xe2\xc5\ -\xce\xf7\xb2\xc7B_\x7f\x1cj\xa2.\xe58\x8d\xf7\xed\ -\xbe\xf0\xec\x8dKGi\xeb\xbdG\xf2W\xef\xd6\xe6\xd1\ -2,\x0dp\xe1T\x91m\xa8\x9b\xa7\xc4\xb5\xeb\x5c\xad\ -\xf9\x95\xa9\x09o#/\x9f\x9eB\xf6\xb3\xc9\xe8?\xf8\ -\xc3\xc4\x1d\xa7#\x8b^\x0a$*,\xb2\xdcu\xbf\xaa\ -?:k\x09R)1\xecD\xe6\xcb\xd4\xad)\xf5\x1f\ -\x84\xf3\xb2\xcb\x84\x96NV\x9aVz\x99p\x9ax\x22\ -\xdb\x80}\x9a\ -\xb4\xc5\xcaq\x84\xca\x17\xa0\x13\xa5W,\xbb\x15\xb4|\ -,\xd40K\xe6N\xca\xb9\xaa\xe5\xf1\xe4\x9a\xcd+\xbe\ -\x19q1y\xa2se\x92\xd7\xd5\x8a'\x9dsy\x87\ -\x8e_\x94\xffX\x94\xacj!.\xbf|\xb9\x0eg\xe8\ -\xa0=\xe4\xfd'3\xa2f\x8e5yzp\xe9\x98\x93\ -\x97%\xd6\x08).\xfc*:-\xfa\xd9\x86\xfd\xbby\ -\x1eI\xe6e\xef/\x18p\x7f\xbbQp\x99\xd5\xb1u\ -\xe6;x\xf3\xd9+]^\xf1\xc9\xbe\x97\xce1|\xb3\ -qM\xd5\x1b\xff\xb8\xc8\xd9u\xda\x03\xa4\x15\xee\x1d\xb1\ -\x9b9z\xe4\xd5\x9f[\xbf%\xf3\xba\x1a\xe5$\x8e\xbc\ -\xc6\x19\xb9)\xe8\xfa\xc7\xf2\xf8\xc3\xd1`\xad\x89x\xcb\ -!\xecG\xd2\x1b\xed\xd5\xef\xe7\x7f\xa7\x93\x13\x16Z\x1e\ -\xdc#\x1d\xf7\x22\xcd\xbf\xd2\xc6\xe5*\xd8b\xb9\xcf\x19\ -\xef&\xe4\x9dgR f\xe2\xa7\xbf\x8bG\xedY\xc1\ -\x8d\x83w\x87]%\xbb\xfb\x8eCV\xfd\xcf\xe4\xca\x19\ -I\xa3i\xc9_2Vp~\xbb\xcb[\x1e\xfca\xde\ -\xe6\xb4o\x1a\xd6gR\x1d\x143\xb7o\x8bJ\x11\x96\ -\xb9\xec\x1e;>G\xc7[\xe8\xb2{\x0d*X\x1b[\ -\x9ft(5`\x86\x00\xb2\xd38za-\x89(\x14\ -5;\xf8\xaa\x9f1\xa7B\x09A\xb7\xe8#\x9fBq\ -~\xd6\xb1\x17\xe2\x82\xab7\xd5_-y8\xf5\xb0\x8f\ -\xcb\xdb;\x8a\x1e\xca\xa9o\x5cW_\xd1\xae\xaf\x9e\xa3\ -E\xda!=\x98\xf4&e\xed\x15\x8f\x05\xecb\xcb\xcf\ -n\x8e\xbezZs\xf7\xd8B\xd2d;\xd4\xd8N\xd3\ -\xc1\xae\xf6\xa6\xb8\xaa\x8e\xba+Pe\x17s\xaa:I\ -\xc9\xdc9\xb7\xe9\xce\xb9\x87\xaaB\xc5[\x95\xc0j>\ -\xf0\xbe\xb1\xdc\xc1\xc0c@_\xf4\xdeUf\x9a\x07\xd6\ -\xf2\xf4\x06\xfdc\x93\x89\xf9\xb6\x0d\x9f=&\x97\xb9\xe4\ -]\x22.=\x95\xe1Xto\x95\xd9\xaa\xe3\x9e.\x93\ -\xdeo\xf9\xb8\xe6P\xa0\xec\xeb\xe7w\x84\xbc\xcakT\ -\x93\xd8n/\x91\x1a\x18<\xf7k\x0c\xd7\x88\x99k\x93\ -\xdeCE\xe9\xb9\x8fm\x10\x9f/\xf9\x85\x13\xb2\xf8\x16\ -x\xea\xcc\x01\x84\xaf{#>\x8c\xff\xb0\xb5\xe4\x86\x9c\ -\xc9\x08\x85\x15i\x867kW\x0c\x05\xc3\xfc\xc8;H\ -*\xd3\xfa\xf9fmv\xc1\xc29@\xe6.\x22\ -f\xb7\x0b\xbc\x94q\xf8Z\xa8\x97I\xf1\x7fg\x8a\xbf\ -p\x8b\x0f|\xd2\xefe:\xbf\xfe-\xca\xff\x87\xbc\x1c\ -h\xf3$D}\xac\x9d\x5c\x826Q\x1eQ`{\xf8\ -\xf6\x12\x7f\xd2'\x0d\xbf\x8f\xd1\xec\xe1%&\xc1\x1cB\ -\xa6nK\xb9\xf5\xec\x07\x95\xcd:\xf2\xde\xfb\xfa=\xca\ -\xd5-\xb39S\x91\xcd\x14#\xd5\xf9\xa4\xaa\xd7H\x0a\ -{\xc2/\x19\x0f~U_A\x01o\xb2C\x7f\x9b\xcc\ -\xbc\xf3\xec\xb2\xf92\x9e\xe07n\xd9f\x22\x8ae\xba\ -\x8e\x06fn\x1a\xdc7\x12\xa6\x9a\x91\x88\xc8\x86\x93\xa6\ -\x1cv1\xeb\xa6\xbf. \xae\x8c\xe6\x84\x9fF\xe2~\ -\xc9x\xf1\xab\x9e2\xc86#\xf9\xf8W;i\xdf\x0d\ -uU\xe5\x1e\xaf\xe1\x93&\x09\xff\xee\xdf\x7f\x1dA\xc6\ -\x97_\xe2\xfe\xbahB\xf5\xd4\x90\xb4%\xfbo`\xa6\ -\xbd\xbb\xb1\xb32t\x10\xd2\xe8R\x82\xcc\x03~\x89)\ -\xa4\xd7\x05\x95\x8c\xb38\xd1z\xba\xaa\xfd\ -\xedt\xa1\x80\xd09\x05\x15z\xfb\xc0\xbc\x5c\xab{\xb1\ -\xf8\x9e\x80j\xe5\xae\x0b\x99y/\x1f\xcb\xa2\xdb\xe4\x0d\ -\x96f\xf2<\xfe\xf5R^|\x81\x83\xb6}b\xa2r\ -\xbe\x09B\xb4.\x9e\x13~(c\xbd\xa4\xdd\xaf\xcf$\ -K\x9d\x22\x17}m\x04q@\x08f[n\xf0\xdfT\ -'\x9cJ\xfc\xac\xac^\x17\x0em\xe5\x93\x02GK}\ -\xb1~\xa2j\x7f\xeb\x92\xcd\xa9\xc3\xd7\xeas\xf4\x1bV\ ->\x9c\x82d\x1b\x19\x07\x17\xdf\x1b\xae*\x9cS\xac/\ -=\xb7\xf2V\xbfOos\x06\x91\x86\x10\xfd\x8b\xef\x8d\ -P\x0d|_br$`.*\x97?B\xe5h\x9a\ -\xcd\x9e\x90\x80M\xdcg\xd2\xd40\xd3\xf1F\x04\x19c\ -\xa4;\xaet\xcc\x0a\xefz\x19n\xbdyf\xe1\xc3\xc2\ -\xe2^\x84\x86\xc9:.\x1d`C\x14:\x22\xf4\xeb\x93\ -\xb5\xa4]\xa0;_V\x9a\xef\x9a\xc3\x8b\xa7\x22\x08I\ -\xb9\x86\x10^\x14Rr\x82\x7f\x84\x89\xd5\xf3b\xafq\ -\xb9\x08\x1cDM$,\xc1z\x91]\xa0}}\x8e\x99\ -\xcc\xf6\x89\xe7\xd8\x84\xe3r\x06\x11m\x0eh\xef=\xc5\ -\x9fTj\x9f\x99W\xedMx\xf3\xe5\x0b\xfbb\xe3\xc1\ -\x81\xaf?\xa5\x89\xdb\x99M\x16_\x17\xfde\xc2}6\ -\xfe\xb9\xa6\xa5\xe6\xe38\xed\x16\x07\x8f\x7f]G\xfe\xec\ -\xbae\xe2\xbc\x90\x8dpH\xe4\x1e\x1cx{;i\xdc\ -\x00\xf1\x90*n\xe3\x8d\xcf\xf7z\xad\xd5\x8eF\xce\xe7\ -\x95\xf5o\xe0 z\xbc\xab\xe1\x97\x88%\x7f)1A\ -7%\x9e\x91\x1e\x84@\x0f\xc0\xb4R%\xb1EUV\ -;r^F\xe8\xf0\x90QF\ -\x09V\xbcN\xb6Al\x8fV\xeb|O^\xbfS\x94\ -\xb3P\x83t\xf4#\xdb\xc5\xd1UWU\xf5\xdd\xb7\x87\ -\xcfT[{X.\xb0\x9fcL\xce\xa0t\xee\xd9\xa7\ -\x0d'Ip\xbeV\xa8x\x9a\x06\x1e\x1c\xf1kF\x92\ -\x00bljp\xc3\xc0fg\xc8;1;\x1e?\xae\ -\x9f\x95\x06\xd3\x1cL\xe5\xfc\x06\x18?'\xf6\xd3\x944\ -\xffiS\xa80\xd9\xcf\xfbx\x8a\xc5\x91\x10\xffw\x83\ -\x057\x22\xa7\x80\xac\xe4\xce\xda\xf7#\x9dMo\xd0=\ -\x1b%\x97\xaf/\xd6\x0f_\xcd\xa9\x22\x09\xe8\x9fY\xba\ -Q\xd7\xb5\xf8!\xf7\x8dp\x99\xad\x92\xd5~\x9ft\x1c\ -5RC\x10\x9dS\x99\xee\xd3\xec\xe7\x94\x8a\xdd\x118\ -c\xb8\xcf>\xc5\xa2n\xe04@\x93\xd6\x001\xa7h\ -!)\xf6b\xf6\x86\xe1>\x9ft\xc6\x88\xd7\xdd\x18\xb1\ -\x01\x99\x9d\x946\xf1E\x88\xbb\x022\xeb\xcd1]\x99\ -\xac~'\xf2e\x1c\xf8\xad\x03\x07\x97\xb2\x9d\xf9\xa0]\ -\xa6\xe4\xa1\x8c\xcc:\xf0\x9cM\xd2x\xba1\x97\xdc|\ -\x8b\x03v\xbf\x22wm]\xb4\x1f\xfdZbr\xd2t\ -\xa4\xb7\xac\xfc\x04D\xe0\xc0s\xdb\xcf\xe1\xf7\xc3\x85\xaf\ -\xaf\x02\x7f\xf7J\xe4\xdf\x18\xf7\xf0u\x89II\x82\x8e\ -\xbb\xfev}\xc4\xb4\xdc\x92\xc3$\ -\xa3_A\xc0Y>\x9b\x05c\x8c\xc2\xfb/Cd\xc6\ -\x07]\xd5\x9d\xe4x/t\x93\x84\xf4c\x0e!\xfb\x0f\ -\x99a\xbc\x86Kj>\xce\x0e\xd1_\x8e\xc0\xb9\xf1\x83\ -\xdd\xc5\xfe\xbe\xc2\x95e\x93\x85\xd5\xb6>\xf4\xd5\xd4\x9b\ -5DwF`\xbf\x14n\xbf~\x0a\xb5\xd6\xc5\xe6\x19\ -\xfdV\x8b\xef\xe6\xb8.=\xc6HLt\x14\xd7\x15\xde\ -\xb3\xc8\x09\x0eI\x0e\x0e\x81\x86\xa76[3\xb6\x0f_\ -\xfa?\xf6yY\xc6\x91\xd7\xc8\xe1\x9bu\x07\x97N\x0d\ -|\x05\x84T\x81\x8c\xcf\xca\x86\xb9b:\xff\x138\xa9\ -\xb8}3\xf4v.Y\xa8\xb1\xc0_U\xf7\xa8\x14\xf4\ -vR\xdc\x9f\x98\xc7\x12\xc1}\xa1=\xd1\x08Fc\xa8\ -a\x08\x80\x0c\xc0f\x00{\x00?\x80w\x00\xd9\x00\xe5\ -\x00$\x00\xf4\x1f\x01\x09\xefs6>\x06~\xf8\x98l\ -\xc6\xc7h\x08\xb51d\xe6\xd6\x01\xcf\x87\x01,\x008\ -\x06\x10\x0a\x90\x03P\xc9\x04\xe3\xcf\xac\xa8\xc4\xc7(\x14\ -\x1f\xb3\x05\xf8\x182\xed\x5c\xa0B\x1b'>\x87\x0f\x02\ -D\x01\x941\xc1\xb8\xf6U\x94\xe1cx\x10\x1fSN\ -f\x9a\x07T\xf8>\x03\xc0\x15\xe0;\x13\x8c\xdd\xdf\x86\ -\xef\xf8\xd8\xceh;\x0fz\x99\xef\x10\x13\x01\x1c\x00r\ -\x99`\x9c\xfev\xe4\xe2c=\xb1-\x1fz\x81\xf7|\ -\x00;\x01\x92\x99`\x5c\xfe5$\xe3c\xcf\xd7Ss\ -\xa0\x0d\xef\xa7\x00\xf8\x00\x10\x99`,\xfeU\x10q\x1e\ -La\xf4\x1chq\x7f\x0e\x80\x95\x00\xf1L\xd0\x7f\x16\ -(\x88\xc7y\xc2A\xef9@h\xfd\xces\x03\x18\x01\ -\x140A\x9fYh\x8d\x02\x9c7\xdc\x04:\xca\x82\x16\ -\xf7\x1a\x08p\x14\xa0\x8a\x09\xfa\xca\x02uT\xe1<\x1a\ -H\x0f\xfe\xb7\xe1\xfd)\x02k\xad\xef\x0b \xe2\xbc\xea\ -\xd6\x1c \xb4\x96\xf9GY\xbc\xefS\x80\xbc:Fh\ -\xb1\x16t\x91\xf7P\x9f\x80k\x0aK\xe6\xf7=T\xe1\ -\xbc\xa3I'$\xb4\xd6\xf7\xa0N\xc9\xd2\xf5\xfa.\x0a\ -p\x1evZ\x1fl\xf1Y\xb8\xa7d\xed\xf1\xfa>\xe2\ -\x09-\xec\x03\x9d\xe4=\xb4)\xf90\x01\xed,\xd0\x07\ ->\x84\x16v\xc2N\xc8}hWd\xe9{\x7f\x0f\x88\ -8O;\x5c\x07Z\xfc\x0d\xfa\x15X\xf6\xfc\xbf\x0f\xc9\ -\x84\x16>\xa3\x0ex\x0f\xfd\x8a\x0eL@+\x0b\x8c\x81\ -\x03\xa1\x85\xef\x98\x0a\xff\xa1o\x99\xe5\xc3\xfd{\x91\x8b\ -\xf3\x98\x1a\xff\xe1\xbcpe\x02\x1aY`,\x5cq^\ -\xb7}\xf7a|\x11+n\xe7\xef\xc7w\x9c\xd7mu\ -~+&\xa0\x8d\x85\x9e\x81U\x1b\xde\xc3\x18\xd3(&\ -\xa0\x8b\x85\x9eA\x14\xa1u\x5c\xf1B\x02+N\xf7_\ -B\x19\xce\xf3F\xfe\x1fc\x02\x9aX\xe8Y\x1c#4\ -\xe7\xe5\x842\x01=,\xf4,B\x09\xcd9Y9L\ -@\x0f\x0b=\x8b\x1cBs>\x1e+'\xeb\xdfC%\ -\xa19\x17\xb3\xb7i\xe9!\x88\xa0\x84\xbd\x10\xc2(a\ -\x0f\x84\x10\x0e\xc1\x16\x10j\x06\xfc\x1c\xfc<\xfc^\xaf\ -\xd3\xce\x10@\xde\xdfg\x02:\x18\x07\xc8\xbfF~\xee\ -\x97@\x0b\x0f\xc8\xa0\xc5Gg\xa3\xa5\x8e\xab\xd0\xb2+\ -\xdb\xd1\x0a/\x13\xb4\xe2\xeeA\xb4\xc2\xef0Zq\xcf\ -\x0a\xad\xf06A\xcb\xae\x1a\xa0\xa5\xcek\xd1b;e\ -\xb4\xe8\xa0,Zh2\xae\xc5\x9c\xf8\xab\xe6\x02\xe4\xfd\ -;&\xa0\x831<\x07\xff/\x04\xfc+uZ\x85V\ -\xfa\xdb\xa251\xbeh]\xe6\x07\x94\x5c\xf2\x13m\xa8\ -*E\x1bj\xabQ\x94T\x87\xa2\x0dd\x80\x06\xcaO\ -p\x0d\x7f\xdfP]\x8a\x92K\xf3\xd0\xfa\xef\x9fPb\ -\xec}\xb4\xf2\xc1q\xb4\xd4u=Zd5\x15%\x18\ -\x8b\xfe-s\xa11\xff\xbe\xb7\xe9\xa0+\xdf\x0b\xcd\xa5\ -\x00\xcfW\xa3U\xcf\x5c\xd0z\xc0\xef\x86\xaa\x12\x140\ -\x17\xednk\xa8.C\xeb\xb3\xbf\xa0\xd5/=\xd0\xd2\ -\x0bZh\xa1\x854e\x1d\xe9\xbb\xf3\xa0\xb1\xf6Bo\ -\xd3A\x07\xbe\x0bc\xb2\xbd\xfc\x86!Z\x1b\x1f\x82\x92\ -+\x8b\xbb\xcd\xef?\xcd\x85\xda\xe4\x08\xb4\x1c\xac\x1fE\ -V\xf2-t\x05&\x18\x8f\xce\xa3\xef\xd7\xdd\x00\xe3^\ -h>\x11-\xbf\xbe\x0b\xad\xfb\xfa\x1am\xa8\xaba(\ -\xdf\xdb\xcd\x83\xfaZ\xb4.\xe3\x1d\x98\x07\xfb\xd1B\xcb\ -\xc9\x94y\xd0\xdbc\xd2y\xf4]\xdec\xef\x9a(Z\ -r^\x13%~|\x04\xd6\xec\xaa\x1e\xe5{\xbbyP\ -G\x04r'\x14-uY\x07\xf4\x03\xb1\xbe(\x0b\xfa\ -\x0e\xe0;\x0f\xd6\xde\xca\x80\xa3(\xb9\xf8G\xaf\xf2\xbd\ -m#\x97\xe5\xa3UO\xce`kQ\x1f\x93\x05}\x03\ -`L\xe1\xbe\x8c\xf8>\x00E\xeb\xebz\x9b\xdd\xd4\x1b\ -\x99\x84\x12?\x07\xa3\xc5'\x17\xb0\xe6\x00]y/\x02\ -\xf6\xed+\x81N\xff\xbe\xb79\xdc\xa9V\x9f\x13\x0f\xf6\ -\x8c\x1bXk\x01] \x82\x96\xb9oFI\xf9\xe9\xbd\ -\xcdV\x9a\x1a\xa9\xf0;fg\xea\xfd\xf1\xeb\xc3\x00\xef\ -O\xd9%=\x94T\x94\xdd\xdb\xec\xecR\x83v\xa4\xb2\ -\xab\xff\xf5\xfe8\xf6E\x80\xf5\xb3\xf4\xa26\xf6\x1e\xd1\ -\xbd\x81\xbd[CM9\xdaPY\x8c\x92+\x8b\xb0\x9f\ -\xd85\xf8=\xbd\x1b\xb98\x17-\xf3\xd0e\xe9\x034\ -\xf2\xbe\xe4\xccR\xb4>7\x99.Vz\ -qS\xef\xc9R8\x07\x5c\xd7\xa3$\x02\xed6\x8a\x9a\ -W7)\xbaio\x8f}o\x03\xfauN\xcc\x03c\ -\x98E\xd3\xf8A\x1d\x91\xa2K\xf5\xb2.\x0d\x9e\x0fc\ -O`\xdc\x18-\x0d\xc6\xa2\x95\x9c^\xcc\xd2\x03@\xff\ -a\x8c\x1dM\xeb>\xf8,\x94\x17\x84\xfdc\xd1\xde\x8f\ -\xd1\x15\xc1\xde\xe3\xaaPWJ\x1c!\x0d\xad*\xf8|\ -\xef\x8f\x7f\xaf\xf2^\x04-<0\x05\xb3\x8d\xd0\xd2\xea\ -\xb3>\x82\xbd\x82\x22\xf3\xbc;\x98\xcdJ\x01\xad\xcb\x88\ -\xa5\xad\x1f`\xafStH\xbe\xf7eXo\x01\xec\xd3\ -`\xdc\x0c-\xb2\x13\xc6yA\xfb-\xd3\xf0\xbe\xa9/\ -\xc2X,\x1a-6\xe3\x06b%\xb6\x8f`\xd8~\x95\ -\xd9\x01x\x08\xed\xf1\xb4\xb4\xba\x94H\xb4\xd0r\x0a\xf3\ -\xbd3P\x96YLBk\x13_\xd0\xd4\x9f\xea\xe7n\ -\xbdO{o\x8d\x97\xf9D0^/;?X\xa4:\ -\xb4\xc2\xdb\x94y\xdf\x17@W\xf9\xcd=hC}\xe7\ -\xed\x83u\xe91x\xfc(\x93\xcdg\x86\xf3_\x18\xcb\ -\xc7\xa1\xc5\xa6\x0e\xed-L\xedC\x01<,\xb2\x9e\x8e\ -\xd6\xffH\xe8t\x9f`\xdc`\xb1\xfd?\x18/\x06\xde\ -\x15\x18\xd7\x03\xd7\xc0\xce\xb6\x9a7^\xcco73\x16\ -E\xab\xc3\xaft\xbaO\x98>sm'\xf3\xca4\x86\ -\xf1_\x10\xad\xf4;\xdc\xf9}\x1f\x90\xfd0\xd6\x9e\xe9\ -\xc7\x09\xae\x01\xd0\x87EC>B\xd5\xa3S\xcc\xdf/\ -\xbaC\x84&\x9b\x1f\xb9\xa2\x08\x8b\x09az9\x89\xd9\ -\xb3Ti\xf2\x0f\xc1\x18\x03\xc2>\xb1\xde\xa7\xbd\x07y\ -\x0fc,`~eg\x1b\x8c\xc7):4\x8d\xf9\xf5\ -$\xa8\xd7\x1e\x94\xa5\xc9\x8fY\xfb\xe5)Zh&\xc9\ -\xfc}\xa3\xe7\x18\xd1\xa8\xfbc\xfb>\xb0\xbfb\xfe1\ -\x12\xc1xY\xfb9\xb8\xf3}\xc3\xf6\x00L\xb8\xa7e\ -$\xff\xc1\x9e\x07\xf6\xbb\xb3\x8d\xf8\xf1!Zh:\x1e\ -\xed}{o'\xb0O\x02\x8b;\xebl\x83\xf9\x02E\ -Vr\xff\x16\xff\xa1\x8c\xcc\xfa\xd8\xe91\xaay{\x97\ -I\xec\xfd\x9d\x00\xd8\x03\xd4D^\xeb<\xff\xb1\xb5\xed\ -\x1f\xb2\x03cv\x7f\x19\x9a\xf2y\xfej\xfe\xffL\xf9\ -\xf7\xf8\x0f\xd6;\x96\xfc\xc7\xf9\xff/\xca\x7f\xa8\xff%\ -t\xdeVN\xd1\xff\xa4\xfb\xc0\x18uE\xff{\x8b\xf9\ -A\x99\xbfot\x1c#\x9a\xf7\x7f\xa9}l\xff\x17\xd7\ -\xe9\xbe\xd5~y\x06\xe6\x8c\x14\xf3\xf7\x8d\xce\xe3T\xfd\ -\xc2\xad\xd3c\x04\xf3\xb4`\xfcu\x9f\xb0\xff\x1c\x9fG\ -\x9b\xfd\xe7\xcdm\xe6\xb7k\xd3\x1b\x98\xfd\xd7\x9a\x06\xfb\ -o=s\xfb\xfe\x9a\xfa\xd5\x05\xfb\xef\xe3\xd3\xcc\xdf/\ -\x06\x8cS\x99\x9b\x0eM\xf1\x12\xb0\x8e\x1b\xd3\xc7LB\ -\xffO\xd8\xe5N\xf7\x09\xe6\x92\x94_\xdf\xfd\xef\xf1\x1f\ -\xfaJ\x8f(b6@X[\xad\xfe\xfb\xe7\xdf\x03|\ -\x06\xe6o\xc2\xb5\x95i\xd7I\xcc\xff\xfb?\xa0\xcf\xd3\ -\xe0\xff-/@KN\xa91\xff\xba\xc6\x08\x80w\x05\ -\xee\x03a\xad\xcd\xce\x00\xab\xad\xc3\xcc\xeb$\x94\xfd0\ -\x16\x98\x86\xfc0XC\xac\xcf\xea\xfe{E:\x07z\ -\xdc\xa33\xf7\xea\xe5\xb1\xc0\xe2\xbf\x12\x9ew\x9a\xf7\xb0\ -aq\xccF\xa2\xbdO?\xad\xd8?\x16\xeb\xef\x1fa\ -.E\xc9\xcb\xeamz\x19\x8d=x\xee*\x91\x86\xf8\ -O\xa0\xfb\x94]\xda\xda\xf7\xd6~\xa8\xbb]5@k\ -\x93\xc3\x7f\x8f\x94\x08\x94\xf8\xf6.^\x13\x8d\x89\xdf\xdd\ -\xee\xa2\xb1fA\xc6[\x9a\xde}\xa8\xd3\x14Y\xf7\x01\ -\x9bF[\x18\x0aRr6:\xd1`|\x1f\x1c\x9b\xbf\ -W\xbf\x11\xc1\xec\xbdU!.\xb4\xe7\x7f\xc8\xd4v\x8c\x9e\xe2\xbf\xef\x01\ -\xacfnW[\x8f\xc7\x84\xc0\xfa/fR\xd8y1\ -\xb0\xc6TW\x1a\xe9g\x0aZ|\x5c\xa5\xf7\xd7\xae\x7f\ -\x81\xff\xf4:\xc3\x09\xa3]\x14\xe8kK\x80\xcc\xb9\xdf\ -\xe5\xb3\x06\xe0\xf92\xf0\xdc\xa1>\xb7\xdf\xeb\x8b\xfc7\ -\x16\xc3\xce\xec\xc1b\x86\xbaT\xffM\xa4\xb9\xfe\xdb\x89\ -\xf9\xd8^\xad\xbbu\x00a\xfeR\xa1\xe9_\x10\xe7M\ -\xcb\xfe\xbf\xf8G/\xf0\x9f\x12_\x02\xd7Y\xe83\x82\ -6\xf9b[%\xbc\xf6\x9ep\x8b\xf3\xde:Q\xff\xf1\ -\xd5M\xba\xd4\xa0\x85\xb1nE\xb6\xb3\xfa\xb6\xdco\x04\ -V\xf7f\x03\x16\xe3\xf8{\x5cG\xab\x82\xceQ\x8f\xdb\ -\xea\x01\xfeC\xde\xc3\x06\xf3q\xe1\x1e\x8d\xf8)\x08\xab\ -1V\x0e\xcfzs\xdf\x8c\xd5\x94\x87{\x10\xa8\x8b\xc3\ -z\xb0\xad\xea\xbf\xfe\xfcJ\x93-\xf7w\x0d\xd6.\xc7\ -\xf4\xfd\xbf\x81\xf7-\x01\xed\xfa\x9d\x01\xb5\xef\xf6\x04\xff\ -\xe1Y\x11T\x19R\x87\xe5\x9d\xc2\xf3\xc1\xe0yQ\xf0\ -'\xc3\xea?\x97\xe4br\xa4\xcf\xcb|z\xa37\xf9\ -\xdfC\x0d\xd6\x7f/g\xd5\x7f\xff'\xf9\x8f\x9d\xff\xe0\ -\xc9:\xff\xe1_\xe4?\xf4\xeb\xb1\xce\x7f\xf9\x07\xf9O\ -\xae\xc7t\xcc\xe2\x93\xf3\xff>]\x8f\xc5\xff\xdf6\xe8\ -\xcf\x83{\x07\xd6\xf9o\xff\x16\xff)\xe7?\x86`\xe7\ -\x86\xf7\xb1\xf3\x1f\xe9|\x06(\x8d1{}\x9c\xffM\ -\xe7\xbf\xde\xde\x87\x16ZJ\xf7\xb5w\x1e\xf2\x9e\xae\xe7\ -?C;+\xcc\xe9o\x07X\xb3\xc1\x98\x8a\x8f\x96.\ -\xfe\xbf\xfb\xbf\xe1\xffx\x94\xf8!\x90N\xdcnn\x94\ -\xf3\x9f\xc3\xb1ZD\xd8\x99\xf0}\xf7\xfcg:\x9e\xff\ -.\x82\xf1\x12\xda8\xebR\xdf4#-\x0a%\xc6=\ -\xc6b\xe2\xdb\x8d\x11\xf4\xff\x9fU\xc7\xec\x83U\xc1\x0e\ -\xb4\xe3\xa9#\xa5\xe6?U\xff?\xe5\x1c\x19Zrp\ -\xff\xc4\xf3\xfa\xec\xcfX\xdd~\xa8\xd7\xff%\xe7\xbf\xbf\ -\xa3'\xff;\xcay\x81\xe7r\xc0:\x7f\xd4\xe3\x7f\x84\ -1_B\x97\xf1\xbb\xf8\x09cQ\xb4\xd8~!Z\xe1\ -g\x8d\xcd\x03(\xab\xa1/\x02\xb3\xf5\xc1\x9c#R\x1d\ -e\xed\x81\xf9g0n\x0f\xda\x04k\xab\xb1Z\xb4\xd0\ -n\x03\xeb\xf7\xc0\xfcT\xe8\xe7\x801>\xd8\xbb\x0e\xe3\ -\xb41_R\x9f\xe5{# \xef\xef\xd3\x95\xff\x1d\xd4\ -\xf2\x82zq\x87\xfcg4\x9a\xcep\x92\xc0r,\x8a\ -\x8f*av\xf8\xb2\xcb\xdb\xc0\xba\xbd\x1f\xd3?\xe0Y\ -.\x15w\xad\xb0\xdc\xc22O\x03\xa0\xc7\xadA\x8b\x8f\ -)S\xf2M\x80\x0ci\x8e5\xeb\xf3\xd2\xea\ -\xeb\xd8\x0dJ[^\x0fF\xd1\xac\x96\xd7\x5c\xadn\x8f\ -=\xa0-9m\xae\x1bZ_\x1f!\xb5\xbe\x9eKl\ -}-Z\xda\xfazp\xdb\xeb\xac\xd6\xd7\x5c\xe1\xad\xaf\ -9\xda^\xdb\xb4\xbef\xfb\xd35\xc2j\xac\xc6j\xac\ -\xc6j\x0ci6\xad/\xff(\x8f\xc3[_\xb7\x93\xef\ -Y\xad\xaf\xb9\xfe\xb4~\xb4]o\xda\xaeGm\xd7\xab\ -?\xado\xed\xd6\xc3V\x04q\xb5_O\xdb\xae\xb7m\ -\xd7\xe3\xb6\xebu\xbb\xf5\xbc\xf5z/\x05~\xa8 \x94\ -qeC\xc4(\xbfWAhj\x1d\xe8zj\xb8N\ -\x13\x86\xeb9\xd5\x00\x0d\xbd\xa0k5\xe0\xcf\xce\xc6i\ -\xb1\xc7ik\xa7c\xd1\xda\xa8\xf4[\x08\xc0\x10 \x02\ -\xa0\x94\x09\xf4\xcc\x8eP\x8a\xd3h\x88\xd3L\xf38\xb4\ -\xf9\x0e/\x80.@\x5c/\xf1\xb8;s#\x0e\xa7\x9d\ -\xb7\xb3c\xd0\xa6\xef\xe2\x007\x01j\x99\xa0?]E\ --\xde\x07\xf1?\x8dA\x9b\xbe\xcb\x13\xfe\xae\xbc\xe1(\ -\xbcOT\xc7\x80J\xdf?1\x01\xcd\xf4\xc6\xa7\x8e\xc6\ -\x80\xd0z\xce\xffM|\xa76\x0f\x9a\xde\x856}\x87\ -r\xe2&\x13\xd0\xc8h\xdc$\xb4\x91\x898\xa0\xac\xec\ -\xcb\xb2\xae\xb3\xa8\xc5\xfb\xdav}\x8fc\x02\xdaz\x0a\ -q\x84\xd6\xfa\x01\xd4\x17\xfa\xd2\xfa\xde]4\xe0}n\ -\xd4i#\x98\x80\xa6\x9eF\x04\xa1Y\x9fgf\x9d\x96\ -Q(%4\xefez\x9b\x96\xdeB\xe3>\xae\xe7\x9e\ -\xb9\xb7E}Fx\x0dkG\xc1\xb3R\xe19R-\ -\xcfn\xeb\x99\xbaB\xb0\xeft\xac\x93\xdcQ\x9f[\x9c\ -5xT\x09\xabOZ\xf9\xc8\x1e\xady}\x0b%~\ -x\x88\x12?=\xc1\xce\xba\x85\xf5\x87+|-\xd1R\ -\x87\x95X=SJ\x9dJ\x86\xd6\xd5k\xdc\xbf3\xee\ -\x19\xf0\xbc=\xf3\x89X\x9f\xb1s\xd7\x8a\xb2)\xb5y\ -\x7f\xd3\xe0Y]\xf5YqX\xed\xe8\xe2\x93\x0b\x9a\xe7\ -\x0d\xfd\xe9c\x9c\xed\x02\xf2\x1c\xcc\xed\xd2\x8b\x9b\xd0\xda\ -\xa4\xb0.\x9f)J.\xceE\xabB\x5c\xd0\xa2#3\ -\x18q\x9e4\x83\xfa.\x8c\x9d\x93\x01\xcf\xf1\x86\xf5\x96\ -\xe9\xd1`\xbdfx\x86\x1f\xe5\x19L\x5cs\x0c\xf4\xbd\ -\xc8F\x11;'\x86\xde\x0d\xd6\xda\x84u\x8c)\xf5\xf6\ -\x98p\x0c`\xdf\xc1<\x85\xf3\xbd3\x0d\xd6\xe3&W\ -\x14\x82~\x15\xa0\x0dU`\x9e\x90\xea\xff\xfc\x1d \x1b\ -*|,z\xbf\xaf\xed\xfa.\x82\xd5Cl<\x93\xb2\ -C\xfa\xabJ\xd0\xda/\xcf\xd0\xca\xfb\x87\xd1R\xd7\xf5\ -h\x89\xbd\x1aZ|B\x15-9\xbb\x0c-\xf3\xdc\x81\ -\xad\x01\xf5?\x12\x01\xb3\xc9\x1d\xdf\xa3\xb2\x18\x93\xa7\xcc\ -Vs\xb5\xf2\xe1IJ\xaduj\x0d\xc8|b\xdc#\ -\xb4\xd4iu\xeb\xba\xdbMg\xb4\xe2\xf5\x9a\xc18\xc2\ -z\xa2\xb0\xae;\xa9\xe0[\x87c\x00\xcffg\x9as\ -\xb6\x01\xed\xf0\x9cQrY~\x87<\xaf\xf4\xb7E\x0b\ -\xcd$;'\xc31}A\x18\xabo\xff\xbbw\xa9&\ -\xca\x0b%\xec\x97\xe8\xfd\xfe\x03\x1a\xe0Y-T\xfb^\ -]\x86\x9d\xa5\xd1\xd4/\x1a\xc7\x15\x9e\xd3[\x9b\x10\xda\ -\xe1\xbda\xed~\x06\xac\x8b\xb4\xf1\x1e\xbc\xbb\xe4\x8a\xa2\ -\xf6\x04\x92I\xd8\x99Q\xd8\xb9\x02]\x95\xd7\xe0\xfe\xc5\ -v\xca\x14\x99@m\x0e\xc4\xf8R\xf4\xe7\xde\xea?x\ -\xff\xaa\x82\xcfS\xa5\x0d\xd6\x0b.\xb4\x9c\xd2}]\x1e\ -\x8cA\xf9\xf5]Tu(\xa8\x1f\x15\xdb\xcd\xed%9\ - \x82\xe9\xb6\xf0\x5c\x94vs\xb3\xbe\x96r6(=\ -\xe6&\x5c[\xcc\xa4\xa8\xcb\x02 o\xcbo\x1b\xf7\xce\ -;\x00\xc6\x1c\xae]\xd4\xe4^}N\x02Zd%G\ -\xbf}\x1c\xe8\x1f\x5c\xf7\xb1\xf3<\xda\xbe\x03\x91\xd7\xd1\ -^\xd1\x87\x00Me\xee:T\xcf\x15\xaeys\x9b\xce\ -\xcf\x12FKN\xa9a\xfaR\xbb\xf7,%\x02;\x9f\ -\xa9\xe7\xfb/\x08\xf4Q\x13\xaa<\x81\xeb\x1d]\xe7$\ -\xd4\x0b\x0e\xca\xa1\xf5\xb9I\xed\xe7\xda\x8f\x04\xb4\xf0\xa0\ -l\xcf\xd7\x22\x06\xfd\xaf\xbc\x7f\x84\xfa;y\xcb\x98\xa2\ -\xcf\xd0\xedy\x14\x19@M\xd6\x90\xf23\xa8\x9f\xc1\xc4\ -\xea?\xc3\xfb\xdf\xe1\xfc\x0f\xe8\xd9\xf9_\xd4+\xf3\x1f\ -\xc8?7\x1d\xaa\xf6\x1cx&\x13E\xef\xa1\xd7\xb3\x84\ -\xb1\xbd\x12u\xf9\x17\xd9;\xf2\x0f\xae\x7f@7\xab\xcb\ -\x88\x05s0\x1d%\xfdJ\xa3\xa0 \x03;\x17\x0d;\ -G\x8c\x9e\xeb\xdf\x1ds\xea\xeb\xdf\xab\x1b\x0c\xe4q\xa3\ -M\xb6\x05Z\xeaZ\xc6b\xd8:\x0f\xf7lE\x87p\ -\xc0\xff\xc3\xf3\xb4\xe8\xa5\x97\xfeQ\xff\xd9\xcf\x18\xfd\xc7\ -X\x14\x9b\xdf\xd8\xf9Q\xf0,)\x88\xbb\x07\xd1\x923\ -K[\xf3\x95\xd1u\xf1\xa1\xfe{m'u\xfd\xb7$\ -\x97q\xfb`\xc0?h\xabh\xdb\xaa\x9e\x9ci\x1eo\ -l\xaf*\xf8\x1bt\x93/p\xffsL\x19\x93q\xd4\ -\x1a\x11\x9e\x11\xca\xa8\xfdO\x87\xfd?\xdb\xe4\xa3\x80v\ -\xce2\xf7\xcdh\xd9%=*\xd8\x8a\x96:\xae\xec:\ -}p\xff\x0b\xd65x\xfe,\xb5\x86\xed\x7f]\xd73\ -N\xf7\xff\x13\xff\x01J\x9d\xd6`t`g\xedA\xfb\ -]K\x00Y\x05\xd7k\xec\x8c\x90&\xfd\xbc\x13\xe7n\ -\xe1\xfe\x92b\xfb\x05\xe0\x9d\x7fI\xb5\xef\xb0\xd5D\xdd\ -a\xac\xfd\x03\xf6\x9f\x8a\xed\x01\xfa%\x9a\xfa\xef\xbc\x96\ -r\xde`\x07\xad\xee[l\xab\xfeC\x1bP\xa1\xe9\x84\ -\xf6\xe7\x91\xb5\xb4\x7f\x01\x19Zq\xef\x10\xa6\xd7t\xd4\ -Hy\xa9\xe0\xbd\x9f\xc7\xd8}/\xe8\x7f\xd53'\xfc\ -\xdc\xcf(\x0a\xc0\xff)\xba]'\xfb\x9f\xd1\xa2\xffP\ -_\xf0\xdc\x0e\xe6s(\xb6?(\xbd\xa0\x05\xf64\x8b\ -\xb0\xf3\xd4K\xce5\xda?\xddp\xfbg\xc7\xe7\x06c\ -\xf6Ox6h\x0f\xec\xf9\xb1sop\x9ea\x806\ -\xbc\xc69Gs\xff\x05\xb1\xf3\x84\x9b\xfa\x01\xe49\xb9\ -\xb2\x08\xb3\xebc~\x92N\xdb\xbf-\x19\xde\xef\xd6\x10\ -i\x83f\xf9D{\xff\x8d\xa9\xea0\x9diL\xe7\xff\ -\xe8\xc1\xfeC\x7f(\x83\xfc_\x9d\xf3\x01\xb6\xfa\xcc\x1ea\xccw\x00i\ -\x86\xf1\x81\xe4\x92<\xea>\x7fR\x1dJ.\xfa\x81\xe5\ -\xcb\xc2\xf83,_\xaa{\xfc\xe8>\xed\xe0\xf9%\xa7\ -\x97\xa0\xb5\x9f\x82\xd0\x86:b\xa7\xfd\xb7\x0d\xc4J\xcc\ -\xb7\xd7\xbb\xb9Z\x22\xd8\x98\x93\x08Y\x9d\xa6\xbbm\x83\ -~/,\xd6\xaa\xa7\xfb\x00\x9e\x07\xe3b`\xec\x03\xd5\ -\xf1\xad\xa9\xc0b\x81\xb0\x18\x9d\x8c\xb7X\x1cjG\xf1\ -\x080\xf7\x9a\x92/\xd5C}\x801\xbbg\xd5QR\ -av;Z`\xceHu\xd8%,\x1f\xb0\xe8\x90<\ -%\xc6\xc6L\x0a\x8bw+u\xdd\x80\xe5\xc6\xc3\xbc\xb9\ -v|\xf8\x91\x88\xc5\xdc2\x9c\x0f{)\xb9=\xd4b\ -\xef\xea\xd2\xdfb>jJ\x1f\x85Z\xf8\xc4D\x9a\xfd\ -\xbbX\xee\xf7F\xaaq\x8b5\xd1>\x98\xfcb\xec\xd8\ -\xc3\x98\xd1]\xedb3\xea\xd2\xa2\xb1\x1a\x04\x9d\xf2_\ -\xc3\xdc\xb6Sj\xedr\xcf`|\x02\xe4\x11#s\x8e\ -\x0aM\xc6\xb7\x1b{(/a\x9e%M\xcf\xc5rt\ -6\xb7\xcbC\xc7\xe2\xd3\xb18\x22F\xcc\x1da\xacn\ -\x01\xcckn\xd9\xaaC]\xbb\xe6\xd7\x07\xeb\x18\xcc\xf3\ -k\xf5.\x17|\xa3\xd4\x04`\xc4{\x00\xe3tn\xec\ -n\x95\x83\x0c\xf3\xb3i\x1e\xfb\x16\xf7+s\xdf\xd2j\ -\xdd\x80100\xfe\x8f!s\x08\xdc\x13\xe6]\xb6\x92\ -\x1b9\x09X\xecI\x97\xc6\x1f\xcb\xe9\x9f\xd9N\x8eU\ -\xf8\x98\xd39\xc7\xa5\xf9y\xd5//\xb5z\x16\x8c%\ -\xc2\xf4\x80.\xdd\x0f\xc82\xcb\xc9h}\xf6\xe7V\xf7\ -\xac|x\x821\xe3\xdf!\xfd]\xcc\xd1\xe8i\xfa\xa9\ -\xcd\x9f\xdc\xa4\xae\xe7\xd8t8\x7f,\xe84\x7f\xda\xc7\ -e\xc2x5,'\x05\xc8\x09\x08\x18?\x08s5\xbb\ -$/:|\x7f\xb5\xbb=\xfeX|mcl-\x8c\ -\x93\xc3e2\xfc=\xd4\x05Z\xa2\xcb\xf3\x07\x93\x9f>\ -\xad\xe5'!\x13\xc8O\xc5\xee\xc9O@#\xd4a\x1a\ -\xe3\xa3\xe1zU|lNs\xdc\x9a\xe1\xe8\xd6\xe8\xca\ -\xbe\x0a\xcf\xb1j\xbf~\xf9t{\xfd\x82\xe3\xdd2\x9f\ -\x0e\xea\x92P\xc7\x87t\x16YO\xc7t\xc5\xd6X\x0f\ -t/\x15\x9ah\xa7\xae?T\xd0E\xf6c\xf4\x83q\ -o\xbaou\x19F\x7f\xc1\xeeQX\xae\x14\x9c\xa3-\ -\x01\xf7U\xadr\xa5\x1a\xe3\xee\xa9\xeao\xa2\xd8\xfc\x86\ -\xebF\xdb\x86\xe5\xdc\xd3A\x7f\xc3t\x9c/O1\xba\ -\xa1\xae\x0b\xf7'0\x9e\x14\xa3\xdf\xf7@\xbb\xe7b\xcf\ -\x8e\xf2\xc6i\x17\xc1tP\xb8'\x81\xf9\x02Pw.\ -4o\xa3?W\x16\xb7\xfb>\x94ct\xcb\x97\x07c\ -\x04\xf7\xd90\x86\x1f\xf2\x19\xc6\xaab\xef\xb0\xa1\xe0\x9f\ -\xe9\x872\x16\xec\xd7!_`\xce\x1c\xdc\xbb\xd4e\xbc\ -\xeb\xc4\xfee=}\xf7/-\xeb\x915\xdewOg\ -\xe9w\xa4\xfa\x19j\x8d\xa1\xfb\xc7\x969Xx\x9c5\ -\xccS\xa3J?\x94\x1b4\xd0\x8f\xed\xdf\xdf\xde\xa3\xc7\ -\xfe\x9d\xba\x0d\x02\xe6\xbb\x80\xb5\x11\xe6\xb7\xc1=:\x16\ -\xf7\x0b\xd7\x1b\xb0~\xc1\xf9@\x99\x1782\xdf\xa3\x95\ -\x0fN4\xd1\x0fk\xa8Q\xad7\x83\xd9Or\xe8m\ -?\xa1n\xbf\x82\xba\xf2\xcd=\x18\x1d\x98\x8e\x0c\xf6\xbb\ -\x94Zo\x12\xf8\x9e\xb65\x9a\xe5\x86\x08\xf6\xaeR\xec\ -W\xb6=a\xbf\x0a\xeb\x90~\x18\x93\x0e\xf8L.\xfd\ -\x85\x96\x9cY\xd2B.\xb7\xcd\xfbhCG\xd3\xbck\ -c;d\x8c\xfd\x90\xba\xfd\x16\xce\x1f+9,\xcf\x0e\ -\xc6\xe5C\x19\xc8d\xf1\xa9\x8d\xf6\xdb\x8e\xed\xe7\xadb\ -\xc2\x99\x8av\x94\xd0l?\xef\xeb\xfe\x8b\xbe\xee?\xfa\ -\x1b\xfcw}\xd6\x7f\xda\xd7\xfd\xd7\x7fC\xfc@_\x8f\ -\xdf\xe8\xcb\xf13X\xfc\xd0`\x04\x09\x87?9\xf0x\ -#\xb6\xe6\x9f\xac\xd6\xbdf\x03\xffi1\x9e\xe1\xf0'\ -G\xf3\xb8\xc38-1\x84r\x84OS\x9c\x16?\xf5\ -{\xb5\x99[C\x01\xb6\x01<\x05( t\xef4\xces\xda\xbf\xdf\x14\x0b\xd1\xa2n\x09\xed\xb6\xde\ -\xc6w\xac\xf3\xdf\x81\xf5\xb1\xc1\xfe\x14\xdaN`\xbd\x10\ -X[\x01\xd6\x0d\x84\xf5\x10\xb0\xfaA\xb4\xd1\xd1\xf9\xf7\ -\x1b\xdes\xbf\x04\xb6\xd7\x845>0_\x15\x16\x8b\xd0\ -\x80\xd5^\x806)X\xbb\x02\xda\x11\xa0\xed\xbd\x93\xfb\ -\xb9N?\xbb\xd0R\x1a\xcb\x8dn v\x9c;O1\ -@\x90\xb1z\x11t\xb3Ya\xb5\x84$1\xfbX+\ -\x93A\xc17\x94\x18{\x1f\xad~~\x91\x92\xeb\xfa\xfd\ -S\xab\xbaV\xd0\xfePd;\xab\xfb4\x80\xefc\xb6\ -\x192%\xee\x03\xda\xba\xaaC/`6\x10,\x07\x18\ -\xab\x87K\xc9\x93\x866LX\xdf\xa7\xc9v\xd3]\xbb\ -#\xf4C\x1cV@I?\xbf\xe2\x9d\xae\xc7\xf2\xb01\ -;r\xdb~\xe1{\xd22\x8f-M5\xb7\xa0\x8d\xa0\ -[v\xdbF[\x09\xeeW\xacM\x0e\xa7\xd4\x06\xf8\xdd\ -\xfc\x06\x7f\x83\xf9\xd6Mc\x80\xd5z\xec:\xef\xab\xc3\ -=\x9b\xee\xd5)\xff\x0b\xb4a\x9fY\xda\x14\x8b@\xa9\ -g0\x09\xed\xd2>}\x9f\x18\x16\xf7C\xe1{5V\ -;\xe2\x8fc\x89\xd97\xe41\x1b)e\x9efb5\ -q\xbb\xe4\x87\x02|\x86g\x0d`\xcf\xaf'\xe2\xb6T\ -\x06?\xbfe\x8d\x1b\xc8\xcb\xe0\xf3\x98m\x0e\xde\xa7\xcc\ -\xd3\xe0\xcf\xcf\xa76\xfe\x16\x9d\x1c\xff}\xe2\xd8w\xa1\ -\xdd\x15\xda\xd5\xe1\xfbE\x891\xa1\xd4(\xc3\xecq\x7f\ -z\x9f!\xcd\xcf\x9c\x9b\xe7_\xa7k\xadQ\xea\x81C\ -\xb9\x01\xe5i\xed\xe7`L\xdeB\x9f=\xa4\x09\xc3Y\ -uJ\x9d4j9\xd5\x8d\xef\x9f\xfb\xe6\xa6\xd8\x1f\xca\ -\xfb\xd7I\x9f\x1bV\x8f\x5c\x1a\xad\xcf\xfeBy\xd7\x92\ -^b\xe3\x81\xc9\x5c s\x1a\xaa\xcb1?\x0cVK\ -\x06;\xa3A\x8a\x22\x07\x9a\xe4\xcf\x14\x8a\xfc)n-\ -\x7fh\xf2A\x03YU~\xd5\x00\xab\xe5\x0d\xe5\x08\xcc\ -\xa3\x87\xe3\xd7\xb2\xc1z\x1aX\x0d\xe1\x10W\x94\xf8\xf1\ -!\x90\xbfn\x1d\xca\xdf\xe2\xae\xc8_\xd0\xb7\x82\xdd#\ -)\xfe7\xd0\xaf\xb6r\x1f\xd6a\x87\xf7\x84\xcf\xa6\xda\ -h_\x7fZ\xaf\x81\x80\xe70n\xa2\xe4\xf4\x22\xec\x1a\ -\xca[\xe8\x83\xa9\xcf\x89\xc7b\x83\xca\xaf\xef\xc6\xee\x0b\ -\xfd\x0aX]\x94\x86n\xaf\xbf\xcd\xfa\x07\xceG\xf8\xac\ -\xda\xc4\x97\x18?0?+\xb8_\x13p~\x16\xdb*\ -\xd1K\xffh\xa5\x7f\xc1\xda\x19\xe5^&\x98}\x1f\xca\ -\xbf&\x1f[\xdb\x1ay\xf4\xd3\xbf\xda\xeb\x9f-\xebm\ -0\x0e\x8d\xfago\xeb\xdf\xbd\xbd\xff`\x86\xfdW\xaf\ -\xed?{{\xffm\xd3\x8b\xd6\x18\xf8lh\xa7\x80f\ -\x091\xa4\x85\x9d\x82\xb3\xfdgq\x9a\xc7\x00\xb8\xe1}\ -\xfa\x9d\xed\xae\x01\xff\x8c\x1b\xfe\x9d\xc6\xef\x06ua\x0c\ -\x83Z<\x97\xca\xdf)g/4\xe9,\xd4\xef\xd1H\ -s\xeb\xdf\xc3so\x80|\x85\xb1\x7f%\x0e\x9a\xcd:\ -}{9\xd6\xbe\xbf0\xbe\xfa\xec2\xac\xde l\x18\x9b\ -\x02\xfd\x96\xd8\xd9\x22\x8dgl\x18R|\xe1\xf0\xb9\x94\ -x\xd56\xf47\xeeA#\xaea\xeb\x1e<\xaf\x07>\ -\xa7\xd4U\x0b\xd3\xb9\xe0zU\x1d\xe1\xf9\xbb\xf1kh\ -\xf4\xe1\xc28\x22\xe2\xbb\xfb\xd8\x99$\xf0\xfb\xb5\x89/\ -(\xfe\xd4\xdf\xf3\xaf\x00\x8b\xedv\xd3\xc1\xc6\x16\xae\xb5\ -X\x1c4\xb4\x05\x00]\x03\xc6v\xfca\xfeP\xe6/\ -\xd4\xd9\xb0\xfd[\x8b\xf5to\xa7\xe6ow\xdf\x9fn\ -\xbd\xbf\xddm\xff\x07:J\xaf{\ +\x01\x9f\xd1x\x9c\xed\x1d\x09\x5c\x8c\xdb\xf7\x9bR!\xd4\ +\xc3#)e\x8dl\xf9{\xc8Z\xf6}_\x1f\x22<\ +\xbb\x92\x84,\xd5\x90%d\x97\x9d,E$B\xa4\x92\ +\x22\x22k\x12\xd9[H4\xed{\xd3\xcc|\xff{\xee\ +\xf7MM\xd3LM3\x93&\xba\xbf\xdfy\xbdI\xf3\ +}\xf7\xdes\xee\xd9\xef9\x04\xc1 T\x09\x18\x0cB\ +\x9f\x98\xa7M\x10\x0b\xd0\xff3\x99\xd4\xe7\xb65\x19D\ +\x18\xfa\x9d\xa9)\xfdy\x00A\xa4\xb7`\x10FF\xd4\ +g\x8f\xd6\x04\xd1d>\xfa?}\xfa\xb3&A\xdc\xdb\ +\xc1 45\xa9\xcf\x8bj\x10\xc4\xe2\xe3\x0cb\xfb\xb8\ +1C\xeb\xd6\xd6\xae\x8d\x1e]w\xf8\xb0A\x13\xe0_\ +\x01j\xc2\xabgZ{\xa0wj\x1b\x0e\x1f\xd4\x7f\x92\ +\xed\x87\xe4\xcf\xa3\x975\x88\xfa\xa2\x9a\xf6\xa8\xd5\x18+\ +\xa2\xc1\x86\xe8e\xdeM\xe3w\x5c>1\xe4\xb9\xd9?\ +\xb5/\xd5\xb09p\xd0\xccP\xe5\xcc\xe5'\xdb\xa6]\ +\xf3h9\xcd\xac\xd6\xf8I?\xaf\x98\xaeh|\xd9P\ +\xc9\xac\xf9 \xb7\x05C\x0f\x8c\xaeU\xbf\xc5\x88\xc6\xcf\ +'\xef\x98\xda\xff\xf9\xf1\xd0eg{t\xeb\xb4\xeb\x80\ +\xf1\xf6\xe8\x87\xcf\xb4\xe6-{\xa4\xd75\xd3\xa4k&\ +\xa7\xbb\xe9\xf4\xb4\xf4w\x09G3\xbb%\xe4Y\xfb\xe9\ +8=>~\x8c\x11\x9c\x909\xfcp\xc8\x94\x93\xd7n\ +\xae&\xec\x08\xe7\xd9\x8c\x98\x06\xdc\x97\xdf\xec\xdb\x11\xe4\ +\x1b\x87\xa0\xd3\xe4\xda9\xad\xbf\xec\xdb\xdf\xe9\xc8I\xf4\ +\xf7\x1b\xfcf\xbex\xc3\x08o2\x83\xe58%\xcc\xe4\ +\x95C\xb2r\xfb^.\x8765\xaa\xa9\x1e\xae4\x17\ +\xfd\xdd5}\xb3\xb6\x9e\xb7\xffG\xf4\x9a\xb7\xd4\x8c\xd8\ +\xca8\xa1j\xa5z9)\xf2HC\xe6\x05b\x01c\ +\x94\xd7\x83\xde\x87\xdc\xd5\xffk\x14U\x83i\x7f\x98\xac\ +=\xfda\xea\xfeN\x1a~\x1f\xfe\xc7lG\x98\x18\x07\ +\xae\x8a\xf9A\x84\xa7f5>=\x99\xd3\xe9g\xc6^\ +\x83\x8e\xcay\x8c`e\x83[ut\x9a\x10hns\ +\x88\xd9\x03\x97~ip1\xae\x1d3uJ\x9c\xee\xc4\ +\x08\x95\x8b\x87\xe6)\x99\xea\xdd\x0c\x22\xbc\xe2\xd6\x0c?\ +\xbc\xcdc\xd3|\x22\xba\xff\xd2\xbf\xdd\xdc\xd4'n\xcf\ +`\x9c\x9e\xca\xd1\xf2\xd9\x11\xdd\xc9\xac\xd9Ym\xb7\xd3\ +*\xfdg\xbe\xd1\x1f\xd7\x0b&\x16\xcd\xfc\xdb\xf5Dg\ +\x15\xaf\xa5i\xbbl\xfa\xefh\xd7\x84X\xa7\xe7\xa0\xb3\ +,\xf1@@\x07e\xad\x89\x8b\x9dM/\x12\xe1C\xb3\ +S\x1d\x1d\xd7\x7f\xb3\x1c\xcb\x989i\xce[\xfd\xc3O\ +c\x996\xcd\x99\x1bV\x92k\xbc3\x0e\x14\x9cd\xa4\ +\x05\xf0N66\xf8\xa0\x14\xde8\xed\xd3\xa3\x80\xec\xbc\ +A\xb3\xae\x13^\xdd\xbc\xf5\xd3\xe3\xcevd\xbex7\ +\xbdN\xf0K\xf3x\x83\xd4D\xe5\xb4\x1b\xb3\x1a\x0fz\ +\xf7E)\xfcBN\xca\xc0\x19\xb7F\x0e:\xd6n\x91\ +\xd7\xc6\x83f\x9d\x17\xadW7]\x9eA\xd6\x99b\x7f\ +vnH\xf8te\xbb\xd5w=,\x18\xa7\xc7\xa6^\ +\x0b\x1b;i\x96\x9e\x8f\xa6\xd6-\x96\x8aE-\xf4\xb0\ +\x0f*\x84\xc9\xa6\x9d\xeb\x9cr^\xd7\x8b\x19\x1a\xf8b\ +\xda\x8b\x0c\xe2\x87\xb3\xd7\xe7mo\x13\x0c\xce&~\x8e\ +b\xedv\xefP\x97\xe0\xd6\x9d\xe9\xcd\x1e\xad\xe9\x17\xa9\ +\xe290\xf8:\x81^\xfe\xbf-\xee\xcd\xcd\x87\x13\xde\ +\x19\x0d\x8e>\xb9\xaei\xd4\x97\x98\x96\xbf\xbd\xc3\x9c\xe1\ +5\x8fh\x13\x979\x83\x0e\xfd\xafo\x8dQ\xea\x84\xdd\ +\x87\xb8M\xcf\x1bo~\xbc\xb4\xd3\x91\x9e\x83\x17=\x99\ +`n\x17{\x7f\xcf\xf5\xa8-\x8f'\xde]f\xb3\x9c\ +11G\xb9\xed\xb4/\x1f;\xd9\x0dv\xea\xd3J%\ +&s\xc8\xa9V\xcb\x8c\x89,\xb5\xf5\xf6*kR\x9b\ +\xef\xf7&:\xd6\x98\x90\xcd\x99\xdf\xb5\x99\xaa\xbf\xc6\x14\ +\xfb\x16\xed\xec\x16Y\xb9X|f\x9c6\x09\xe9\xd3\xc1\ +N\xc5\xa9\x8f\xe5\xed}\xdc\x1a\x89\x89J\xde\xd1c\xdd\ +\x0e6L\xd4\xd2X\xe4\xa6n\xa5\x87^\x7fi\xd1\xd0\ +\x93f\x1b\x8f)\x9bF?\xf3\x1e\xcb\xd6m\xf0j=\ +;\xf1\xfd\x22\x9f\xf3\x9d\x98wW\xe5\xa6F\xa4\x1et\ +z\xc2^xs\xc6\xee\xa8\xd7*1+\xb9\x83\x96x\ +^\xdc=E\x97`/\x09\xea\x1a\x91\xba\xdb\xc9\xb0\xd7\ +\xc4\xa7\x87\xe6\xde\xf4!B\xdd\xa3\x9b\xfc5\xd9=\xd1\ +\x7f\xfad\xe2\x87\xa3\xbaYD\xea\x0e\xa7K\x11\x93\x93\ +\x991w\x1d\x99l\xeb\xe8\xe8{\x8bMZ\x18\x0c\xef\ +\xdfTs\x94K\x81\xde%\xdd\x1a\xce\x1b]\x5c\x1f/\ +\xfep\xe6\xcd\xe7\x1d\x86\xb7\x08\xdb\x5c\x03k\x9f\x86\x86\ +>J\x97\x163f\x12\xedM\xf6M\xbc9\xcfu\xf4\ +\xbac\xb9\xf9jw\x0fu\xb0k\x15\xb9y%\xd7k\ +\x15\xc95y\x9bk\xd43\xa8]~#'7\xbb\xba\ +\xfa\xb3RL\x0e\xd5%\xec>\x9e\xe9\x14\xb6\x7f\xe8\x00\ +5\xcd\xa6K<\x0bR_v\x9c\xdf\x84\xf8\xb6\xca\x0c\ +\xd1\xa9z\xfb\xf4\xddj\xee\xbcy\xee\x9d\x82\xa7F\xb4\ +6h1\xf1\xa0\xd7\xd3\xd6\xca\xa6\xb33\x1d\x86\xf8G\ +\x0e\xfaoG\xd7u'y\xa6~\x8d\x82U~\xd6o\ +\xb0\xefb\xa7\x83\xec9~\x84y\xa6\xebc\xb3\x11o\ +\x7fj(\x99F'_\x18\xe9\xef3\xe8\xbfK\x9d\x02\ +-\x83.LRg\xaeqh\xb1\x86}\xda\xf2\xf01\ +U\x9dC\x9dZ\x87\xed\xd8z\xfc\xaf:y\x9a\xb5\xba\ +\xc5\x9e\x0b\x0fX?(\xebvN\xe6\x84\xfaa\xban\ +=V\xe6\x9b\x04\x8dJ\xd1?\x196g\x95\x9d\x86'\ ++ b\xf9\xf6\x7fB\x88\xd0\xda\x0ei\xb3\x88\x1f-\ +\xb7\x98E\xb8\xb7Z_\xc7\xa6\xe0\xaf\xba=\xbe\x9e\x9b\ +\xa7\xa6Y\xb7GB{\xfb@rJ\xeb!\x9a\xcf\x02\ +\x9f\xc4\xed\xc9\xee\xdbj\x7f\xefa\xad\xd6\xb8u\xde\x18\ +e\xb3*\xf4\xad\x7f\xed5u\x0c-\x19uF\x1e\xde\ +{\xf9\xafZ\xdd\xe6\x9c\xfe\xb8\xe7\x80\x01\xc7i\xebq\ +\xcd\xba\xa9\xbe5#\x93\xa2L\x98\xc3~\x04\xab\xf5\xef\ +\xe2{\xf0\xe1'\xcf\x0e\xb3\xda\xac1\xf2=8\xb8\xd7\ +\x01\xcf\xcc\xbf=F\x84\xb4\x9d\xd6d\x1a\xa7\xa3qG\ +\xb3^\xff\x8b2_\xaenYOoH\xab\xff\xd8\xc3\ +\xd3\xcek\xfd\xc8\x09j\x5c{\xe5\xc8\x7fG\x91\xb7v\ +\xf9\xb9w\x98Q\xf3\xad\xf1\xae\xda\x0c\xab!\xfe6\xb3\ +\xfe\xfb'h\xfd\xee\xa5k\xbe\xdb\xb4\xd9\xd6\xbe\xc1\xe2\ +\x89\xbc\xe4SV7\xae\xbb\x04\x1d\xbco:\xcd,:\ +\xe4Y\x5c\xa3f\xe4\xf5\x87FF/\x8f\xde7\xf9\xa6\ +l\xea\xdab\xd1\xe1N'?\x0e\x1e0\x1aM*\xbc\ +\x81ehp-\x870\xfb\x1e\xc7\xef\x1d\x1b\xf6\x83\xe8\ +t\xb2^L\xc4O\x02mL\x1f\xde|\xf5\x03\x99\xc7\ +\xef\xf4\xdb\xb58\xd5\xd9+\xbb\xc96\xa7\x86\xb7\x08\xb6\ +\xdb\xc8\xef\xdd\xef15^Yu\x1d\xb3\xba\xe0\xaf\xbe\ +W>\xc7{\xfa5m\xda2M\xa7\xaf\x83\x81\xb6e\ +\xe8s\xab\xaeh\xca\xf7\xbfq\xe0\x95\xdb\xfd\xdc/\xdb\ +\x5c;qI\xe3\xc3\xbd\xb3\xa9*\xc1\x06\x11\xee\xd6\xbd\ +\xe6M\x0aL\xb0]\x19\xa2l\xf1\xcaH\xf9\xf4\x89}\ +\xbc)\xceY\xbes-\xdb\x1d_\xfe\xf7\xb8\xd1\x19\xab\ +\xcc~6\x0aNd7\x8b{\xdd\x94\xa2\x9e\x0e\xedg\ +\xac\x08\xb2\x0bZ\xec=={\xf3\xf1.7n[\xfc\ +\x9b\xff9X\xab\xb3V\xd3%!yY\xaa\x8e\xa7\xd7\ +\xd9\x85.o\xa8\xc14s&'9\x10G\xdcoG\ +[\xae\x9e\xdfy~\xcc\xe6\xec\xab\xb1\x8d\xf6\x853>\ +5\xe9\xfbL\xdfc\x96k\xfa\xf1\x05\xfaA\xb9\xb3\x03\ +l.N\x1a\xf7i\xb9\xde\x8f\x9c\xa8@\xe3\xf3\x7f\xad\ +\xde\xec\xfcQ\xab\xbfU\xca\xeb)\xcec\x97[9\x9f\ +\xbe\xb3:\xf0\xc5\x05\xe7F\xa6\xae6\xdb\xdd\x1a\xe6|\ +T\xf3\xe7\x8e\x18}tp\xc8\xf3\x13\xbcK!\xa6\xbe\ +z{\xfc\xf7\xcc\xb5\xb6W\xff\xfa\xba\xa9\xce\xc8v^\ +\x1a\xc1\x13[\x12\x1f\xdcG\xb6\x8cuxD\x0e\xf5\xaf\ +\xf9w\x17B\xbbkD\xf4\x8e{_\x1a\xa1\xa7\x99\xcf\ +\xed\xde\xd2L\xe7\x8d\x8fr\xea)\xd5\xa4\x03\x9dvm\ +\x9e\xac\xaeV\xeby\xf8\xca;\xc3\xfdk\xeen\x5c'\ +\xcfje\x9ef3\xb3\xbeK\x0a2\x16>\xccf\xdc\ +A+\xea\xbc\x5c\xbe\ +\xd9jaV\xcd!l\x8d\x16\xf5\xaf\x9f\xef\x9c\xb4Y\ +\xc3\xd5\x8bXZC\xb9_\x1f\xffu\x91\x99fJ3\ +\xee\x9aeg\xcdoyF9\xbf\xb5+\xb3\xbb\xf9\xd4\ +w\xf3\x8d<\xb7\xec\x1d\x96\xd2\xd6,l\xddn\xcf\x19\ +\xdd{\xdcF\x13\xeb\xfb\xe8\xf8\x8a\xa5\x89^\x06\x015\ +\x0e9\x04\xb48\xd3f\xe2\xf7\xb7\xedU\xee?\x8b\x9b\ +\xf2YoV\x0b?O\x1b\xc2J?|]K\xae\x17\ +\x22L+^J\xd3\x97[\x98\xf1f\x84\xe7\xdd\xe0!\ +\xfa\x08\x95\xce\x0fI\x8e\xf2\xd8+\xbeQ\xddN\xe7f\ +\xdc\xfc\xd0\x95\x810\xbc\xcf\xc5iV\x03t0\xc3\xb2\ +\xe7\x8d\x88\xaes\xd9|-1\xe95\xd9\xc3\xf1\xda\xbe\ +!^\xc6\xf5L\xb7\x0e\xfb\xc1Lx\xab>\xad\x19\xb1\ +z\xcc\x8d7\x13\x8fN\x9e\xadN\x10\x06\xef\xebz\xef\ +\xeab\xfc\xc3E\xd3\xfb\x1fB\xfb\xfd\xfc[Sv\xf5\ +\xe9\xe5\xb9\xae\xbd\xfe\x93\x8d\xa63\xd6\xb6\xab\x81\x84B\ +o5\xefYw.\x05\xd5\xf7\xfcR;\xed~\xe3\xf1\ +\xddf\xd5b\x8cT\x0e\xbe\xc23\x09\x7fg\xc6\x89_\ +z\xd3\xb5\xd1\xeb[\x91\xdfW2Vg\x0e\xa8y\xfb\ +\xac\xca\xde\x17\xdf\xb4\xfdwz~Q\x22|:\xde\x7f\ +lo\xa9\xc1x\xaa\x14\xfc\x82\xe7p\xce.o\xb3\xbd\ +\xb3\xcb\x93u3\xd9*6J-k|2\xd6]\xfa\ +/a\xecN.\x1f\x17\xf8wp\xc7\xfb\xff\xf9u\xab\ +\xd1!\xc0\x0b\xfd\xc2\x95\xec1k\xbe\xcb\xacU\x17\x06\ +4\xf0\x990ud\xfc\xd2\x85\x87\x16\xddn\xb2f\xc8\ +\x9e#\xab\x17\xed\xac\xdd\xa1`JO\xf4%\xdb\x8b\xb6\ +\xea\xc1\xef<\xc3\x13\x0f=\xb9\xb4\xc2*\x81u\x92S\ ++\xe8f\x8d\xcb\xe3\xc7|\xfd/\xd6z\x9b\xab\xf1\x1b\ +\xc4/B\xef.\x9a8\xf7\xc6\xf4\xae\xd3\xea\x13\xab-\ +\xdd\x16\xbf\xd5\xb1oq1g\x14a\xfe\xbc\xd1\x9c\xd0\ +\xae\x7f\xbf\x0e\xb28jm\x9c\x10\x10lK\xae\xecR\ +\xcf@\x97}{\xf0\xb2s\x93o<\xd8\xfa!mn\ +;O\x0e{\xdf\x12\xf8\xde\x95\x99]\xcf\x91\x1a\x8bf\ +\xec\xbdz%\xb5\xdd\xcdv\x8b\xae\x9d\xfa:\xfa\xc8?\ +\xf1\x81\x1f\x1c\xbe\x7fIdwj\x93\xb0&s\xc6j\ +\x8d\x9c[\xe6?\xe6Y\xbc\xecg\xb0\xf5^\xee\xe1M\ +\xc6\xeb{\xc7\xe7\xd5\x1d\xea\xb5\x98a|\x99\x5c\x8e\xb8\ +\xa1f-\xbf5\xdb\xf4]vG\x1e\xfb\xf7S\xe8\xb3\ +\xb8\xee\x9b&\xf6\xab\x93s+.\xe3\xa2\xe3\x08\xe7\x95\ +\x93\xdak\xa5\xc5\xbe\xf81\xe7DP\xc1\x10\xff\xb6u\ +\xaf]V7W\xbb\xc1&U\xdf\xf8\xd6\xf4L\xf0\xe7\ +\x9dq\xbd\x92ek?2,`r\xaf\x03!\xdf8\ +m\xda$\xf4\xe8\x12\x9e\xfc(~\x22\xc3\xdd\xc4q\x80\ +\xf2g\xfbG{\xd4\x8e|\xf9rs\xe5?\xef\x92z\ +\xbfWWIq.\xb8\x98\xcd\x1e\xf3b\xf9\x5c\x8e\xc1\ +\xc2\xa4]&\x88\xb1\xbal\xff\xa7_A\xce\xd8\xd6\xd3\ +\xdb\xbdq\xaa{\xb3\xe7\xa5\xf8\x09\xcf\xb2\x08\xef\x1b\xa4\ +\x09\xf7\xb8\xdf\xc2\x84\x8bw\x1b\x11\xe3\xe6\xf5\x0a_\x82\ +\xb8\xbd\xb1Cn\xdd\x17\xd1v_\x9d^~C3\xfa\ +\xd9\x22\xf29\xa7\x11s`\x01\xc9\xcc7\xab\xdd\xdev\ +\xf1No\xc4k_|\x19\x13\xfb62z\xc4\xd5H\ +\xe7\xad\xc7\xbb\xe4\x0c>n\xd3%q\xee\x12\x02\x11\xff\ +G\x7f\xc2\x93\xb5\xfe\xd0?K'\xbb\xd6\xcb\x8a\xeb\xca\ +hp\xa3\xc9\x89\xc0\x93\x13\xfcgX\xfd\x9c\x14\xbcO\ +c\xd9\xd1/J\xb7s\xd2:\xb6I\xf0\xed`\xb2\xea\ +\xd1\xa5\xce\x8e\x8936\xfb-\x1e?\xba\xa3\xb3e\xff\ +\x96\x8f\x1e\xce\x083\xde\xf8x\xf1(\x8b\xb1\x8dv\xff\ +Pz\xdb\x89g\xb4\x94\x17t$\xd1\x8c0Ug\xb8\ +\xa4n\x9d\xa2M.V~\xbd\xf3\xe3\x88+O\xf5\x92\ +,V|\xeaY\xef\xd0\x95u\xaf\x1a9,\x8b{2\ +H;\x90\xe4\x05mp\xb0\x5c\xb0\x22 \xb6\xdf\xc9\xc9\ +\x8d\xdf\xd7\xd2;\x15\xe5[sd\xab5i\xef}Y\ +#\xb6f\x9f|\xd52b\xb0\xc7v\xaf\x96#\xb6\x7f\ +\xabW?\xec$g\xb8\xd7+\xef\xd5\xadr\xc3\x1a\xf0\ +\x8e}\xb7\xf7J$\x9b~?\x14io\xd4\xd6v\x9c\ +Wo\xd7\xcdHrd\xae\x89\x8ey\xf1\xe3/\xf7\xb7\ +u\x17\x05L\xd6{\xe6\xbbi\xb2A/\xb7\x99:5\ +\xd1~\x9fH\xca\x99\x17\xb39h\xae\x8a\x0f\xbb\xd3I\ +t\xa4\x1b\x10[4\x12\xee\x84\xbf\x1c7i\xed\xa0O\ +c~\x8e\xaa7\xb5M\x04\xbb\xd6\x04\xaf\x88\x87aM\ +\x9b.\x99GD\xce\xbb\xf2\x83\xb1\xd3z\xa3\xf9d2\ +Z\xff\xe6\xe0#]\xea\xf6\xcc5\xdcy8L\xb7\xd5\ +\xfe\x80\xdd\xd3\x82[&\x7f\x08\x09\x22L\x8fjZM\ +h\x8f\x94\xc6\xb6S\xb4j\x866\xfc\x1a\xd6\xe0\xad\xc6\ +\xf5wo|\xff\x193\xd8>\x7f]\x8fh\xed\xb3l\ +\x8fFJc\x16\xab\xb4\xd5%\xb6D\x1c\x9d\xc9c\xd4\ +\xf7l\xf2|Y\xd7\xa1G\x1b\xb0u\xb9\xf1K\xbd\xce\ +\xf6\x9e\xe3\xfdj\xf1\xa4\xda\x8c\x0b>J:\x0d\x88\xd5\ +K\xff\xd3arW\xf7\xf4=\xff\xa5\x85\xb6\xe5\x84\xb9\ +a>\x0d\xa632\xa6\x0e\x1f\xff\xc6\xf2\xafP\xfb\x80\ +\xe9\xce\xef7&\xf4\x18\x10\xc7\xaa\xd1\xe2\x8c\xf2\x93\xab\ +\xea\xe6\xc4\xc1\xf1\x96;\x8dr>\xbeY\xb89\xf8\x00\ ++9b\xf0=D`\xaa\xcc\x06\xad\xf7\x07$\xf9\xf6\ +\x9e\xd6\xc2\xcfq\xc6D\x87\x9d\x9d_\xea&Y\xf4o\ +\x15\xe9\xb6\xccs/c\x5c(\xa2\x7fD\x0c\xc3\x1b;\ +j\x1bu9\x1e\xd6y\xe0a\x9b+A\x93\xdf\x8f\x1e\ +\x18\xf9\x99\xd8\xf3\xb8FT\xb3\xbb\x7f\xeb1u\xf3\xb3\ +Ng\xcdo\xbb\x93az\xe5\x96\xff\xdd\x9d+b\x0d\ +j\xee\xbag\x8a\x04\xc4\xe3\xec6\x0b\xba\xc6}\xed\x91\ +\xd0\xd0B\xc5\x88x\xdc\xc0m\x06\xc3\xb0\xaf\xf7$'\ +\x86\xe9)\xa4\x02<\xfc\xc69\xf4\x8e\xd5\xd4\xa0\xcd\xc5\ +\xad\xe8\x17\xcd?\xd76\xce\xbd\xc8\xa9\x1f?\xb7\x0e\x91\ +Q\xc7\xc2Q\xdbo\x0as\x87\xf2F\xf4\xa1a\xd8\x14\ +\xe6\x8aE\x97Fh\x11\xdf\xda!\xfa=\xdbU\xe9\xd2\ +M\xa5\xc8\xa6.\x9ai?\x12\xc7\xef\x8b\xea\xb6\xda\xa7\ +\xf9\xfa\xcf\xeb\x0f\xfd\xe5\xe9d06\xcfe\xdd\x80\xf0\ +L\x86y\xd2\xc8\xb6\xfd\xaf\x9b$\xce\x0f=\xefS\x7f\ +\xac\xfa\xa5\xd5W\x1c\x13\x02\x88\xfd\x13\xbb\xe6\x9e\x9a\xef\ +\x16}w\xe1\xf0\xcf[&\xf7\x8e\xba\x97\xc9\x08_9\ +\xb3\xcd\xbe]\xa3\x07\x0e\x0b\xfd:\xfb\xdb\xf4\xd9\x86^\ +ji\x1b\x88avZ]w.\xe3\x8d\x1c\xd1jM\ +\x07\x93\xc0K:./V\xc1o\xb3I\xaf/\xef\x8f\ +\xe4\x18tk\xaa\xd9\xf3\xfc\x0bc\xe6\x05eS\xeb1\ +ow?\x1f}\xf1\xfa\xcf\xe8\xff\xa6\xcf\xd9\xb7\xb7\xd7\ +\x9b1'\xf7\xd7\xdc\xcb\xd6ws\x1d\xd5rWd\xc2\ +\xe9\x88\xe9\xc4X5\xe6$\xd2a\xf7s\x87\xd6n]\ +{^[AL^\xa0\xd4G\x97\x18\xba\xa5\xdf\xe6\xc3\ +:\xdc\xa9['\xb57u9\x8a\xfe\xc84\xeb\xf6\xb3\ +m{\x87]\xf2\xeb\xef\xd6\xd8\x8b\xf73\xf9\xe5\xfd\xd7\ +\x0c\xfd7u&\xae\xda6\xad\xf3\xba\xe6\x91J\xe3\xe6\ +\x1b\xeee\x98w\xadG\x5c\x1a\xbc.d\xdc\xe2S\xf5\ +\x88\xc9\xec9\xad\x1e\xb1<\xbb\xf4\xd5%\xea\x1b+\x9b\ +\x12FV\x9b\xcc\x88\x16\xadm\xfdf\x84\x12\xfa\x19F\ +\xaa\xcc\xe7_l7\x84o\x8f\xbcOx\x1f\x8dR;\ +9\xacc\x1d\xa2\x85\xc6<\xe2\xdd(\x97n1\x83\x08\ +\xe5\xe0\xd5}g\xae7\xad=\x87\xd0\x5c\xab\xa5\xcc\xec\ +3\xf6fH\xaf\xa4\x07\xe8OG|\xec\xbfo\xc8\xdc\ +yZ\x17\xedj\xeeX\xdb\x8bh\xd5\xac\xc6\xf8\xc5\xbd\ +\xdb\xd6\xdf\xd0b\xd2\xbeG{\xfbr-\x0e\x1e9\x90\ +k\xbdr\xe2\xe0Y\x0e\xfa\xcf\xd0\x147v$n\x0e\ +S\xc9\xd0KRz|`\xba\xcb\x85\x88\xf3{g\xe5\ +\xee\x1b\xa7\xefy\xbd\x9d\xf3\x05e\xfd\xc0v7:E\ +\xb3\x9e\x8fq\x19\xd6\xf7\x90\xe7\x93!\xad\xd0\xb4/>\ +\xef\xb9~sN\x0b7\x8d\xe17C'\xadm\xf3\x00\ +=\xc3\xe5~\x92\x92G\xffK\xad\xcf?i\xa8\x1f\x93\ +Nh\xf6\xd66\xb4a\xde`\xd4\xde[{\xeed6\ +\xc9|\xa4L\xec\x1d\xf9\x15\xe9\x19?}S\xd6\x0e\xd4\ +{\xbaA\xd5\xd0B=!\x9f\xd8\x9b\xb8\xf3\xe4\xa4\xff\ +\xc6\x1a_\xad\xad\xbaanGb\xf2y\x82\xa1J\xd4\ +\xbf:_\xc9\xdc\xdbw\xcb\x1eUu\xed$\x8b\xbf\xd7\ +[LVE\xbff\xbe\xbb\xa7\xc9L:\x1c\xceza\ +g\xb82\xf9\xf5Q\x1de\xd3\xd6\xfb\xf5B\xe3'\xaa\ +\xb9G\xdf5@\x8a\xe1)W^W\xdb~D\xab\x06\ +=\xb5-\xbf\x9f\xd5g\xec\xbf\xe7\x19A\xe8\x07\x13\x84\ +~@g\x0e:&?}S\xdb!-\xb0%\xd7\x9a\ +\xa1\xff>\xbe\xc3\x16\x06\x11\xb3\xc5\xdcpO\x96\xaf\xf5\ +h\xe6.\xd5\xf53\x1e\xaa\xce~}\xf4\x84\xf5\xb6\xbd\ +\xc8\xea\x7f\xd6\xc1r\xe7]\xde\xbbq\xc7\xb3Z\xa7\xd5\ +\x09[\xea5=^M\x8bX\xbb\xbfy\xf0\x14f_\ +\xa4M\xec\xe8\xb7\xbb/\xf7x\xd2\xae\xf3\x0dU\x99c\ +\xd8!s\xda\x07O\xbf\xc7\x1c\xcb\xd1~\xd9\xc1r\xf8\ +\xec\xad\x1aF\x84M\x84\xde\x8ba\x0e\xed\x17\xd6\xe8`\ +\x93oc\x9a\x95br\xe3\xb6\xd7\xb3\x96\xc1[\xb4\x08\ +\x7f\x9b6\x06\xfd\x1a!]jo\xd6|+\xf7a\x1e\ +\xb5\xba\xc4\x06\x04\xd9\xa8\xdf\xd0Q5e\xfc\xd4X\x8c\ +\x14\xef\x91\x17zv\x5ct\xab\xf7\xb9m\xee;\xf5\xbf\ +\xdc\xef\xb0\x8d1\xcet\x8b\xc6\xe2e\x1e\xbb\x947\xb5\ +\xf1\xb7\xe9\xa5\x7f\x97\xdct\xe9\xb9\x03\x93\x91v\x0a1\ +\xc0\xb0\xb5\xef5k\xaan\x18\xf1\xe9\xda'\xfd\xfaK\ +\xea\xeb\xe5)3\x1f\x05-\x1b\xd1\xf8L\x07U\xd3\xbf\ +\xfe\xeewy\xa3\xa5I\xe8\xdd\xbdd\x5c\xaf\xcf\xb9\x87\ +w;\x99\xac\xf0\xf3\xbfk\xd0\xef\xd6\xa4Z\xca\xa7\x1f\ +\x7f\x9a0e\xbbZ\xf7\xb9\x03-\xe2FO5\x08\xe8\ +\xban\xd0\xf6\x7f]\x86\xabX\xb6u1\x22F\xdc\xce\ +\x1d\xb5K#\xb9`d\xff\xd6w\x1f\x8c\xe9\x1a\xe1\xa0\ +\xd9\x92\xbb\xa4\xcf\x12\xbb\xe5HC\xdc\xbcC\x8bh=\ +\xea\xc5r\xc3.\x8b\xe69\xab\x0d\xeb1\xef\xe4\x7f\xde\ +\xbe\x83\x8fv9\x18\x96\xdf\xb1\xf5\xdd\xa1\xe8\xdbO\x8e\ +\xd8\x9e\x1fb=4\xe4\xb9\xadMv\xdb\x81\xa7k#\ +\xbd;\xc5\xb9\xd9\xbeNC\xbe\xe4em\xb5\xf9+\xe7\ +!\xd1\xf6\xeb\x97eC\x8ef-\xa8\x994\xf6\xe6\xd2\ +\x7fz\x06\xb9G\xa6\x04\xc5\xa3/\x0e[h\xfd\xc4G\ +\x89\xc8\xbe\x1bc8>\xfd\x83ql\x0a\xdaJ\xc3\x09\ +\x17\xff&<\xf2\x07\x1f>\x1f\xd7m\xc5\x93\xcfz\xe6\ +\xc4\xa4\x83/\xbe\x1d\x1ci\xfdU\xfd\xe5{\x86\x85\x0f\ +\x19\x96\xbd\xda\xdd\xd0$\xe6D\x80I0\xf1.\xea\xc1\ +\xb2!\x17\xcf\x9d`\xaei\x1e\x13\xbfJi\xdel\x9f\ +v]?\xccn\xdb/0\xf6\x08\xfc\xb3\xc7\x13\xaf\x8b\ +\xbe\x11\x83\xf3L\x8dbB\xcey\x0e\xed\xeb;7\xdc\ +6:bl\xae\xcb~\xf4D\xd7\xdc\xce\xa1\x84\x07R\ +\xecn\xed\xff\xd8\x93\xf1a\xc7\xbd\xa1S\xf7\xac\x8bc\ +\x9cE[x*\xbaf\xbb\x8f\x19~\xdd\xb2}'\xcc\ +\x1b\xf2\x06\xcd\xe0\xe7\x87\x10\xdf'\xf6\xb7\x91\x98\xe9\xe3\ +\xf7%\xe5\xc3\xd0\xed\x8e\x0c=dCL\xb0\xf4\xbe\x1a\ +\xfeiB\xa3\x03\x99K.\xeb{\xa9(\x9f\x1e\x81\x98\ +\xdd\x88\xd1\xea\x8d\xda_ne\xb9\xa4\xaes\x96\xde\x83\ +\xcc\xa6\xcd\xe3C\x92?\x0c\xcc\xf6\xdf\x8e\x1e\x14i\xce\ +\xb9P\xb7u\xb3\x86\x97\x06-m\xa3\x17rr|\xbe\ +A\xac\xb6\x9d\xde\x9e+\xcdV=\x98\xb0\xe8B]\xce\ +\xdfs\x89\xc8\xc7\xed\x86\x9b %\x1d\xc9\x01\xad\x82\xb3\ +\xa3\xf3kOz\xec\x917\xe6h<\xd2W\x8cx\x07\ +V\x9e\xe7\xee\xe8B,98,%\x06\x09\xfb\x88\x87\ +\x1e#\x8cb\x06.\xf3\xa8\xbb\x93\xccWC\x9b>\xde\ +\x7f)\xd2\x9f\xe3\xaa*\xf7\xcb\xfa\xe1\xd0\xc3\xfar-\xa4T\x0f\ +\x1a0z\xf7\xb2\xb7\xa9\x8f~\xe4\x8e\x5c\xe3\xfa\xec>\ +\xb1(\x1c)acN\xa5\xb5\xbf\xa1\xe2r\xd4\xd9\xb4\ +\xad\x0e\xcb\xa9\xee\xf0\xc3#\xde\xbal\xf7\xf9Z\xc0\xdd\ +\xf2\xa9\xf1+\xa5+}9#\xdf\xbb\xdf\xff\xef\xd4R\ +\x8dh\xff\x94\x95\x0b\x88#\xdf\x9e\x0c\x9a\x9e?x\xc0\ +\xa4 D\xa8\xedUagG\xcd\xadS\xfb\x7f5>\ +-\xb4\xb6\x7f\xfaj\xa7\x9d\xe1\x1c\xe2\xc8c\x0b\xe5A\ +n\x93yK5N\x06\xbe\xbbP_\x95\x196%j\ +\xcd\x0a\xa4\xf7oe\xaa\xdf\x0e\xccP\xaeC\x8cDS\ +\xae\xc3\xd8\x969 ^\x0d\xd91#\xfc\xf5-\x0f \ +\xec$%7|TC\xe7qK\x17\x13\x95;\xeaL\ +r\xc4\xe1\xd0]\xce)\xc4\xc0\xe0\x9e}&\xe43\x02\ +_\xdcP7\xef2ou\xb0E\xe6#K\x9b\xe4\xd8\ +\x19W'\xf1\xfa\xb4$\x1a\xb9\xa9\xc7-\x18R\x13\x1d\ +\xb6\xe1\x8d\xb7\x8c\x08_\xb0<\xe2\xca\x89\x84\xab\xe6\x8c\ +\x18-\xa6\x17\x9a\xfc\xd3.Mw\xd8>D\xa6'R\ +\x05\x1d\x8f>\xb4\xf8y\xf6]G\x15o\x15\xf3\x80\xb0\ +\xfc\xd1H\xa1\x8b\xab\xb3Q\x09\x99M+rr\x5cg\ +\xe9\x9e\xfb\x97 fw:\xec\xf82a\xe0\xc2e\xc9\ +c\x89\xe4\x08\xf7\x8c\x1d\xe6\xc1\xdbR;\xab\xe8\xde\xbc\ +\xe8t\xfc\xc1\x85\x1e3\x03\xfc\xaf\xd6\xbe\xe8b\xfb\xe8\ +\xda\xccN\xb9_\xf4c6\xd9\x5c\xbc\xf3@\xab\xb1\xd5\ +\x93\x9c\xb9\x96\xd7\xfc\xefn\xbb\xe1;q\xd6\x15\x8ei\ +\x7f\xb3\x85D\xcd\xe3G\x9e[\x052G\xfb,M~\ +\xdf\xc6\xe1\x8c\xca\xd7\x1e[\x18\xc17\x89\x01\xce\x83\x07\ +\xaeP\xd35\xfc\xd9*\xd4\xd4\xb5\xc1\xd8\x94k\xc3\xee\ +^\xb9\xa1rzo\xbakw\xa4\xe09X?gn\ +GV\xb2\xf1\xfa\xe3Q>\x8f7\xa5\x1f\xe7\xe8\xeb\x13\ +\xf5j\x18\x22\xf6\xa1\xd2c\x9bF\x80\xf5\x8c\x8e\xbd\x9f\ +u\x9c\xbf\x02-\x7fj\xf2\x97y\xfb#G\xdc\xf9\xaa\ +E4\xd4PR\x0a\x9e\xf83b\xdf\x82\x05+\x9a\xba\ +\xda\x05\xecF6\xf4q\xafnGf<\x89\xe3|\xee\ +\x01\x8ap{\x87\xc5c\xd5\xc9q:\xc4\x8d\x08\xf7s\ +\xb9\x8b?+\xa9\xde\xb3\x9f\x12\x95k\x85\x9e\xd3)\xe0\ +K\xf4\xfaUwn\xab\x87\xab\xc4\xac\xf9Y\x7f\xc2b\ +\xc4\xff\x0f\x8fU\x03=\xf7\xef\x8fK\x8f7\xdf\xf7\xe9\ +^\xe0\xc2\xe3\xb3f\x84~tr\xb8\xf2\xa0\xf7!\x86\ +\x97z\xda,\x820E[\xb4\x860\x8bpw\xcb\xd8\ +\xc4\xd0C\xa2\xa1K\xf7\xc8\xef\xef\xbblB{\xfci\ +\xfd\xa1\xd8\xcfw\xbb?m\x9f\xbe\xdbA\xdb\xe1q\xbc\ +uk_\x15W%\xcd\x03\x9d\xda\xb9\x1c~\xf1\xcd\xa8\ +\xe7\xd5\xcf\xd9>\x13W\xd7\xf3\xb7\x9e\x911\x17)\xd9\ +G\xfe\xfdt\x1f\xbd!):j\xe1\xa5<5\xf7\xe9\ +\x84\x1da\xef\xd2\xa9\xdd\x9b6Zis\xdc\xc8\xd3_\ +\xf7|2<\xf4\xe2[V\xcbg\xb7\xee\xe4\x05\xef~\ +\xcf\xbbY?u\x9a\x11o\xdcuu\xc2\xbbi\x8c\xcd\ +\xbeq\x0d\xd0\xcbu\xd60/\x9e\xf1\x8ex\xd9q~\ +\xcf\xbc\xc5\xbbs\x8f\x91\xa6\xf9\x9d\x08\x82C\xc4\x9c\xb7\ +\xd9~\xb1\x1fb0G\x8e4\xf5\x9d\xfd\xbe\x8b#\xb2\x01\xbe\xcf\ +\x9a\x9f>\xb7\xddS\xfb\xc0n\xf6m\xb3C\xac;2\ +;Yu\x0fvq6\xfdQ\xab\x07\xd2\x8dW[]\ +\xf8\xdc\xb56\x9a\xe5\x07\x86\xdb\xd55\xd3b\x93\x0f\xe8\ +q{\x1cLV\xc2\xeb_H,t\x1b9\xfa\xad\xba\ +\xea\x86\xfc\x90\xfe{T785\x8b\x1ap/om\ +_\xe7\x11\x81\x88\x16\xde\x98\xacI\x8f0^t\xd0\xc5\ +\xec\xfekD1\x09\x0fM.G\xaa\x10>mB\x9a\ +\xfc\xdc\x7f \xdby\x87\xdf\xec\xf8\xcb\x0dk^~7\ +\xa2\xe0\xca'\x87\xb6\x1f\x95\x08c\xada\xdf\x94\xf4\xea\ +\xf0\x9a\x8c\xe8\xab>.\xa9\xb7\xe6\xbe\x03\x93/\x87\x14\ +\xa8\x04+[\x85\x8ckqaD\x7f\xcf\x90\xf9\x93\xeb\ +0]l\x87\xd6^\xa6\xa2\xd9\xf3\xcd\x9au\xba\x9d<\ +\x8f\x0d\x8f5~W[\xdf\xfaXC\x1f\xff\x19\xaf\xee\ +2\x82\xa7\x10\xfbZ&\xaf\xcfU\xcb9\xaa\xd2\xf6A\ +\x13F\x8bGm{g\xcc\xf68\xf6/s\xc0<\xec\ +\x0c\x9c\xb0\xca\xcc\xcb_\x85\xb0Z\xa5\xbfO\xf9\xe0\xcc\ +\xec\xa9#\xbc:\x12\xc4\xed\xb6\xdc\x14'\x8f\xb3\xc7\x12\ +\x17\x8df\x9c6\x19e5\x92\xad[\x7f\xfcp\xdf\x1d\ +zy\xef\xc7\x8e6#\xe08L%\xec\x22R\x0f9\ +e4\x9a\x12\xa7K^\x8a\xef\x1c\xf9Z\xc5hf\x8d\ +(U\xcd\x8b-\xb6\xf4\x0aHX\xa5\xc2\xbc\xeb\xa5\x19\ +\xa04h\xc9\xed\xc9\xb7\xdfL\xed\x98\x98\xa8\xa4\xd5k\ +\xdeR\x13%\xadC\xf6\xb9\x83\x9auGH\xcb\xbb~\ +*\xe8\xdd\xb0\xc6\xc1V\x7f\xf5\x99\xb8w\xd8\x13%\xed\ +\x13G\x97\xb6%\xb2\xdc\xb7\xeah\xe8_\x9f];\xf8\ +\xe1\x92C\x05S\x22\xd2\xea\xed[\x17\xed\xc8RvJ\ +Zu\xed\x8b\xbdC\xf3\x89\xb7\x9c\xcc\xb6\xaf\x19\xb5\xb1\ +\xb9\xe9\x7f\x87w\xae;\xb9\xf4\xc8\xe2\xf5\x83\xae>\xe8\ +\xdd\xcc\xdc\xf5\xb9\x9a\x93\xc7\x1c\x8d,\xff\x05\xb5'\xf6\ +\xd8>{\xfb\x9a\x995\x062\x1f\x1bjd\xf5Z\xa0\ +\xfdz\xecv\xf3\x9a\x9a\xff\xde\xcfz\xa21M\x93 \ +\xd0\xef\xff;4\xc9>\xf8,Ah)\x0djs\xed\ +r\x86r#U\x22XS\x7f\xc9^w\x17=oc\ +\x82\xb9\xa1\xa6\xd5\x8e\xbd\x1e\xdd\x1b{]\xe10\xeb2\ +\x88o\x17j\x06\xd5\x98Z\xa7~\xb2\x81J\xcc\xc8\xae\ +\x17\xd8\x86\xd7\x92//\xb2\x8b\xbd\x9f\x10sCyP\ +|\x07\x22\xef\xd3\xe5\xc1\xfd\xdexg{\xdb\xda\xfd\x8c\ +K \x96\xadw\x1cT\xaf\x8b\x8aWF\xe7w\xf9g\ +\xeb\xf6\xad\xf7\xc9\x09{\xbcG\x13\xc9\xb7\x82\x8c\xf7\xf5\ +\x9a\x10~*a\xcfG\xee}\xbf\x99c\x10Q\x9e\xee\ +z\xeb\xadZL\xbf\xdb\x81Zk_+\x8dZ\xe7q\ +\xa1Y\x1c\xc3\xfc\x87\xb3\x97\x9d\xb2W7\xd3o\x7f\xfb\ +\xa2\xfd\x8d\x1e\xd6\xc8\xf4(A\x80\xdf\xbc\xb1\xcf\xdb\xe0\ +&?\xafk\xfe\xfb\xa3\x9dO\x00\xef\xbaM\xff\x83\xb5\ +\x01\x1d\x16Z\xc1\xc6\xb6u\x9a\xf8=\xfd\x1f\xb3K\xef\ +\xa1y\xb9\xe7\xfe#\xb4\xb2,\xefE\xc5\xe87\xef9\ +\xa3\xf6\x87\xfe'\xa3\x875v;\x12\xc1 \x08\x1c\xc3\ +\xe8bT\xf3\xf1\xda\x09\xe1c\xde\xec\xee\xe1oqx\ +\xf2A\x0d\xc2\xb1Y\x8d\xc6\xa7U\xfa\x1f\xb6Y\xebd\ +8m\xeb\x94t=\x8f\xe9\xe9uw\xd7b\x0e`k\ +\xa1\xa7\x987\xbf\x18\xdf\xce\xe7<\xef\xa0M\xffK\xf1\ +\x8f\x09\xa3\x1f7T\x1b\x13\x1bTn\xd5\x1e\x16\xfa?\ +\xa6\xde\xe9\xcf\xc9\x87\xdd\xd5'\xb6\x1f\xaaf\xda\xdc\xae\ +\xff\xab\xa9\xe1\xf5\xda\xfd\xa4\xc2\x16>\xf3\x03L\x86u\ +\x8c\xe9\xb2s\xa9\xd2\xe9\xbd_\xfe\xa7\xa5j\xa5=\xbd\ +\xc7\x193\xffQ\xdf\x16\xb5[\xf4\xbc\xe0\xdfE\x17\xd2\ +:\x8cl\xfc\xb8\xc3\x95:\xcc\xb9\xebX\xa75\x82\x0d\ +\xd8W\xed\xcc\xdb\x8e[\xdcn\xd1\xd3\x95\x8d\x09N\xe3\ +\x0f\xdb\x03\xd4#\xb6\x5c\xdfa\xa0t\xba\xa1\xdd\x943\ +\x1a\xd35\xb3~\x9a\xf0j\xd4Q\xbd:\xac\xbfg\x0c\ +s\xca\xfb\x89\xe1\x86\xebG\xf8\x9b\xaf~\xa2F\xfc\xb5\ +\xf65\xa9\xdc\xc0\xa2\x87\x91\x96\xb6\xdfr\x08[\x0d\x1f\ +\x08\x1c\x11\ +\xe8\x15\xd5\xf2\xbe\ +\xeaA:\x8d\xbbr\xdb\x05\x02\xf8\x07\xbb\xa2Z\xcf\xaf\ +\xba\x10K\xe3Pb\xfc\x0b\xe0^\x8fUm\xdf\xff\x0e\ +\x10\xc2\x12\xf0\x11I\x88{\xf0+\xeeV\x80\xb9W\x83\ +|`7K\xc0W,\x01\xfe\xc1\xb7\x5c\xed\xd3\xfd}\ + \x85\xc6\xa9X\xfc\x0b\xe0\x1ebK~\x0a0\xe7j\ +\x90/\xf8\xb1\x04\xe2\x86\xa5\xe0\x1f\xe2\x8b\xd5\xb6\xde\xef\ +\x07\xf94nK\xe0_\x00\xf7\x90_\x10\xaa\x00s\xad\ +\x86\x8a\x81P\x96@\x0e\x89\x08\xfc\xcfb\xfdI\xb9\x1b\ +\xcbu\x11\xe8\x90,\xcb\xa64h\x93,\x0bm\xea\xff\ +\xad\xf5\x114'Y+\xf4\x05~O\xff\x9b\xa5\x0e\xf5\ +=\xf8~e\xaf\xa1|\x00\xb85\x13\x83\x7f\xc81\xbb\ +\xa5\x00s\xfc\x05\xf8Fx\x5c\xa1G&\xad\xed@&\ +o\xecI\xa6\xed\x9fHf\x9cYJf^\xb6#\xb3\ +o\xed\x22\xb3\xfd\x0f\x90\xb9\x0f\xce\x91\xb9\xa1\xee\xe8\xe7\ +Y2\xdbo\x1f\xfa\xbd3\x99yi\x1d\x99\xe1\xba\x88\ +L\xdd3\x86Lfv'\x93l\xdbS\xcf\x85\xe7\xc1\ +s\xad\xaa\x04=\xf8\xd0\xb8\x16>\xfb\x90g\xf8\x1b\xe6\ +\xeb\xe9\xd2\xe7U\x17\xe3+u\xe702\xf3\xe2\x1a2\ +\xe7\xbe+\xc9\xfe\x18JrS\xbe\x91\xbc\x9c4\x92\xc7\ +\xce#I\x1e\x8f,up\xb9\xe8\xefrI^v*\ +\xc9I\x8a!\xd9\xefC\xc8\x9c\xa0cd\xc69K2\ +e\xdb\x002i\xb5\x01\xcd\x17\x14\x9a\x16\x92i\x5c\x0b\ +\xe7\xf28*\xc0\xdc\xe4\x07p\xd6\x11\xafNZ\xd5\x86\ +L\xdd=\x86\xcc\xbe\xb9\x13\xe3\x8b\x9b\xc1Bx\xe4\x94\ +\x8e\xe7r\x0e^A>\xc9M\xfdN\xe6G\xdc&\xb3\ +\xbc\xec\xc9\x14\xa7Ad\xd2\xca\x96\x94\xacPL\x19\xe1\ +(\x84{\xb0\x0b\x1e*\xc0\xbc\xe4\x83wt\xfe\x92\xed\ +\xfe\x87\xce\xa5\x05\x99\xff\xea\x16\xc9\xcdL\x92+\xbeK\ +\x1d\x88G\x00O\xc9{r\x99L?f\x8ee\x0c\x9f\ +\xffT\xfa\xde\x14\xc1CV\xf1\x1cR\xb8o\x90\xa4\x00\ +\xf3\x92\x03\xde\xbb\x229\xbd\x96,\x88~\x86\xcee\xde\ +\xaf\xc3\xbb\x88\xc1\xcb\xcb$\xd9\xef\xee\x91\x19g-(\ +]\x01\xf8\x81b\xc8\x85$\x1a\xe7|\xfc\xc3\x9d\x93\xaa\ +{7\x03\x9d\xaf\xa45m\xd1>/#\xd9_\x9e\x91\ +$\x87]\xa9x\x17\x1e\xc3m9\xf6\xb3\xfc\x8e\x83\xf3\xfd\x1d\xf6C\ +\xffB\x1a\xc8c\x15\xd5\xdd`W>\x8eK\x07\xc0=\ +/+\xb9\xb2\xd1T\xa1\xe3\x17\xd3\x00\x9bUT\x8bD\ +q\xf1\x8f\xe4=\xf0\xfc\xdf\xf5\xdc\x0b\x8f\x82\xf8(2\ +u\xd7\xa8_A\x03\x8a\x8f\x7f\xb4\x07\xa0\xeb\xfdJy\ +\x0f\xf6\x04//\x8b\xe4e\xa7\x90\xdc\xac\x14\x1c\xeb\xe1\ +\xe5e\xffR\x9f\x12\xfbs\x18\x99\xb2\xb9_E\xd3\x80\ +b\xe3\x9f\xb6\xf1*T\xcf\xe7\xf1Hnf2\xf6\x19\ +\xe6>\xba@f\xdd\xd8\x86cy\xe0\xb7O\xdd3\x16\ +\x9f\xc3\xd4}\xe31\xff\xc9<\xbf\x92\xcc\xbe\xb9\x83\xcc\ +{z\x19\xd9\xefo\x10]\xa4U\xdc\xbc\xd0\xc8{q\ +\x9dLZ\xd7\xa9\x22\xe3\x06\x8a\x8b\x7f\x88\xd5\xaei\x87\ +\xed\xfb\x8a\x18\xbc\x9ct2?*\x98\xcc\xf2v\xc4\xbe\ +C\xbc\xcf\xd6-\x04\xf2;\x04r<,u\x8arC\ +\xe0\xe7\xca\x968\xb6\x04r\x1ar\x02\x80>y\xf9\xd9\ +\xf2\x9f$\x97Cf\xfb\xee\xa6rP*\xc6O\xa8\xb8\ +\xf8G\x00~=y\xfbv \x0e\x98\xfb\xd8\x83Ls\ +\xf9\x97L\xb25,\xc2o\xb9\xce\x98nal\x19~\ +&\xaf\xefL\xa6\x9fZ\x80\xfd\xfb 7\xe49xH\ +\xfe\x00\xef\xa9 9\xa0\x98\xf8G\xf8\x00\xbe\xcbI\x8e\ +\x93\xdf>\xb2\xf3\xc8\xfc\xf0\x9bd\xda\x81)\xe8\xfc\xb6\ +\x90o\x5c\x9e\x9fg\x80\xf8U\xfa\xc9\xffpN\x89<\ +\xf3\x0b\x0ab_\x92\xc9L\xe3\x8a\xa0\x01\xc5\xc3?\x9f\ +\xef?\xbf&\xb7\xfd\x03\xdd\x11\xf2\xb6\xe0\xb9\x15\x1a\x7f\ +\xa5\xe9 y\xc3\xff\x90\x1e\xe1D\xe5\x99\xc8e\xf0\xc8\ +\x9c\x80\x83\xb4\x1c\xf8\xcd\xf1\x8f\xce~\xc6\x99er\x8a\ +\xe3\xf1H\xf6\xbb\xfbX\xbeS\xb9\x01\xbf(\xd6\x06\xe7\ +t\x85\x1e\x99~\xc4\x8c,\xf8\x1a!\x87u v\x92\ +\xfe\x13\xd9A\xe3\xe5\xed#V,\xfc\x83,Ez\x15\ +\xd8>2\x0f\x1e\x97\xccE\xbac2\xb3\x07}\xe6+\ +\x87\x96S\xb7\x0f\x22\xf3\xdf\x06\xc9\xbe\x1e4\xf2\x9e]\ +!\x93V\xb5&\xe5\xc8\xbf\x14\x0c\xff:8o\x87\xe4\ +\x14\xc8\x8e\xfb\xb0KdR\xe5\xc5\xd5\x04h\xa0)\x99\ +\xb2\xb1'\xd6\x0de\x1d`o\xa6\x1d\xfaW\x9e\xf4\xac\ +8\xf8\xc7z\xb4\x11\x95\xbb#\xdb.\xe1s\xaf\x10\xb8\ +\x17\xa0k\xec\xc7x\x1f\x223\x0d\xe4=\xbbJ&\xd9\ +\xc8\x8d\x07(\x0e\xfe\x11MC\xfe=\xe4V\xcb2@\ +\xdec\x9e\xaf(\xb8\x17\xa0\x81\xd4\x1dCIN|\x94\ +L\xeb\xe3f%\x93\xa9{\xc7\xc9\x8b\x07(\x08\xfe\x91\ +\xce\xbf\xca\x00\xdbg\xb2\x0c\xd0\xf3\xb1\xaeWY\xf2^\ +\x02\x1aH?1\x0f\xdb\xf4\xb2\x8c\x9c\xc0\xc3X\xbf\xfc\ +m\xf0\x0fg\xc3y\x84L\xf6\x12\xd8\xf7`\xe3)X\ +\x8eu\x09:\x07\x1f#\xe4\xfc\x80\x9c\x92v@\x8c0\ +\xd9\xa1\xbb\xfeA\xef?\xbfR\xea\xd8:\xe4\ +\xd0\xa6\x1f\x9eYu\xce\xbe\x00\xdd\x83\xbe\x22m\x9c\x00\ +\xe2\x97i\x07\xa7\xca\xba\xee\xca\xc7\xbf\x95\x1e\x99\x13|\ +B\xaa=\x80\x0165\xbeW\xa5\xe8r_\x04\xfe\xc1\ +'\xc0\xf9\xf1I\xca\x95\xf3\xc8\xac\xab\x1b\xab6\xfe\xe9\ +\xfb\xd8`\xb3K\xb7\x05h\x0f\xaemV<[_R\ +\xb0\xd6\xc7\xb5\x05\xa4\x1d\xb9\x8f\xce\xcb\xaa\x03T2\xfe\ +up\x0d\x05\xb8G/\x15\xfa\x91\x1d\x9d\xb6gl\xd5\ +\xe3\xfd|(\xf4yIwG\x15\xe2\xcc\xf8n\xb1\xf4\ +\xbc\xafr\xf1\x0f\xf1\x91=cp~\x9d4\xa3 \xf6\ +\x15}w\xa6\x8a\xf1~>\x80\x0c\xd8jJr\xd3~\ +H\xb5~NR\xac\xac~\x80J\xc6\x7fSd\xc7.\ +\x90:\xd6\x0b\xb9a\xf8\xfe\x5cU\xd1\xfbK\xe0_\x17\ +\x9f_\xf6\xc7GR\xad\x1f\xf2\x16Sw\x0e\xaf\xc2\xf8\ +\xd7&3/\xae\x95Z\x07\x86\x5c\xcc*\xcb\xfb\xf9\xb0\ +\xb2\x05\x99\xfb\xf8\xa2T\xeb\xe7\xe5f\xcaj\xfbT.\ +\xfe-\xb41\x0e\xa5\x1a\xc8^\x84|\x5c\x9c\x93Y\xd9\ +8\x94\x05\x10\xee\xb2\xfd\xf7K\x87\x7fv.u\x87\xb8\ +*\xe3\xff\xf6\x1e\xe9\xd6\x9e\x9f\x8ds\xed\xaa\xfc\xf9G\ +\xf4\x0by\xaeR\xc5\x03\x10\xdf\xcc\xbch+\xcb\x19\xa8\ +\xba\xf8\xcf\xcd\x90w.D\xa5\xe1\x1fp(\x95\x0c\xc4\ +\xf8_\xfbg\xe2\x1f\xd9~`;T\xc2\x9d\xe9\x0a\xc0\ +\xbf\x94:\xd0\x9f\x8c\xff\xbcL\xaa\x96\xce\x9f~\xfe/\ +\xfd\xa1\xf8G6\xa3\x9cb\xa0\x95\x0bh\x0fp\xec[\ +\x9a\xc1- 3=VWi\xfcg]\xdf*\xe5\xda\ +e\xd6}\x14\x03\x90\xed\x0e\xf9<\xd2\x0c\xea\x0c,\xa8\ +\xba\xfa?\xf0\xbe\x0b\xab\xa4\xce\xf7\x85\x1a\xbdU\xd6\xf7\ +\x8fA\x17\xe7r\xe6=\xf7\x96\x0e\xff\x10\x03t\x99V\ +\x85\xf1\xdf\x14\xdf\xb3\x95\xf6\xce\x1c\xd4{\xabRq\x7f\ +a\xa0\xef\x0e\x16\xc4\xbd\x92j\xfd\x90/\x97\xe24\xb0\ +\xea\xfa\xff\xe8\x9cX\xf0cJ38?>\x90\xc9\xf6\ +\xdd\xaa\xb4\xff\x1f\xfc\xb7\xd2\xd6\xa6\xe5\xfc\xf8\x88k\x9d\ +V\xd9\xf8\x0f\xd0\xbf}W\x8cGi\x06\xf0\x8d\xb4#\ +U0\xf7\x83\x0fh\xdeR\xeb\xfeh\xc0\xbd\x22|\xa7\ +\xb1\xaa\xe2\x1f\xe4\xdfj\x032?\xc2O\xaa\xf5\xc3\xc8\ +\x09p\xa9|\x8f+5\xfes\xee\x80\xde#\ +3\xed+ \xfe\xab\xe8\x99\x96\x14 \xe7\xcdy8\xc9\ +aI\x97\xf3\x08\x03\xe7}\x1c1\xfb=\xee\xff\x08A\ +\xd2\xea\xb6\x94M\x83\xf7J[\xa06\xd3o@\x13r\ +\xaam#'\xde\xafx\xf8\xb7nN\xd5S\x8b\xbaG\ +\xe6\xdc=\x8a\xefHA\x0d>\xf0q`?\x1f\xbfO\ +\xdf\xf2\xaaK\x13YW\x99\xb2\xd54\x83\x9c\xf7\xeb[\ +\xe5\xc5#+\x06\xff\xfc\x1e{\xe5\xd1M\xf9w\x01\x04\ +s!9l\x1c\xe7\x87\xfb\xae\xe0\xeb\x85Zx\x90\xef\ +\x04\xf7\xc5\x92\x90\xee\xcf\xe2\xf7\xd7*F\x13\x95\x8fc\ +\xd1\xeb\xd3\xa1j\xd8\xcaX\x13\x8a\x9b\x1c\x87\xfb\xcc\xc9\ +)\xee!\x1f\xfc\x0b\xf6TD\x9f!\xa7\x15d\x1c\xf4\ +\xd9\x93\xf8\x9e:\xd8\xc3[L\xca\xac\xf1\x0b\xb9\xf2\xdc\ +\x8cD\xb2 \xe6\x05\xce\xff\x85\xfb\x1f\x10C\x80\xef&\ +\xadmO\xf7\xea\xe4\xd3\x84\x82\xd0\x03\xd4\xb0\xdd?\x09\ +\xfbke\x1d\xd0kP\x8e\xf6\xaet\xf8\x17\xec\x99\x8a\ +e\x9a!\xa2\xc9\x81\xb8njN\xd0Q\xaa\xa7bV\ +2\x99\x1f~\x0b\xcbs\x89\xf0 C,\x08\xbe\x03t\ +\x03\xef\x85\xde:\x99\x977\x90i.S\xc9d\xfb\x7f\ +\xe4U'Av\xdc#\x1e&\xeb\x80\x9er\x10/\x91\ +c\xceS\xf9\xf0O\xf3Y\xd0a\xe0\xbcA\x1fT\xa8\ +K\x07\xfd\xcd\xb8\xe9?J\xc85\xcc\xab\x1c{K\xc6\ +\xab\xd0\xb3\xe1,\xcbe \xbb\x0ah\x22\xf7\x89'\xd5\ +\x8f\xb32\xf4\x04\x9a\xe6\xa1\xde\x07\xe7\xe7g\xb9,+\ +\xe7\xee\x11y\xfb\xba$\xc4?\xe5\xa7\x87\xf3\x09\xf9:\ +\xf9o\xee\xa2\xf3\x16_\xe6\xfdu\x9c\xa3\x0b\xb5K\xcb\ +\xb4St\xb1,\x97w\xad\xdf\x9c{\xa7*\xe7\xfcC\ +/:[C2\xcb{\x93\xdcj@r\x12>P=\ +b\xe4\x9b\xef \x19\xfe\xe98\x1d\xf8\xdb\xcb;p\xfd\ +\xe2\xb2\xe6\x0c2\x04\xe2\xe0H\xa6\xcbm\x94\xea\x1f\xaf\ +\xa8\xfa\x9f\xd4:\xa1\x96\x0d\xd8x\xf2\xaa]\x0c\xcf\xc9\ +\xf4\xdcP\x11\xfa\x8c\xe4\xe7_\xcaX\x15\xf0\x8a2c\ +\x94\xfc\x1a\x10H\xaf\x93\xd7\xc0\xb91H\xee\x96\xe0=\ +\xd0\xf3\x9b\x9f3\x22\x97\xfe{\xba\x85\xf2\x18\xees\x03\ +\xbd\xcb\xb3n1\x0c\xe8\x0f%\xe3=O\x19\xf1\xdfL\ +j\xf9\x0cr\xa2\xa8\xd7\xa1\xf8g\x83]'\xed=X\ +Q\x03\xe7F\x80\xfeWl\xcf(9\x96\xed\xbb\x0b\xdb\ +\xd0)\xdb\xfac\xba.\x9fO\x81_\xfb\x9b\xd2\x7f\xe1\ +y\xa9\xce#q\x7fxN\xc2{\x99j\x18\x89\x5c\x07\ +\xaeiVa\xbd@\xca\x85\x7f\xa87\x01\xe7\xaa<\x03\ +\xdfQBzb\xa9:\x00Z\x1b\x9c\x1by\x0e|f\ +\x84u?~\xad\xa17\x81\xf8o\xb8\xc9_1O\x83\ +\x1c\x0c\xb0U!\x06\x09\xb1H\xfc\x1d\x0bm\xda\xffH\ +\x83\x05m\xdb\xa2gB\xdd\x15\xe8'\x0e}\xbe\xe1=\ +\xb2\xd4\xae)m\x80\x0e\x9by\xdeZ\x0e\ +;\x90\xcc]\x87i#\xd3s=\xf6K\xe6\xdc;\x89\ +{\x89s\xbeGQ5\xfcd\x88\xdf\x949\xd0\x9c\xa0\ +N\x1c\x0b\xf8\x93\x22\xe0\x9f\xaf\xa3\x87]*\xf7R\xd8\ +Q\xc1\xe2u\x00\x9a\xae\xe4a\x1f\xf3\x07\xe8K\xd0\xf7\ +\xbd\x04\xcf\x11\x87\x7fq\x83\xcb\xa5r\xb3*\x12\xcfb\ +F\xde3o\xda\xc7\xaf@\xfd\x7f@\x07@<\xaf\xbc\ +2\xaeT\x1d\x00\xd7\x80\x18+sM\xccb\xefCg\ +\x19t\xf0\x12\xef+/\xfe+i@\xadh\xa8\x11\xa8\ +p\xfd\xbf@\x07\xd87\xa1\xdc}\x8f(\x1d`\xb1h\ +\x1d\x00\xc9V\x88\xf3\xc8\xb3\xb7\x16\xe4\x92%o\xe8\x22\ +9\xffW\xa0\x01\xb8\x87z\xe1\x0a\xd9\xff\x0fx5\xda\ +Wiz\x1a`\xdf\x95\x98gJ{\xffE\xdc\x00\x9d\ +.I\x94\xdcTp\xfc\xffb\xdc\x97\x1f\xff\x00\xd6\xcd\ +\xc9\xdcP\xb7\xb2\x17\x83\xe4&\xf4\xea\x84:\xa7\xb9\x0f\ +\xceQ\xf9\x0a\xd6B\xbeK:\x1e\xce\xd7\xc7\xe55\xb2\ +o\xee\x14\xcdk\x14\x15\xffh\xaf\xf2\x9ezUF\xdd\ +\xf2\xf2\xe3\x1f\xe7\xac\x8b\xb8\xaf\x8ct\x02\x90\xe1\x18\xdf\ +\x88>\xe0o ?\x11\xd7g\x02\xbc\x8b\x8aY@\x1e\ +\x9cC7\x92\x93\xf8En[\x09w\xe22N\x89\xb9\ +\x13\xa7\x80\xf8\x87\x5c\x9el\xbf\xfd\xb8\x1eh%\xdce\ +\xe3\xe3_\xf2\xfe\xcf\xa0\xaf!\xbc\x82_\x1b|\x01\x90\ +\xc3\x086\x01\xe8\x85i{\xc7\xd1\xf8n.YL^\ +J\x9fBi\x03lq\xb1\xf1q\x05\xc3?\xd0}\x86\ +\x9b\x15\x95\xc7P9\xb1j~\xff\xe7r\xf4\x7f\xa7x\ +v\xc6\xd9e\xb8\xff!\xaeA[\x98\x87Q\xce\x1c\x0c\ +\xa8}\xe2e'\xd7=e\x7fyJ\x9f%\xd1\xb6\xa6\ +\x22\xe0\x1f\xfc\x9cy\xe17i\x1b\xa5R\xf3\x98\xf8\xfd\ +\xdfM\x10\xb0$\xff\x9e\xaeP\x8e\x85\xf0\xfcK\xcb\xd5\ +\x16\xf4\xc7\xe9\xe08=\xc8>\x90\x1b\x98\xa7\xc8\xe8\x03\ +\x86\xfb \xb8\x8f\xa7\xa8y+\x00\xfe\xc1w\x04>\xa5\ +\xc2\xde\x93\x95\x83w>\xb0h\xdc\xb7D\x10-\x97g\ +\xae\xd0\xc7>R\xf0\xb3\xe2\x5cNA\x80\xdfa\xff\xaa\ +@Lv9\xe5W\x82\xbb\x1c\x10\x03\x82z\x98\xe0K\ +\x07?\x1c\xc4\xb8\xf7 \x0a\xd2\x0eL\xa2{6T\ +\xfa\x1e\xfc\xa9\x90G\xe3\xb8\xf0\xec\x0b\xe1\xbf1\x82P\ +\xe9\x9f\xaf+$C\x85Aa\xed\xa1?\x05Bi\x1c\ +\x17\xc3\xbf\x10\x0d,@\x90\xaf\x00s\xad\x06\xf9B>\ +\x8d\xdb\x12\xb8\x17\xc2?\xd8\x05~\x0a0\xdfj\x90/\ +\xf8\xb1\x04l>QC\x80\x06\x86#HQ\x809W\ +\x83| \x85\xc6\xa9X\xdc\x0b\xe1\x1f\xfc\xc2\xbb\x15`\ +\xde\xd5 \x1f\xd8\xcd*\xf2\xf5\x8b\xc5\xbf\x10\x0d\xe8!\ +\x08Q\x80\xb9W\x83l\x10B\xe3\xb2L\xdc\x8b\xa0\x81\ +\x81\x08\xbe)\xc0\x1a\xaaA:\x88E0\xa0<\xb8\x17\ +\xc2?\x83E\xe9\x8c\xe9\x0a\xb0\x96j(\x1f\x00\xce\xe6\ +\xd38,\x17\xfe\x85h@\x15\x81\x03\xab\xda&\xacJ\ +\x90O\xe3LU\x1a\xdc\x8b\xa0\x81:\x08vT\xd3@\ +\x95\x80|\x1aWud\xc1}5\x0dTI\x90+\xee\ +\xc5\xd0\x80\x03\xabZ\x1fPDH\xa7q#W\xdc\x8b\ +\xa0\x01\x90)\xa0W\xc4*\xc0\x9a\xab\x81\x82X\x1a'\ +2\xc9\xfbr\xd0\x00\xe8\x94`WT\xfb\x07*\x1fB\ +h\x5cH\xa5\xe7\xcb@\x03|\x1f\x11\xf8\x96\xaa}\xc5\ +\xbf\x1eR\xe8\xbd\xd7\x13\xc4\xc9\xaf\x18B4\x00~E\ +\xf0-C|\xa1Z7\xacx\xc8\xa7\xf7z8K\xc0\ +\xa7\xfb\xabp_\x0a\x1d@l\x09|E\x10c\x962\ +\x87\xa4\x1aJ\x81\x13\xd5\xa3zT\x8f\ +\xeaQ=D\x0ef\xf1\x8fe\xf2\xd3\xe0\xe2\x9fK\xf0\ +\xe7\x98\xe2\x9f\xd5\x84?\x0b\xcb\x83\xb2\xe4\x87\xb0\xbc\x11\ +\x96G\xc2\xf2\xaa\x84<+6A\xe5\x92\xf2PX^\ +\x0a\xcbSay+,\x8fK\xc8kay^\x5c\xde\ +\xb7E?L\x09j\xdf\x19\x84>\xf5{\xf4\x8b\x96\x9d\ +(\x90t\x88\xd0[\xfeB\xd0\x17\xc1r\x04G\x11\xdc\ +D\x10TIp\x93\x9e\xc3rzN\x7f\xc9K\xc7\x12\ +z\x0e\xf8\xe0\x0d\x11\xaccQ~y\xb0\xdd9\xac\xca\ +\xb7)\xf9\xc0\xa1\xe7\x14B\xcf\xd1\x90%\x107(\xef\ +>\x08\xad]\x1f\x81\x13\x828\x05X\xa7\xa4\x10G\xcf\ +Y\xbf\xbc{ \xf0\xf7J\x08& x\xa5\x00\xeb\x91\ +\x16^\xd1kP\x92d\x0f\x04\xd6^\x13\x81-\x824\ +\x05X\x83\xac\x90F\xaf\xa5fi{ \xb4\xf6-,\ +E\xe8\x9d)?`\xd3k\x12\xb9\x07\xac\xe24o\xfb\ +\x9b\xad]p\x0flY\x22\xce\x82\xc0\xfa\xe1\xac\xfc\x0e\ +4/\x0e\xd2\xe85\x16\xae\x9fU\x9c\xcfWe^'\ +)\xbcb\x09\xc9\x05\x16%+\x9d*\x7fn\xbf\xec\xfe\ +\xbc\x13\xab\xb8~\x00\xfa\xc2/\x94\xef\xbaE\xb58\xa0\ +\xee\x06\xd4\xd2Z\xd9\x92\xaa1\x08}\x9c\xa0\xd6,\xfc\ +]\xc5\xf5\xfc\x8c\xa3\xd7\xcc_\xff\xfa_\xb2n\xbav\ +\x14\xf4\xd6\x84\x9a\x0a\x19\xee\xd6dN\xe0!\x5c\xdb<\ +?*\x88d\x7fzL\xb2\xdf?\xc0\xfdgr\x1f\x9c\ +\xc5u\x0f\xd3\x0eM\xc7\xf5\x94q\xcf;\x5c\x0bIn\ +{\xb1\x8eU\xa4\xcfWl\xbe\x0f\xae\x97\xa5\x87k\xf8\ +f\xdd\xd8\x86\xd7I\xf5Z)\xbb\xe7\x04\xd4\x97\x80\xbe\ +O\xb9\x0f\xddp\x0d\x02\xa8\xf5&\xa7}\x08a\x15\xd9\ +2\x15\x14\xcf\xd4\xa5\xeb'\x9a\xe2Z\x87\xd03C\x96\ +\x01\xf56\xf2\xdf\x06\xe1\x9aET}a\x99\xee\xdd'\ +\xb3\x8a\xec8\xf9\xdb2\xb8\xdec\x1b\x5coI\x9e\xb5\ +\xc0\xf1>\xe4e\xe3\x9a\xb0P\xb3I\x86\xda\x03\x1cV\ +\x91\x0d+\xdf\xb5\xa39A\x9d\xcb\xdcG\xe7\xe5\xda\x03\ +Cx@\xbd2\x5c\x1bSz\xfe\xc8\xb7\xdf\xe5\x8aw\ +\xa8\x87\x0a\xfc\xecW\x0c\xa8\xef\x96q\xd6B\xda\xf9\xf2\ +}\x17r[{\xf2\xa6^\xb8N\xe4\xaf\x1c\xd0\x97\x19\ +j\x9bIA\x07Ar[?\xd4\xe1\x5c\xdb\x11\xf7\x87\ +\x96xp\x0aHNR,\xc9~w\x9f\xcc}|\x91\ +\xcc\x09>\x81\xe5\x1e\xd4\xe0+\xf8\x16I\xd5\x84\x95t\ +\x0f\xd2~\x90i.\xff\x96\x97\x1f\xc8o\xfdH\x87\xc9\ +\xf6\xdf\x8ff\x22\x81L\xcb\xcd\xc4\xb5\xc2q\xdf{D\ +/T\xefJ\xbd\x22\xfc!\xfd\x07\xeaiA_\x1e\xa8\ +/\x0c\xfd\x02%\x19\x05q\x11\xf8y\xe5\x90\x0b\xf2Y\ +?\xd4\xfc?4C\xa2\x1eB\xec\xcfad\xfa\xd1Y\ +t\x8fF\xedR\xeb\xba\xf2u?\xa8?\x0c\xb4\xc1\xcb\ +\xcb,\xf3\xf9\xb9\x0f\xce\x88\xaf\x15^\x11\xeb\x07\xba\xb7\ +m\x8f\xf8\xdd\xbd\xd2'\xc6\xe5\x90\xb9\xa1\xeeT\xff\xca\ +\xf2\xea/\xb0G\xd6\xfa\xb8>\x7fY:\x04\x9c\x19\xd0\ +\x19%\xac\x9b)\xfb\xfa\xa1\xcf\xe7\x99\xa5\xa5\xcb9\x1e\ +\x17\xf7m\xa6\xfa\xc6K+\xaf)]*\xfd\xc8,\x5c\ +\x1b\xb8\xb4\x01\xfc\x03j\xc2J\xb0\xc72\xae_\x17\xeb\ +8e\xf5\xba\xc9{\xe9C\xd7\x11\x95C\x9d$\xf4\x8c\ +\x0c\xb7\xe5X\x17\x14\xbb\xdd9\x19\xa2\xfb\xe4\xca{\xfd\ +\xe8\xf9\xd03\x13\xd7\xdd\x1538I1d\x8a\xd3 \ +\x89\xeb\xb8JD\x07\xe8|C\xff\xa1\xd2\x06\xeeMU\ +\xb6<\x94y\xfdY\xd7\xb7\x95:\x8fl\x9f\xed\xf2\xaf\ +\x17\x8cd\x5c\xca\xb6\x81\xb8&\x9f\xb8\x015\x19\xa9^\ +\x83\xa5\xee\x81l\xebG6\x08\xf4\xb7\x12\x8f\xfbXJ\ +G\xaf\xa0z\xc9\xb9\xf7O\x8b}7\xf0A\xa83_\ +\x06\xddI\xbf~\xdc?\xfd\x1fl\x9b\x8a\x1b\xd0C\x07\ +\xfb6*`\xed\xb0.\xb0\x87\xc1>\x16\xbd\x01<2\ +\xf3\xbc\x8d\x98^\xe9rX?\x9c\xfd\x1dC\xb1\xee)\ +nd^\xb2-\xeb\xfd\xd2\x03\xe8\xda\xcc\x1e\xa5\xda\x96\ +\xa0\x8f\x95\xa1\x0f\xca\xb4~\xa8\x09.n\xff\xa1\xb64\ +\xfc\xbb\xfc\xf8\x9e0P\xb2\xa74[\x03\xf7M\x11\xec\ +\xcb!\xd7\xf5kc=\x0etx\x91\xeb\xcfJ!S\ +\x9d\x87W\xe0\xfa\x9ba=\x19\xf4hq#\xef\xc5\xf5\ +\xb2\xce_\x85\xad\x9f\x9b\x99\x8c\xce\xc7\x90\x8a\xad\x8d\x88\ +t\xc2\xbc\x17\xe2{,C\xbf\x95B_jE\xac\xff\ +\xf0L\x5c'_$\xfe\xa1o\xc7\xde\xf1\x15\x8b\x7f\x90\ +?o\xee\x8a_\xffS/\xcaoZ!\xeboJ\xf5\ +\xb8\xcd\x11c\xa3\x22}\x1f\xec\xbb\x8a\xe3\x7f\xba\xd8F\ +\x04;Y\xdc\xc8\x09:^q\xf2\x1f\xfc<\x9bz\x97\ +j\x8fH\xa8\x83I\x07\xb0\xff\xce#K\xd5=\xb3\xae\ +0+N\xfe[Q}G\xf2\x9e_\xc32\x08\xf4\x80\ +b\x80~\x07\xf4\x87m\x9e\x8a\x88\xeb\x80\xee\xe9\xed(\ +v\xed\xd0\xeb\x00\xf7\xe7\xac\xb0\xf5S\x00>\x9fd\xfb\ +\xae\xb8\xdfw\x09\x80~\xbd\xc2\xfdA\xe5D\xfb\xa0\xdb\ +\x16D?\x13\xbb~\xd0=\x93\xcb\xf6\x85\xc8\xbc\xfeB\ +?\x85E\x13\x04\xda\xc5\xfb\x06T\x18\xed\xeb\xe0\x1e\x06\ +\xa5\xf5:\xc1\xb2\xafl?\x88\xe4\xeb\x17\xd7\x0b\x01\xf8\ +\xc0\xd6\xfe\xd8\x07\x0b1-\x0a\xe7\xcd\x8b\xc7\xf7\xe4\xb9\ +\x0fp\xee\xb7\x0fA\xe7+Z\xec\xdaa_\xa0\x7f\x87\ +L\xf6\xefr\x81\x9a\xc7\xd0\xcbl\xa31\xd5\x83P\xf8\ +,\xc39\xbc\x016 \x0f\xf3\xa2\x82\xf8\xb7\xd8\xde\xcf\ +\xbe\xe5L\xa6\x9f\xfc\x0f\xdbi\xb8o\xcc\x0a=\xd9\xf7\ +\x01\xbd\x0bb\x81\xf9Q\xc1\xe2\xd7N\x92\xb8\x973\xee\ +\xe1(\xad\xfd\x8b\xe8\x06\xde\x03\xfa+\xac\x0d\xe2\x91`\ +\xc7C?\xd0\x12{\x8a\xf4+\xe8\x0d&\xfa\x10\xb2q\ +\x0f\x9d\x82\x98\x97d.\xb2\x85Rw\x8d\x94\xde\x16D\ +\xef\x05yS\x9a\xbd\x89\x07\x8fKf]\xb6\x93T\xef\ +*\xbe\xfe\xe5T\xads\xe8A\xc5\xf9\xf9\x19\xd7\xbb/\ +~\xa6\x84\xf4)>\x1f\xfa\xfa\xba\xf49a\x9a\xe4 \ +}qv\xd1\xfeI\x14\xd7\xa6i\x10\xf1P\x88\xf3\x14\ +\xc4\xbc(\xf35\x10[\x95\xac\x17\x97(\xfc\xeb\xe2\x18\ +<\xf4\xdf\x12\x89\xce\x9f\x9f\xa8X4\xff\xd9\xd07\x16\ +l\xc0\xcc\xa4\xb2\x97\x9f\xfa\x9d\xea\x93gI\xf5\x81K\ +;0\x19=\xab;\x89\xed\x93\x12\xfd8\xb4\xa9\xbfC\ +sI\xdb7\x1e\xc7~%\xf1-\xc3\xf9\x93@\xe6\x95\ +N\xff\x88>\xb3\xfd\x0f\x88~~\xbe\x90M\x07}\xee\ +O/.\xd97Y\xc4\xc0=\x12\xd6\xb4\xa3\xe2\xa2X\ +v=\xa7\xe2\xda\xa1\xe7\xc9L\xcf\xf5d\xfa1s\xbc\ +'\xe0\xbb\x05^\x0a=\xa7\xa0\xf7\x94$\xeb\xa66\x98\ +C\xf9\x9aJ\xd7w\xcb^?\xf8\x15\x8e\xcfE\xc8\x16\ +\xad\xd7g]\xdfZ\xb4~\xd8+4OI\x06\xf8\xeb\ +\x8a\xceL\xc7\x92\xfd\xe6\x11\xaf\xa0\xfaI\xe4\x89\xec\x03\ +X\xfa\xe0\xe1\xe7S\xfe\xe5r\xf1X\x91\xf8\x87~s\ +\xdc\x94\xaf\x22\xdfD\xf1\x80\x16\xd4\xd9\x04\xfb\x03\xf1F\ +IF\xa6\xe7\xba\xc23\x8f\xf1\x1f\x17Q\xf6\x97$Z\ +:\x0f\xd3Pao\xb2\xf2\xf1U\x91\xfc\x1fb3\x90\ +g\x80\x07\xf4AK\xfe\x8a}\xdc\xd9\xb7\xf7`\x1a\xc5\ +:\x1d\xf8_\x90\x8e\x07\xfd<\xcb\x9cb^6\xf5=\ +9\xaf\x1f|/9\x81\x87e\xf1\xad\xc3\xdaK\xc6\xbf\ +\xd1\x1c3\xce-\xc7\xb178\x0b)\x8e\xbd\xe9x\x15\ +_\xa7\xd1\xa5}\xdf\xa3%\xeas\xcda\xc5\x90\xc9L\ +\xe3B]H\x1e\xeb\x87\x98`\xe6\xf9\x95\xb2\xf6\x99\xe6\ +\xe7\xcf\x8b\xf87=\x81~Zz\x98\xbe`\xde\x18`\ +\xbf\xd1\xdf\x80\xae\x07}\xb4\xa8\x9e\x8d\x8f\xd0\x99\x89\x17\ +\xe9\x0f\x83\xd8\x18\x15\xe3l&\xf3\xfa\x81\xc7C\x1c-\ +\xc5i \x8ds\x99t*\xfe\xdd\x01\xf1\xf9/\xb4\x9d\ +\x9b\xff\xee\x1e\x96\xf3\x05_#\xb1\xcf\x89\xeaw\xa4S\ +\xc8\x0b\x81\xf7\x00\xdf\x009\x9duu\x13\x99\x17\xe6\x89\ +\xf5\x1e\xf0\x8f\xe6\xdcq)\xc2\x11\x7f\xfd\xa5\xd8\xed%\ +\x06\xe2\xed`\xcf@l\x1cb\xc2\x98\xff\xc8\xeeS\xe7\ +\xe7\xbf\x94\x9e\xff\x04\xeb\xdf6\xa0\x98\x8c\xe7$~\xc6\ +g\xbfd\xbfW\xc1>\x8e\x14\xcd@\xec\x87\xa2\xfd\xa2\ +\xbf\x85\xb3\x94\x89t\xb4b=\xfa\xd09\xc2\xfc?7\ +\x13\xef\x19\xce\x0b@2\x13\xd6\x0c\xba<\xee\x01Z\x98\ +\x03'\xd3\xba\xf9\xc0\xcf\x7f*=\xff\x8d\xbf\xfe\x0cV\ +\xd1\xfaA\x0f*\xb1~Q\xdf\xd5\x15\xdf/\x12~\x07\ +v\x05\xf4\xa8\xdbj\x8ay\x09\xf0\xc8\xd4\xbd\xe3\xb1N\ +\x05{\x86\xf5\x05\xf8[\xbeM)\x9fu\xf3\x81\x9f\xff\ +Vz\xfe#\xce_3\xc1k\xc68\xca\xcd\xc0zh\ +\xf2\x06\x89\xec\x8b\xb2Ad\x8f6\x1dy\x9c\xed\xb2`\ +=K\xc2\xfcW\xe0]\x80\x93\xd4]\xa30\xe0x\xe6\ +J\x89s\x0c\x14\x11\x84\xf3_\xcb\xce\x7f\xb6\x14\xc0\x91\ +\xe2\xf6?\x95\x14\x84\xf3\x9f\xff\xd8\xfc\xf7?\xfd\xfeC\ +\xf5\xfd\x97\xea\xfbO\xd5\xf7\xdf\xc4\xde\x05\xfb\xa3\xee?\ +\x8a\xd8\x83?\xee\xfe\xab\x98=\xf8\xa3\xee?\x97\xb2\x0f\ +\xbf\xdd\xfdw\xba\xcc\x00I\xd7?\xe0\xd7\xd9\xe1\xd7\xd3\ +\xe1\xd7\xd1\xe1\xd7\xcb\xe1\xd7\xc5\xe1\xd7k\xa8\xae{S\ +\xc9\x83I\xfd(\xc4G0\xf5\x93_\x1f\x83_\x07\x83\ +_\xff\x82_\xe7\xc2\x84\x8fw\xa8\x13a\x84\xc0\x9c\x10\ +\xa8\x13\xd1\xaa\xec:\x11\x22\xce\x85\x06\x82A\x086!\ +\xb8\x8e\xe0\x19\x82\x08\x16\xc5\x83\xe5\x09\x11\xf4\xb3\xaf\xd3\ +\xef\x1aD\xbf[b\xba\x171os\x04\x81\x08R\x7f\ +\x01\xaf\x12\x86T\xfa\xdd\xe6\xc2\xeb\x90`\xee=\x10\xf8\ +\x22(\xa8\x84y\x0bC\x01=\x97\x1e\xe2\xd6 4\xf7\ +\xc9\x08b\x14`\xde\xc2\x10C\xcf\xad\xd8\x1aD\xcc\x9d\ +\xa5\x00s\x15\x07,\xe15\xb0\x8a\xd3\x8c\x22\xee\xbb(\ +<\x14\xa3%\x16u>|\x15`n\x92\x82/\xab\xf8\ +\x99\x863.\xdf\xb3Z\xccw\xa6#\xe4O\xd3&%\ +\x8b3\x8b\x85\x02z\xce\xfc\xbd\x0f\x94\xcf\xbc\xe9\xb8\xf6\ +\x0a}\x9cW\x011\xdb\xac+\x0ed\xce\x9dC\xd8\xdf\ +\x9bs\xff\x14\x99\xed\xbb\x1b\xdf\x8f\x06\x7f[a|G\ +:\xbfS \xabH6\xc9\xce\xdf\xd1\xbc!\xef\x16\xe6\ +\x9c\x1bv\x11\xc7vx\x05yb\x82\x15<\x92\x97\x93\ +F\xb2\xbf<%\xb3|\x9c\xb0\x1fU\x8a\xbc\xa0TV\ +\x91\x5c\x95m\xcf\xd1{S\xf7\x8c\xc1\xf1\x8b\xd2\xee\x8f\ +\x88\x1b\xe0\x93\x87|\x10|\xaf\xa0|y\xad|\x9d@\ +J\x1a\xa7b\xad\x10'\x87{~\xb2\x0d\x1e\xce\x9f\x01\ +\x7fn9\xe8\x89\xaf\xcfH5w\xf0\x1fC.\xbaX\ +:\x91b@\xdc5m\xffDI\xd7\xc0\xd7\xc5\xca?\ +\x7f\xeb\xe68\x97\xa9\xb4\x5c3\xbc\xab\xec\x5c\x9c\xe3\xc1\ +\xcf}\xc4\xf7\x02\xca\x88\xe3\xc3\xdf\xe2\x9c\xa0\xb2i\x89\ +\xafG\x96s\xefu\xf0\xbdX\x88?\x8b\x9f\xc3'\x1c\ +\xe7\x85\xbc\x0b\xb8\xab\x02yS\x90\xe3\x02\xb9\xdbp\xcf\ +\x0a\x9f\x95R\xee\xa1\xb2?=\xa2cA\xa5\xae\xe1U\ +\xb9\xe7O\xe7!\x88\x8b\xaf\xc3\x9ar\x82\x8e\x15\xdd\xf7\ +,\x96w(\xc0\xff\xd1\xb9I?2\x13\xf1\xa0'b\ +\xd7\x00\xeb/\xe3\x9e@\xf9\xe7\x8f\x00\xf8\xb9\xc8\xb9g\ +\xa7\x91\x99\x1e\xab\x0b\xf3\x08\xca|\x16\xce{\xeb\x8ep\ +\xe1#\xf2y\x90c\x06\xb9\x87\xa5\xc4\x06\xcb7\x7f\xfa\ +\x0e;'9\xae\xe4\xdc\x0b\xf2\xa9<8\xfc\xb7\xe5\xe0\ +\xe3\xb0\x06\xfb\x7fH\xf6\x87\x07\xa2q\x10|B~\xfb\ +\x8f\xde\x95yi\x9d\xc8\xf7@^(\x8e\xc5K\xa3\x13\ +\xc0\x9d\x97}\x13D\xe6{Q\xb9a\xdd\xc5\xe1\xb3|\ +\xf3\xb7n!\xf2^\x07\xc4\x1f\x0b\xf3H\xca;w>\ +\xac\xd0\xc3:F\x89\x81\xf0\x0a\xb9\x1eb\x9e-\xf9\xfc\ +!/\x1f\xf1\x10\xce\x8f\x92\xe7\x16\xee\xb9\x83\xee S\ +\x5c\x13\xdf\xb5\x9e^\x227\x11\x06\xe8L2\xcf\x1fr\ +\xfa\xb6\x0f\x11y\xaf+\xdbo\xbf\xec\xf1w|o\xae\ +\x1b\xce\xaf,A\x9bO/\x8b\xcb_+\xc7\xfc)\x1a\ +\x15\xa5\xdfd^(\xf3>\x9c\x04@\xe7H\x22\xbe/\ +< G\x0a\xe7\xe3\xc8:\xff\xfd\x93\xf0\xfd\xb1\xe2\xc4\ +\xcf\xa5j<\xc8\xe1>\x09\xce\xef\x12q_\x9d\xfd\xee\ +\x1e\x95\xe7$\xe3\xfc!\x87\x81\x97\x97Ur\xff/\xad\ +\x95\xcf\xfe\xafi\x87u\xea\x12\xfb\x1f\x19 \xfb\xfe\x8b\ +\xc8w\xe1\x8f\x9c{'e\xcfy\xc0\xf9D\xbdpN\ +\x9d\xf0\xa0\xee\xeb\x89\xfc^\xb9\xf8\x0f\xe4W\xe6G\xde\ +\xc1\xb6\x09\xbe\x9b\x02\x80\xfe\x1f\xf2\xb4\x81ve\xe3?\ +\xdad\xfa\xa9\x85\x22\xef\xfbg]\xdb\x22;\xff\x01\x80\ +\xdc\xa1\xf5\x9d\xa9;$p\xb7\x04\xc9M\x0c\x90s\x22\ +\xd3}\x12]L\xdf\x90\x9f/\xf8\x1e\xce\x19\x17:c\xf9\xaf\ +\xfdp\xce\x1b\xf0\xe5\xack\x9b\xb1\x1e\x09:2\xce\xb3\ +-_\x9e1}_x\x98H\xb9\x08#\xfb\xe6\x8e\xd2\ +t\xc1b\xf3\x07\x19\x0awKA\x87\x843S\x10\x1f\ +\x85u\xe1bg\x0c\xe9o\xdc\xb4\x84\xe28F\xf6\x17\ +\xd8(\xec\xcfO0\x0d\x17\xe1\xa2\x14|\xd0~\x07\xb8\ +\xeb\x07w\xeaE\x0d\x90e8?M\x92\xf9\xc3>\xec\ +\x1c^\xe2\xee~A\xec+:\x87W\x97\xbe\xdb=\xab\ +T{\x11\xdf\xed\xb6\xd0\xc6x\xc2:\x85\xa0\xbfG \ +O\x15\xceM\xd6\xd5\x8d\xe2k\x05p9d\xe6\xe5\x0d\ +\x92\xdb/X\xbf)\x99\xf7\x0by\x99p\x1f\x8f\xff~\ +\xe0\x05\xe2\x06\xe8q\x90\xef\x96\xb8L\x0b\xd9XVX\ +nfyo\xc2k\x06\xff\x04\xc8o\xb8;\x095\xd3\ +\x00\xb7\xa5\xd9\x91\x14O+3\xef\xbb8\xfd\x8b\xb9\xb3\ +\x02\xb5\xca\xf0\xbe\xc1\xbf?\xbd\x22\xf6\x9d\x9c\x84\xf7\xd4\ +\xfd\x1at\x9e!\x8f\xb1p]H\x87\x84\xfcE|>\ +%\xb9\x13\xf0>\xa4(/\xb8\xf4\xf3\xc3\x8f{\x14\x9e\ +%\xb0A\x0a\xdf\x8b\xecS\xc8\xa3\xc4\xfa\x0d\xdc/X\ +\xdbQ,\xad\xc2\x80\x9cu\x9c\xbf\x86\xd6\x0a\xb8\x97f\ +\xe4\xbf\xbd[\x16\xcd\x0b\x02?fC\xcf_\x07\xf3\x82\ +\xdcG\x1eh\x1d\x0e\xf8lQw\xab\x9a\xe19\xe1\xda\ +\x16\xe9?\xc5\xbe;\xdboo!\x8d\x97w\xfe\xa0\x97\ +@-\x1a\xea~\x94\xc4\xfe\x1f~\xbc\xa9\xe8w\xc0\xff\ +VPwW\xc0\xee\x815\xa4\xee\x1d\x87\xcf\x1b\xf8+\ +3\x5c\x17\x93\xd9\xc8\xae\xc69\xae\x90;)\xe0C\x00\ +\x1e\xce\xbf\xf3\x92\xe9eW\xcal\x05\xe6\xcd\xce\xc3\xf4\ +\x82m\x94\x95\xe5\xce\xff\xe5\xc7\xcaD\xf27\xb8\xc3\x01\ +\xf2\x0f\xe8\x16\xdf\x99\xb5\xd0.\xba\xbb\x079\xaahM\ +\xb0\xb6\xcc\x0b\xab\xf0\x9a\x0a\xf1\x0e\xf2\xe8\xf4b|\x07\ +\x18\xe7}\x0b\x9eSn\x01>\xe7@\x97\xc0\xa3\xa1f\ +a\x92\xad\xa1\xb4\xf6\x03?\xceW\xd2\x7f\x8b\xe6\x01\xbc\ +\x9f?\xc0?YB\xc7,\xbc\xcbW\xb2\xde\x14\xe8\x03\ +8o\xfb\xe0T\x5c_\x09x\x00\x9c#\xb8\xbb\x07\xbe\ +g|\x9f\x91\x9fw,\x9d\xee\xc7\xf7\xdf\x8a\xf6\x9f\xa3\ +\xf9\x00\x0f)\x88\x0dG\xf0\x12\xdf;(\xf7\x1e\x15\xf3\ +\xf7\x0b\x82\x5cj\xa7\xf2\xfd\xe7b\xe3\x17Pk\x09p\ +\x8bA\xb4\xfdPY \x18\xbf(=~T\x91\xf7u\ +\xa5\x07\xe1\xf8\x11?~W\x15zt\x8b\x8a\xdfU\xd9\ +\xf8iU\x8f_\xff\x0e\xf9\x03\xbfC\xfeFU\xcd\x9f\ +\xc1yD\x1a\x04\x11\x03?\xd5\x08\x22\x18~*\xd3\xf9\ +G\xd5Y`2\x0f&\xfc\x87Q\xb4\xaf1\xf0S\xad\ +h\xdf!OK\x9f\xa0z\xfa\x14\xe6ii\x8a\xce\xd3\ +\x12\xc2e\x0d\x16\x95\x1b\xb8\x07\xc1c\x16\xc5\x1f\xbeJ\ +\x091\xf43\xf6\xd0\xcf\xac!L3B\xefn\x8e\xe0\ +\x10\x82\xc4\x0a8w\x89\xf4\xb3\x9b\x8b8K\x00\xffC\ +\x10V\x01\xef\x15\x860\xfa]\xc2\xeb\xfe\x15\xef\x16\x9c\ +\x03\x7f\x1fj\xd0\xfbR\xceg\xe8\x8a\xd6!%\xb73\ +\x0e\xb1\x8ahMr|\xd3\xbayaM|d\x1b\x81\ +\x9f\x18|;\xe9\xc7\xe6P9\x13\xe0\xd3-\xbbfj\ +\x22\xab\x88\xce%{7\xc4\xe4\xd6\x1b\xe1\xbc\x02\x5c\x93\ +\x1ej\x09\x09\xda\xedP\xbf\x22-\x01\xc7\xa2\xc0\xee\xc2\ +\xbam\xe9\xfb\xc1?c\x12\xac[\x07\xfb9\xe0\xbd\x12\ +\xd5\xc1G\xf6\x19\xd8\xcbP\xf3\xae\x949\xf0\xcfw\x99\ +\xefN?6\xbb\x14\x9f\x10W\xec\x9c\xa0\xde\x5c)v\ +?\x9f\xb7\x94\xb2\xe7:\x18\xcf\x90K\x22\xbc>\xf0)\ +\x80\x9f\x11h\x00\xd7@\xf0\xdfO\xf91\x84\xfc?P\ +\x03\x04|\x1c\x22\xf4\xf5\xaf\xa5\xbf_\x17\xfb\x8a\xc0\xaf\ +)8\xc0\x7f\x07\xef\xa4\xfch\xc5\xed'\xb0\xff\xc07\ +Y\xbc\xce\x17\x8f\xcc\xba\xbe\xa5\xfc\xef\x07\xbf\xde\x89y\ +\xc5\xfcz\xb0\x0f\xa9\xb8N\x9a\xb6h\xfa\xa6\xf7\x19h\ +T\xf0n5\xcc9ycOa<\x94\xfe~\xf0k\ +\x85y\x0a\xe0\x99C\xc7d\xca\xb07\xe9\xfc\x1b\xa8g\ +U\xb4\x05<\xea\xfey\xf1\xef\x8a\x7f?\xf8\x0d\xd1^\ +\x82?\x80?\x0a\xbeGIZ\xa7\x86\x8a\x8b\xb9\xfc[\ +\xcc\xf7-\x22^\x22\xfe\xfdP3\xd5i`\xb1\xb8N\ +\xde\x93\xcbd\x19y\x0f\xc5\xe7\x0f~\xcf\x9f\x9f\x04\xce\ +\xc2\x03:\x9e+\xc9\xfb\x9bb\xba\x17\xac\x17\x00\xfeM\ +\xc9\xf9\xab.\xb6\xaf\xe1Ny\xe1\xfe\xc5\xbc\xc0|S\ +\x80nJ\x7f\xff\xf6!\xc5\xea\xa5\xe5>\xf6\x90\xf0\xdd\ +\xf4\xfa\x11\xaf\x14\xc4\x1f\xc4@\x85\xe2I\xa5\xe3\x1f\xed\ +\x1f\xf0\x0f\xa0y\xc8\xad\xc0\xb9\x08\x92\xc6\xa3p\xbd\xbd\ +1\xc5\xf6/\xef\xd9\x15\xe18\x84\x88\xf7\x0b\xd8\xf5\xe8\ +o\x81\x06!\xbf\x07\xe7\xf9@\xcc\x03\x7f_2\xbb_\ +\xd0\xcf\x04CD=\xa4\xa2\xf7\xf3{\xbd ~S\x18\ +[\xa1c\xeb\xe0\x7f\x84\xfc\x0c\x88\xf3\xe2{\xde6\xad\ +K\xc5;U\x93eN1\xdc\x81\x5c\x02?\xa9\xc8\xf3\ +\x0fy~;\x86\xe2\xde2 _\xf2^\x5c\xa3\xe8\xd4\ +B\x9b\xaa\x1b\xc6?\xc2\xe8,A\x1c2\xeb\x86\x13Y\ +8g\xe1\x18\x02:\xf7\xe0\x8b\x17\x96\x15\x98vK\xc6\ +\x80\xa8\xf7C]\x1c\xc4\xbf\x0b\xe7\x9a\xfe\x83\x92\xe3\xe8\ +\x99\xa2r/ 7\x12\xe2\x18\x10\xa7\x009\x0b\xf1\x11\ +\xf0\xbd\xc13\xa0\x86\xa5p\xfe\x16\xf8\xcaD\xf0>\x81\ +\xf5\xf3\xcf:\x95\xc3\x00\xfc\x16\xea\xeb\x80\xfc\xc6\xf2V\ +`\x80\xcf\x1e\xeaxB\x0c\x0c\xd7\x92E<\x11\xf2\x93\ +\xb0\xbfVD\xdc\x01\xf2\xc5\xc0\x17*&g\x8d\xd6\x91\ +\xa9\xda\x80\xb9\xf7]1\xae3\x5c\x17b\xff6\xe4\xba\ +B\x1cVp\x80\x9f\x1bbU\xc9\x1b\x8c\x8a\xf1\x16Q\ +\x03\xea2\xc1\xbe\x94\xc23\xf8\xfa9\xf5\xd9\xba\x05\xf6\ +E\x83\xdf\x16x5\xd0>\xaeOq}+>;\xd0\ +?\x04\xe2\xc4\x98F\x817\x8b\x88\xcd\x81\xdf\x19jU\ +\x80\x5c\x869\x96\x91\xab\xc7\xb7\x0d\x8ax.Z\x1b\xd4\ +\x82\x85\xb8\x1b\xce\x0f\x82\x18(\x9d\xfb\x0by\xa4\xb8\x9e\ +\x14\xf0`\xc8SC4\x9e\xed\xbb\x0b\xe7\xffB\xedJ\ +\x1cs<<\x93\xf2\xc1JVG\x8eo\x97$\x16\xf1\ +\xac\xce\xb8n\x0d\xc8p*\x8fX\xe0\x19\xc2>?\xc1\ +\xfb\xed\x82y\xd5\x92\xf9\x05\xf9\xfagI\xfd[\xfa\xdc\ +\xe5\xf2\x00_\xff\xe6\xdb\x1fO*\xf8}\x82 h\x7f\ +(\x82\xfdUi\xf6ge\xdb\xdf\xccJ\xf4\xc6\xc0\xbb\ +\xc1O\xa1IP\xbe\x8aB?E\x8d\x92~\x0az\xce\ +\x0d\x100\x11\xbcG\x90\x8e C\x0c\xa4\xd3\x7f\xc3\xa4\ +\xbf\xc3\xff\xee9\x04\xdc\x12\xb8\x11\x7fv\xb9\xf4w\xf8\ +\xef\xe5\x96\xf8\x1e\xe4C >\x0du\xe7 \xa6\x07\xf9\ +'B|\x82+0\xe7b\xdf\x858\x19\xd4\xbc\x05\xdd\ +\x06\xc7\x82\xe9\xdeeX\xbe\xad-\xc6{\xf8\xeb-\xe4\ +C\xa0\x8f\xf0\xfbH\x80L\x86\x5c\xd7\xfc\x08\xbf\xc2\xba\ +3\xa0\xeb\x0a\xd4\x92\xe5\xefU\xe1\xf7!\xf7\x0f\x06<\ +\x03\xea\xc9\xe0\xba\xd7\xd6\xcd\xb1\xbc\x80\xb8\x92\x90\xbe\x9e\ +Q\xf8\xfd\xc2zDo\xb0n\x8c\xf3\xe2-\x04\xe2g\ +\x88_S\xb9\x08\xf9T~\x1b\x15\xdf\x11\xf8>U\x1f\ +\x06x<\xe4d\x95\xc8o\xc49\x08]q\xfdH\x88\ +\x9b'Q9*E\xdf\x07\x1b\x0b\xed7\xd4\xc3\xc3}\ +;\x84\xeb\xe5\xf2\xeb\xb1!\x1d\x02rQh\x1eN}\ +\x1fz\x06B\xdcxe\x0b\xec+H?\xb5\x80\xea\xaf\ +\x22X\xbb\x13\xfd?U\x9f\x93\xc4u\x04i}\x1f\x7f\ +\x1f\xf0\x022\x15\xea{\x81M\x08\xf8\x02\x9d\x18\xea\x99\ +\xa6\xee\x1c\x86\xf5\xf5\xec\xdb{q\xcc\x98\x9b\x9e\x88\xf5\ +OZ\xfee\xa0\x9f\xe9`\x93\xc1\x9cqn\x08]\x03\ +\x12\xf2xA\xbe\xc2w\xf89p \xfb\xa0G\x8a\x00\ +\xbdP\xf4\x8c\xf0\x93q\xde\x1a\xe7\x99C>\x04\xe8f\ +\xd0O\x03\xe4 \xc4z\xa1\x87\x16\xc4[\xa9\xbb\x0a\xc5\ +\xe8\x98:\x0b\xcbu\xb9)\xdb\x07Sz\xa0`\xcc\x16\ +\xeau\x01\x9e\xf8=\xe2\x8a\xcb\x1e>\xfdR\xe7g\xb9\ +\x0e\xb7\x1c\xb2I\xf0\xfc\xc8t~e\x1d\xff\x07\x9d\xab\ +\xf3\x85\ \x00\x00_\x84\ I\ I*\x00\x08\x00\x00\x00\x17\x00\xfe\x00\x04\x00\x01\x00\x00\ @@ -26805,336 +26919,336 @@ qt_resource_struct = b"\ \x00\x00\x03\xa8\x00\x02\x00\x00\x00\x01\x00\x00\x00+\ \x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x06\x0c\x00\x01\x00\x00\x00\x01\x00\x00\xde\xc4\ -\x00\x00\x01y\x1f\xa4\xb3Q\ -\x00\x00\x16\x86\x00\x00\x00\x00\x00\x01\x00\x05\x97+\ +\x00\x00\x01y\xd2\xb2\xf5B\ +\x00\x00\x16\x86\x00\x00\x00\x00\x00\x01\x00\x05\x9eC\ \x00\x00\x01x\xc7F\xed\xa9\ -\x00\x00\x16:\x00\x00\x00\x00\x00\x01\x00\x05\x95\x7f\ +\x00\x00\x16:\x00\x00\x00\x00\x00\x01\x00\x05\x9c\x97\ \x00\x00\x01x\xc7F\xf0\x97\ -\x00\x00\x16`\x00\x00\x00\x00\x00\x01\x00\x05\x96S\ +\x00\x00\x16`\x00\x00\x00\x00\x00\x01\x00\x05\x9dk\ \x00\x00\x01x\xc7F\xeb4\ -\x00\x00\x16\x1c\x00\x00\x00\x00\x00\x01\x00\x05\x94\x8c\ +\x00\x00\x16\x1c\x00\x00\x00\x00\x00\x01\x00\x05\x9b\xa4\ \x00\x00\x01x\xc7F\xf0\x87\ \x00\x00\x03\xa8\x00\x02\x00\x00\x00\x1f\x00\x00\x001\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00\x13\xb4\x00\x00\x00\x00\x00\x01\x00\x05\x80\xf9\ +\x00\x00\x13\xb4\x00\x00\x00\x00\x00\x01\x00\x05\x88\x11\ \x00\x00\x01x\xc7D\x85\xca\ -\x00\x00\x14\x06\x00\x00\x00\x00\x00\x01\x00\x05\x83\x8d\ +\x00\x00\x14\x06\x00\x00\x00\x00\x00\x01\x00\x05\x8a\xa5\ \x00\x00\x01x\xc7D\x85\xcc\ -\x00\x00\x15v\x00\x00\x00\x00\x00\x01\x00\x05\x90T\ +\x00\x00\x15v\x00\x00\x00\x00\x00\x01\x00\x05\x97l\ \x00\x00\x01x\xc7D\x85\xc3\ -\x00\x00\x12\xf0\x00\x00\x00\x00\x00\x01\x00\x05z\x87\ +\x00\x00\x12\xf0\x00\x00\x00\x00\x00\x01\x00\x05\x81\x9f\ \x00\x00\x01x\xc7D\x83\xc5\ -\x00\x00\x12|\x00\x00\x00\x00\x00\x01\x00\x05w\x97\ +\x00\x00\x12|\x00\x00\x00\x00\x00\x01\x00\x05~\xaf\ \x00\x00\x01x\xc7D\x85\xbb\ -\x00\x00\x152\x00\x00\x00\x00\x00\x01\x00\x05\x8d\xc0\ +\x00\x00\x152\x00\x00\x00\x00\x00\x01\x00\x05\x94\xd8\ \x00\x00\x01x\xc7D\x85e\ -\x00\x00\x12\x9e\x00\x00\x00\x00\x00\x01\x00\x05x\xe1\ +\x00\x00\x12\x9e\x00\x00\x00\x00\x00\x01\x00\x05\x7f\xf9\ \x00\x00\x01x\xc7D\x85\xd0\ -\x00\x00\x14\xf6\x00\x00\x00\x00\x00\x01\x00\x05\x8b,\ +\x00\x00\x14\xf6\x00\x00\x00\x00\x00\x01\x00\x05\x92D\ \x00\x00\x01x\xc7D\x84\xb4\ -\x00\x00\x11\xce\x00\x01\x00\x00\x00\x01\x00\x05s\x95\ +\x00\x00\x11\xce\x00\x01\x00\x00\x00\x01\x00\x05z\xad\ \x00\x00\x01x\xc7D\x83\xca\ -\x00\x00\x12(\x00\x00\x00\x00\x00\x01\x00\x05u\xe9\ +\x00\x00\x12(\x00\x00\x00\x00\x00\x01\x00\x05}\x01\ \x00\x00\x01x\xc7D\x85\xd2\ -\x00\x00\x12\x0c\x00\x01\x00\x00\x00\x01\x00\x05u>\ +\x00\x00\x12\x0c\x00\x01\x00\x00\x00\x01\x00\x05|V\ \x00\x00\x01x\xc7D\x84\x12\ -\x00\x00\x11\xf0\x00\x00\x00\x00\x00\x01\x00\x05s\xf4\ +\x00\x00\x11\xf0\x00\x00\x00\x00\x00\x01\x00\x05{\x0c\ \x00\x00\x01x\xc7D\x84\x12\ -\x00\x00\x12\xc8\x00\x01\x00\x00\x00\x01\x00\x05z+\ +\x00\x00\x12\xc8\x00\x01\x00\x00\x00\x01\x00\x05\x81C\ \x00\x00\x01x\xc7D\x83\xcf\ -\x00\x00\x13\x12\x00\x00\x00\x00\x00\x01\x00\x05{\xd1\ +\x00\x00\x13\x12\x00\x00\x00\x00\x00\x01\x00\x05\x82\xe9\ \x00\x00\x01x\xc7D\x85\xc7\ -\x00\x00\x13\xe0\x00\x00\x00\x00\x00\x01\x00\x05\x82C\ +\x00\x00\x13\xe0\x00\x00\x00\x00\x00\x01\x00\x05\x89[\ \x00\x00\x01x\xc7D\x85\xc8\ -\x00\x00\x13\x90\x00\x00\x00\x00\x00\x01\x00\x05\x7f\xaf\ +\x00\x00\x13\x90\x00\x00\x00\x00\x00\x01\x00\x05\x86\xc7\ \x00\x00\x01x\xc7D\x85d\ -\x00\x00\x13B\x00\x00\x00\x00\x00\x01\x00\x05}\x1b\ +\x00\x00\x13B\x00\x00\x00\x00\x00\x01\x00\x05\x843\ \x00\x00\x01x\xc7D\x85\xce\ -\x00\x00\x12\x5c\x00\x01\x00\x00\x00\x01\x00\x05w3\ +\x00\x00\x12\x5c\x00\x01\x00\x00\x00\x01\x00\x05~K\ \x00\x00\x01x\xc7D\x85-\ -\x00\x00\x15\xc4\x00\x01\x00\x00\x00\x01\x00\x05\x92\xe8\ +\x00\x00\x15\xc4\x00\x01\x00\x00\x00\x01\x00\x05\x9a\x00\ \x00\x00\x01x\xc7D\x83\xcb\ -\x00\x00\x14.\x00\x01\x00\x00\x00\x01\x00\x05\x84\xd7\ +\x00\x00\x14.\x00\x01\x00\x00\x00\x01\x00\x05\x8b\xef\ \x00\x00\x01x\xc7D\x85\xb9\ -\x00\x00\x14Z\x00\x01\x00\x00\x00\x01\x00\x05\x85\xa4\ +\x00\x00\x14Z\x00\x01\x00\x00\x00\x01\x00\x05\x8c\xbc\ \x00\x00\x01x\xc7D\x83\xcc\ -\x00\x00\x13h\x00\x00\x00\x00\x00\x01\x00\x05~e\ +\x00\x00\x13h\x00\x00\x00\x00\x00\x01\x00\x05\x85}\ \x00\x00\x01x\xc7D\x85d\ -\x00\x00\x14x\x00\x00\x00\x00\x00\x01\x00\x05\x86\x04\ +\x00\x00\x14x\x00\x00\x00\x00\x00\x01\x00\x05\x8d\x1c\ \x00\x00\x01x\xc7D\x85\xb6\ -\x00\x00\x15\x94\x00\x00\x00\x00\x00\x01\x00\x05\x91\x9e\ +\x00\x00\x15\x94\x00\x00\x00\x00\x00\x01\x00\x05\x98\xb6\ \x00\x00\x01x\xc7D\x85\xd4\ -\x00\x00\x14\x9c\x00\x00\x00\x00\x00\x01\x00\x05\x87N\ +\x00\x00\x14\x9c\x00\x00\x00\x00\x00\x01\x00\x05\x8ef\ \x00\x00\x01x\xc7D\x84\x0b\ -\x00\x00\x14\xba\x00\x00\x00\x00\x00\x01\x00\x05\x88\x98\ +\x00\x00\x14\xba\x00\x00\x00\x00\x00\x01\x00\x05\x8f\xb0\ \x00\x00\x01x\xc7D\x84\x0d\ -\x00\x00\x14\xd8\x00\x00\x00\x00\x00\x01\x00\x05\x89\xe2\ +\x00\x00\x14\xd8\x00\x00\x00\x00\x00\x01\x00\x05\x90\xfa\ \x00\x00\x01x\xc7D\x84\x0e\ -\x00\x00\x15\x14\x00\x00\x00\x00\x00\x01\x00\x05\x8cv\ +\x00\x00\x15\x14\x00\x00\x00\x00\x00\x01\x00\x05\x93\x8e\ \x00\x00\x01x\xc7D\x84\x0e\ -\x00\x00\x15X\x00\x00\x00\x00\x00\x01\x00\x05\x8f\x0a\ +\x00\x00\x15X\x00\x00\x00\x00\x00\x01\x00\x05\x96\x22\ \x00\x00\x01x\xc7D\x84\x0f\ -\x00\x00\x11\xba\x00\x01\x00\x00\x00\x01\x00\x05s6\ +\x00\x00\x11\xba\x00\x01\x00\x00\x00\x01\x00\x05zN\ \x00\x00\x01x\xc7D\x84\xb5\ -\x00\x00\x15\xf0\x00\x00\x00\x00\x00\x01\x00\x05\x93B\ +\x00\x00\x15\xf0\x00\x00\x00\x00\x00\x01\x00\x05\x9aZ\ \x00\x00\x01x\xc7D\x85\xc5\ \x00\x00\x03\xa8\x00\x02\x00\x00\x00\x08\x00\x00\x00Q\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00$\x98\x00\x00\x00\x00\x00\x01\x00\x06+\x0e\ +\x00\x00$\x98\x00\x00\x00\x00\x00\x01\x00\x062&\ \x00\x00\x01y+\x8f\x94\x0d\ -\x00\x00#\xa8\x00\x00\x00\x00\x00\x01\x00\x06\x16\xea\ +\x00\x00#\xa8\x00\x00\x00\x00\x00\x01\x00\x06\x1e\x02\ \x00\x00\x01y+\x8f\x94\x0b\ -\x00\x00$\x0a\x00\x00\x00\x00\x00\x01\x00\x06\x1e\xba\ +\x00\x00$\x0a\x00\x00\x00\x00\x00\x01\x00\x06%\xd2\ \x00\x00\x01y+\x8f\x94\x0b\ -\x00\x00$p\x00\x00\x00\x00\x00\x01\x00\x06&\x8c\ +\x00\x00$p\x00\x00\x00\x00\x00\x01\x00\x06-\xa4\ \x00\x00\x01x\xc7F\xf7d\ -\x00\x00#\xe6\x00\x00\x00\x00\x00\x01\x00\x06\x1a8\ +\x00\x00#\xe6\x00\x00\x00\x00\x00\x01\x00\x06!P\ \x00\x00\x01x\xc7F\xf6\xff\ -\x00\x00#D\x00\x00\x00\x00\x00\x01\x00\x06\x0f\x1e\ +\x00\x00#D\x00\x00\x00\x00\x00\x01\x00\x06\x166\ \x00\x00\x01x\xc7F\xf6\xed\ -\x00\x00$J\x00\x00\x00\x00\x00\x01\x00\x06\x22\x0a\ +\x00\x00$J\x00\x00\x00\x00\x00\x01\x00\x06)\x22\ \x00\x00\x01x\xc7F\xf6\xf0\ -\x00\x00#j\x00\x00\x00\x00\x00\x01\x00\x06\x13\xa0\ +\x00\x00#j\x00\x00\x00\x00\x00\x01\x00\x06\x1a\xb8\ \x00\x00\x01y+\x8f\x94\x0c\ \x00\x00\x16\xba\x00\x02\x00\x00\x009\x00\x00\x00Z\ \x00\x00\x00\x00\x00\x00\x00\x00\ -\x00\x00 :\x00\x00\x00\x00\x00\x01\x00\x05\xf1\xf4\ +\x00\x00 :\x00\x00\x00\x00\x00\x01\x00\x05\xf9\x0c\ \x00\x00\x01x\xc7F\xf0|\ -\x00\x00#\x0e\x00\x00\x00\x00\x00\x01\x00\x06\x0a1\ +\x00\x00#\x0e\x00\x00\x00\x00\x00\x01\x00\x06\x11I\ \x00\x00\x01x\xc7F\xed\xa1\ -\x00\x00\x1e\xb2\x00\x00\x00\x00\x00\x01\x00\x05\xe2\xb6\ +\x00\x00\x1e\xb2\x00\x00\x00\x00\x00\x01\x00\x05\xe9\xce\ \x00\x00\x01x\xc7F\xef\xf0\ -\x00\x00\x19\x08\x00\x00\x00\x00\x00\x01\x00\x05\xaf\x1d\ +\x00\x00\x19\x08\x00\x00\x00\x00\x00\x01\x00\x05\xb65\ \x00\x00\x01x\xc7F\xf0\xa1\ -\x00\x00!\xbc\x00\x00\x00\x00\x00\x01\x00\x05\xff\xfd\ +\x00\x00!\xbc\x00\x00\x00\x00\x00\x01\x00\x06\x07\x15\ \x00\x00\x01x\xc7F\xf0W\ -\x00\x00\x17@\x00\x00\x00\x00\x00\x01\x00\x05\x9d\xdd\ +\x00\x00\x17@\x00\x00\x00\x00\x00\x01\x00\x05\xa4\xf5\ \x00\x00\x01x\xc7F\xed\xd6\ -\x00\x00\x19\xae\x00\x00\x00\x00\x00\x01\x00\x05\xb2\xec\ +\x00\x00\x19\xae\x00\x00\x00\x00\x00\x01\x00\x05\xba\x04\ \x00\x00\x01x\xc7F\xe9{\ -\x00\x00\x22b\x00\x00\x00\x00\x00\x01\x00\x06\x04c\ +\x00\x00\x22b\x00\x00\x00\x00\x00\x01\x00\x06\x0b{\ \x00\x00\x01x\xc7F\xef\xab\ -\x00\x00\x1b\xde\x00\x00\x00\x00\x00\x01\x00\x05\xc5\xad\ +\x00\x00\x1b\xde\x00\x00\x00\x00\x00\x01\x00\x05\xcc\xc5\ \x00\x00\x01x\xc7F\xcb~\ -\x00\x00\x18,\x00\x00\x00\x00\x00\x01\x00\x05\xa5\x22\ +\x00\x00\x18,\x00\x00\x00\x00\x00\x01\x00\x05\xac:\ \x00\x00\x01x\xc7F\xf0\x90\ -\x00\x00\x1a\xd6\x00\x00\x00\x00\x00\x01\x00\x05\xba\xbf\ +\x00\x00\x1a\xd6\x00\x00\x00\x00\x00\x01\x00\x05\xc1\xd7\ \x00\x00\x01x\xc7F\xe9@\ -\x00\x00\x1e<\x00\x00\x00\x00\x00\x01\x00\x05\xdcu\ +\x00\x00\x1e<\x00\x00\x00\x00\x00\x01\x00\x05\xe3\x8d\ \x00\x00\x01x\xc7F\xf0=\ -\x00\x00 \xe0\x00\x00\x00\x00\x00\x01\x00\x05\xf6-\ +\x00\x00 \xe0\x00\x00\x00\x00\x00\x01\x00\x05\xfdE\ \x00\x00\x01x\xc7F\xf0\x03\ -\x00\x00\x1c\xba\x00\x00\x00\x00\x00\x01\x00\x05\xd0B\ +\x00\x00\x1c\xba\x00\x00\x00\x00\x00\x01\x00\x05\xd7Z\ \x00\x00\x01x\xc7F\xed\xd8\ -\x00\x00\x1f\x8e\x00\x00\x00\x00\x00\x01\x00\x05\xec\xea\ +\x00\x00\x1f\x8e\x00\x00\x00\x00\x00\x01\x00\x05\xf4\x02\ \x00\x00\x01x\xc7F\xed\xda\ -\x00\x00\x1cT\x00\x00\x00\x00\x00\x01\x00\x05\xcb\x82\ +\x00\x00\x1cT\x00\x00\x00\x00\x00\x01\x00\x05\xd2\x9a\ \x00\x00\x01x\xc7F\xcc\x09\ -\x00\x00\x18b\x00\x00\x00\x00\x00\x01\x00\x05\xa6\x14\ +\x00\x00\x18b\x00\x00\x00\x00\x00\x01\x00\x05\xad,\ \x00\x00\x01x\xc7F\xe9f\ -\x00\x00\x1e\xe8\x00\x00\x00\x00\x00\x01\x00\x05\xe4c\ +\x00\x00\x1e\xe8\x00\x00\x00\x00\x00\x01\x00\x05\xeb{\ \x00\x00\x01x\xc7F\xe9\x86\ -\x00\x00\x1b\x0c\x00\x00\x00\x00\x00\x01\x00\x05\xbc\x0d\ +\x00\x00\x1b\x0c\x00\x00\x00\x00\x00\x01\x00\x05\xc3%\ \x00\x00\x01x\xc7F\xcb\xfe\ -\x00\x00\x1d\x96\x00\x00\x00\x00\x00\x01\x00\x05\xd7\x13\ +\x00\x00\x1d\x96\x00\x00\x00\x00\x00\x01\x00\x05\xde+\ \x00\x00\x01x\xc7F\xe9\x82\ -\x00\x00!\x16\x00\x00\x00\x00\x00\x01\x00\x05\xf7\xeb\ +\x00\x00!\x16\x00\x00\x00\x00\x00\x01\x00\x05\xff\x03\ \x00\x00\x01x\xc7F\xe7N\ -\x00\x00\x17v\x00\x00\x00\x00\x00\x01\x00\x05\x9f\xff\ +\x00\x00\x17v\x00\x00\x00\x00\x00\x01\x00\x05\xa7\x17\ \x00\x00\x01x\xc7F\xe97\ -\x00\x00\x1f\xc4\x00\x00\x00\x00\x00\x01\x00\x05\xee\xfa\ +\x00\x00\x1f\xc4\x00\x00\x00\x00\x00\x01\x00\x05\xf6\x12\ \x00\x00\x01x\xc7F\xe7\xa5\ -\x00\x00\x22\x98\x00\x00\x00\x00\x00\x01\x00\x06\x06c\ +\x00\x00\x22\x98\x00\x00\x00\x00\x00\x01\x00\x06\x0d{\ \x00\x00\x01x\xc7F\xe6\xb8\ -\x00\x00\x19\xe4\x00\x00\x00\x00\x00\x01\x00\x05\xb4\x0b\ +\x00\x00\x19\xe4\x00\x00\x00\x00\x00\x01\x00\x05\xbb#\ \x00\x00\x01x\xc7F\xef\xeb\ -\x00\x00\x1d\x16\x00\x00\x00\x00\x00\x01\x00\x05\xd3\xe3\ +\x00\x00\x1d\x16\x00\x00\x00\x00\x00\x01\x00\x05\xda\xfb\ \x00\x00\x01x\xc7F\xef\xe2\ -\x00\x00\x1bn\x00\x00\x00\x00\x00\x01\x00\x05\xc1)\ +\x00\x00\x1bn\x00\x00\x00\x00\x00\x01\x00\x05\xc8A\ \x00\x00\x01x\xc7F\xef\x93\ -\x00\x00\x1af\x00\x00\x00\x00\x00\x01\x00\x05\xb7h\ +\x00\x00\x1af\x00\x00\x00\x00\x00\x01\x00\x05\xbe\x80\ \x00\x00\x01x\xc7F\xef\xee\ -\x00\x00\x19>\x00\x00\x00\x00\x00\x01\x00\x05\xaf\xef\ +\x00\x00\x19>\x00\x00\x00\x00\x00\x01\x00\x05\xb7\x07\ \x00\x00\x01x\xc7F\xf00\ -\x00\x00\x17\xec\x00\x00\x00\x00\x00\x01\x00\x05\xa2\xf2\ +\x00\x00\x17\xec\x00\x00\x00\x00\x00\x01\x00\x05\xaa\x0a\ \x00\x00\x01x\xc7F\xed\xd3\ -\x00\x00\x16\xd0\x00\x00\x00\x00\x00\x01\x00\x05\x9b\x19\ +\x00\x00\x16\xd0\x00\x00\x00\x00\x00\x01\x00\x05\xa21\ \x00\x00\x01x\xc7F\xef\xe6\ -\x00\x00!\xf2\x00\x00\x00\x00\x00\x01\x00\x06\x01P\ +\x00\x00!\xf2\x00\x00\x00\x00\x00\x01\x00\x06\x08h\ \x00\x00\x01x\xc7F\xf0f\ -\x00\x00 p\x00\x00\x00\x00\x00\x01\x00\x05\xf2\xfb\ +\x00\x00 p\x00\x00\x00\x00\x00\x01\x00\x05\xfa\x13\ \x00\x00\x01x\xc7F\xf0k\ -\x00\x00\x1f\x1e\x00\x00\x00\x00\x00\x01\x00\x05\xe5k\ +\x00\x00\x1f\x1e\x00\x00\x00\x00\x00\x01\x00\x05\xec\x83\ \x00\x00\x01x\xc7F\xed\xc2\ -\x00\x00\x1d\xcc\x00\x00\x00\x00\x00\x01\x00\x05\xd8!\ +\x00\x00\x1d\xcc\x00\x00\x00\x00\x00\x01\x00\x05\xdf9\ \x00\x00\x01x\xc7F\xf0\x0e\ -\x00\x00\x1a&\x00\x00\x00\x00\x00\x01\x00\x05\xb5\xde\ +\x00\x00\x1a&\x00\x00\x00\x00\x00\x01\x00\x05\xbc\xf6\ \x00\x00\x01x\xc7F\xf0%\ -\x00\x00\x18\x98\x00\x00\x00\x00\x00\x01\x00\x05\xa7M\ +\x00\x00\x18\x98\x00\x00\x00\x00\x00\x01\x00\x05\xaee\ \x00\x00\x01x\xc7F\xf0I\ -\x00\x00\x17\xac\x00\x00\x00\x00\x00\x01\x00\x05\xa1s\ +\x00\x00\x17\xac\x00\x00\x00\x00\x00\x01\x00\x05\xa8\x8b\ \x00\x00\x01x\xc7F\xf0,\ -\x00\x00\x22\xce\x00\x00\x00\x00\x00\x01\x00\x06\x08\x1f\ +\x00\x00\x22\xce\x00\x00\x00\x00\x00\x01\x00\x06\x0f7\ \x00\x00\x01x\xc7F\xed\xdd\ -\x00\x00!|\x00\x00\x00\x00\x00\x01\x00\x05\xfe\x91\ +\x00\x00!|\x00\x00\x00\x00\x00\x01\x00\x06\x05\xa9\ \x00\x00\x01x\xc7F\xf04\ -\x00\x00\x1f\xfa\x00\x00\x00\x00\x00\x01\x00\x05\xf0{\ +\x00\x00\x1f\xfa\x00\x00\x00\x00\x00\x01\x00\x05\xf7\x93\ \x00\x00\x01x\xc7F\xf0*\ -\x00\x00\x1er\x00\x00\x00\x00\x00\x01\x00\x05\xdd\xd6\ +\x00\x00\x1er\x00\x00\x00\x00\x00\x01\x00\x05\xe4\xee\ \x00\x00\x01x\xc7F\xed\xc6\ -\x00\x00\x1dV\x00\x00\x00\x00\x00\x01\x00\x05\xd5\xb7\ +\x00\x00\x1dV\x00\x00\x00\x00\x00\x01\x00\x05\xdc\xcf\ \x00\x00\x01x\xc7F\xf0@\ -\x00\x00\x1c\x14\x00\x00\x00\x00\x00\x01\x00\x05\xca)\ +\x00\x00\x1c\x14\x00\x00\x00\x00\x00\x01\x00\x05\xd1A\ \x00\x00\x01x\xc7F\xf0K\ -\x00\x00\x1bB\x00\x00\x00\x00\x00\x01\x00\x05\xbe&\ +\x00\x00\x1bB\x00\x00\x00\x00\x00\x01\x00\x05\xc5>\ \x00\x00\x01x\xc7F\xed\xcb\ -\x00\x00\x1e\x0c\x00\x00\x00\x00\x00\x01\x00\x05\xd9\xa3\ +\x00\x00\x1e\x0c\x00\x00\x00\x00\x00\x01\x00\x05\xe0\xbb\ \x00\x00\x01x\xc7F\xed\xc8\ -\x00\x00 \xb0\x00\x00\x00\x00\x00\x01\x00\x05\xf4(\ +\x00\x00 \xb0\x00\x00\x00\x00\x00\x01\x00\x05\xfb@\ \x00\x00\x01x\xc7F\xef\xa6\ -\x00\x00\x1c\x8a\x00\x00\x00\x00\x00\x01\x00\x05\xcd\x7f\ +\x00\x00\x1c\x8a\x00\x00\x00\x00\x00\x01\x00\x05\xd4\x97\ \x00\x00\x01x\xc7F\xed\xce\ -\x00\x00\x17\x10\x00\x00\x00\x00\x00\x01\x00\x05\x9c\xd8\ +\x00\x00\x17\x10\x00\x00\x00\x00\x00\x01\x00\x05\xa3\xf0\ \x00\x00\x01x\xc7F\xf0\x80\ -\x00\x00\x1f^\x00\x00\x00\x00\x00\x01\x00\x05\xeb\x8c\ +\x00\x00\x1f^\x00\x00\x00\x00\x00\x01\x00\x05\xf2\xa4\ \x00\x00\x01x\xc7F\xf0:\ -\x00\x00\x19~\x00\x00\x00\x00\x00\x01\x00\x05\xb1p\ +\x00\x00\x19~\x00\x00\x00\x00\x00\x01\x00\x05\xb8\x88\ \x00\x00\x01x\xc7F\xf0(\ -\x00\x00\x222\x00\x00\x00\x00\x00\x01\x00\x06\x02\x83\ +\x00\x00\x222\x00\x00\x00\x00\x00\x01\x00\x06\x09\x9b\ \x00\x00\x01x\xc7F\xef\xdd\ -\x00\x00\x1b\xae\x00\x00\x00\x00\x00\x01\x00\x05\xc3\x18\ +\x00\x00\x1b\xae\x00\x00\x00\x00\x00\x01\x00\x05\xca0\ \x00\x00\x01x\xc7F\xed\xd1\ -\x00\x00!L\x00\x00\x00\x00\x00\x01\x00\x05\xf9\xa7\ +\x00\x00!L\x00\x00\x00\x00\x00\x01\x00\x06\x00\xbf\ \x00\x00\x01y+\x8f\x93{\ -\x00\x00\x1a\xa6\x00\x00\x00\x00\x00\x01\x00\x05\xb92\ +\x00\x00\x1a\xa6\x00\x00\x00\x00\x00\x01\x00\x05\xc0J\ \x00\x00\x01x\xc7F\xf0\x0c\ -\x00\x00\x18\xd8\x00\x00\x00\x00\x00\x01\x00\x05\xa8\xa5\ +\x00\x00\x18\xd8\x00\x00\x00\x00\x00\x01\x00\x05\xaf\xbd\ \x00\x00\x01x\xc7F\xed\xa4\ -\x00\x00\x1c\xf0\x00\x00\x00\x00\x00\x01\x00\x05\xd2\x83\ +\x00\x00\x1c\xf0\x00\x00\x00\x00\x00\x01\x00\x05\xd9\x9b\ \x00\x00\x01x\xc7F\xe9<\ -\x00\x00\x0e\xba\x00\x00\x00\x00\x00\x01\x00\x04(&\ +\x00\x00\x0e\xba\x00\x00\x00\x00\x00\x01\x00\x04/>\ \x00\x00\x01y+\x8f\x94\x12\ -\x00\x00\x0d\x0a\x00\x00\x00\x00\x00\x01\x00\x03\x91\x87\ +\x00\x00\x0d\x0a\x00\x00\x00\x00\x00\x01\x00\x03\x98\x9f\ \x00\x00\x01y+\x8f\x93\xdc\ -\x00\x00\x0e\x80\x00\x00\x00\x00\x00\x01\x00\x04#\x0e\ +\x00\x00\x0e\x80\x00\x00\x00\x00\x00\x01\x00\x04*&\ \x00\x00\x01y+\x8f\x94\x08\ -\x00\x00\x0d~\x00\x00\x00\x00\x00\x01\x00\x03\xf4\x82\ +\x00\x00\x0d~\x00\x00\x00\x00\x00\x01\x00\x03\xfb\x9a\ \x00\x00\x01y+\x8f\x93\xcd\ -\x00\x00\x09\xf6\x00\x00\x00\x00\x00\x01\x00\x032\x17\ +\x00\x00\x09\xf6\x00\x00\x00\x00\x00\x01\x00\x039/\ \x00\x00\x01y+\x8f\x94\x10\ -\x00\x00\x09f\x00\x01\x00\x00\x00\x01\x00\x02i(\ +\x00\x00\x09f\x00\x01\x00\x00\x00\x01\x00\x02p@\ \x00\x00\x01x\xc7F\xf5\xfe\ -\x00\x00\x07\x12\x00\x00\x00\x00\x00\x01\x00\x01\x87\xf3\ +\x00\x00\x07\x12\x00\x00\x00\x00\x00\x01\x00\x01\x8f\x0b\ \x00\x00\x01y+\x8f\x93\xce\ -\x00\x00\x08:\x00\x00\x00\x00\x00\x01\x00\x01\xa3\x1b\ +\x00\x00\x08:\x00\x00\x00\x00\x00\x01\x00\x01\xaa3\ \x00\x00\x01y+\x8f\x94\x0a\ -\x00\x00\x0f$\x00\x00\x00\x00\x00\x01\x00\x04jD\ +\x00\x00\x0f$\x00\x00\x00\x00\x00\x01\x00\x04q\x5c\ \x00\x00\x01x\xc7F\xf6G\ -\x00\x00\x0e\x10\x00\x00\x00\x00\x00\x01\x00\x04\x15\xa7\ +\x00\x00\x0e\x10\x00\x00\x00\x00\x00\x01\x00\x04\x1c\xbf\ \x00\x00\x01y+\x8f\x93\xe3\ -\x00\x00\x08\xd2\x00\x00\x00\x00\x00\x01\x00\x02^f\ +\x00\x00\x08\xd2\x00\x00\x00\x00\x00\x01\x00\x02e~\ \x00\x00\x01y+\x8f\x93\xd6\ -\x00\x00\x0eL\x00\x00\x00\x00\x00\x01\x00\x04\x1d\xe2\ +\x00\x00\x0eL\x00\x00\x00\x00\x00\x01\x00\x04$\xfa\ \x00\x00\x01y+\x8f\x94\x09\ -\x00\x00\x09*\x00\x00\x00\x00\x00\x01\x00\x02f\x89\ +\x00\x00\x09*\x00\x00\x00\x00\x00\x01\x00\x02m\xa1\ \x00\x00\x01y+\x8f\x94\x07\ -\x00\x00\x0c \x00\x00\x00\x00\x00\x01\x00\x03u!\ +\x00\x00\x0c \x00\x00\x00\x00\x00\x01\x00\x03|9\ \x00\x00\x01y+\x8f\x93\xd5\ -\x00\x00\x07z\x00\x00\x00\x00\x00\x01\x00\x01\x96/\ +\x00\x00\x07z\x00\x00\x00\x00\x00\x01\x00\x01\x9dG\ \x00\x00\x01y+\x8f\x93\xdf\ -\x00\x00\x08x\x00\x00\x00\x00\x00\x01\x00\x01\xbe\xd0\ +\x00\x00\x08x\x00\x00\x00\x00\x00\x01\x00\x01\xc5\xe8\ \x00\x00\x01x\xc7F\xf6*\ -\x00\x00\x0f\x9c\x00\x00\x00\x00\x00\x01\x00\x04\x87Q\ +\x00\x00\x0f\x9c\x00\x00\x00\x00\x00\x01\x00\x04\x8ei\ \x00\x00\x01y+\x8f\x94\x12\ -\x00\x00\x08T\x00\x00\x00\x00\x00\x01\x00\x01\xa9(\ +\x00\x00\x08T\x00\x00\x00\x00\x00\x01\x00\x01\xb0@\ \x00\x00\x01x\xc7F\xf61\ -\x00\x00\x0cb\x00\x00\x00\x00\x00\x01\x00\x03\x81c\ +\x00\x00\x0cb\x00\x00\x00\x00\x00\x01\x00\x03\x88{\ \x00\x00\x01y+\x8f\x93\xda\ -\x00\x00\x07B\x00\x00\x00\x00\x00\x01\x00\x01\x8c\x97\ +\x00\x00\x07B\x00\x00\x00\x00\x00\x01\x00\x01\x93\xaf\ \x00\x00\x01y+\x8f\x94\x11\ -\x00\x00\x08\xf8\x00\x00\x00\x00\x00\x01\x00\x02b\x14\ +\x00\x00\x08\xf8\x00\x00\x00\x00\x00\x01\x00\x02i,\ \x00\x00\x01y+\x8f\x93\xce\ -\x00\x00\x0a\xc0\x00\x00\x00\x00\x00\x01\x00\x03N\xe6\ +\x00\x00\x0a\xc0\x00\x00\x00\x00\x00\x01\x00\x03U\xfe\ \x00\x00\x01y+\x8f\x93\xd8\ -\x00\x00\x09\xc8\x00\x01\x00\x00\x00\x01\x00\x03\x12$\ +\x00\x00\x09\xc8\x00\x01\x00\x00\x00\x01\x00\x03\x19<\ \x00\x00\x01x\xc7F\xf5\xf8\ -\x00\x00\x06\x92\x00\x00\x00\x00\x00\x01\x00\x01|\xed\ +\x00\x00\x06\x92\x00\x00\x00\x00\x00\x01\x00\x01\x84\x05\ \x00\x00\x01y+\x8f\x93\xcc\ -\x00\x00\x0a\xf8\x00\x00\x00\x00\x00\x01\x00\x03R\x98\ +\x00\x00\x0a\xf8\x00\x00\x00\x00\x00\x01\x00\x03Y\xb0\ \x00\x00\x01y+\x8f\x93\xdb\ -\x00\x00\x0d\x98\x00\x00\x00\x00\x00\x01\x00\x03\xf8 \ +\x00\x00\x0d\x98\x00\x00\x00\x00\x00\x01\x00\x03\xff8\ \x00\x00\x01y+\x8f\x93\xdc\ -\x00\x00\x0c<\x00\x00\x00\x00\x00\x01\x00\x03|B\ +\x00\x00\x0c<\x00\x00\x00\x00\x00\x01\x00\x03\x83Z\ \x00\x00\x01y+\x8f\x93\xde\ -\x00\x00\x0bZ\x00\x00\x00\x00\x00\x01\x00\x03W)\ +\x00\x00\x0bZ\x00\x00\x00\x00\x00\x01\x00\x03^A\ \x00\x00\x01y+\x8f\x93\xd8\ -\x00\x00\x0a\x94\x00\x00\x00\x00\x00\x01\x00\x039\x16\ +\x00\x00\x0a\x94\x00\x00\x00\x00\x00\x01\x00\x03@.\ \x00\x00\x01x\xc7F\xf60\ -\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x01\x00\x04`\xcc\ +\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x01\x00\x04g\xe4\ \x00\x00\x01y+\x8f\x94\x13\ -\x00\x00\x0fN\x00\x00\x00\x00\x00\x01\x00\x04\x7f\x0c\ +\x00\x00\x0fN\x00\x00\x00\x00\x00\x01\x00\x04\x86$\ \x00\x00\x01y+\x8f\x93\xe2\ -\x00\x00\x0dD\x00\x00\x00\x00\x00\x01\x00\x03\x95\x12\ +\x00\x00\x0dD\x00\x00\x00\x00\x00\x01\x00\x03\x9c*\ \x00\x00\x01x\xc7F\xf6\x18\ -\x00\x00\x0a0\x00\x00\x00\x00\x00\x01\x00\x034\xb6\ +\x00\x00\x0a0\x00\x00\x00\x00\x00\x01\x00\x03;\xce\ \x00\x00\x01y+\x8f\x93\xd9\ -\x00\x00\x060\x00\x00\x00\x00\x00\x01\x00\x01\x1a\xc3\ +\x00\x00\x060\x00\x00\x00\x00\x00\x01\x00\x01!\xdb\ \x00\x00\x01x\xc7F\xf6\x1b\ -\x00\x00\x0b\xe2\x00\x00\x00\x00\x00\x01\x00\x03p\xae\ +\x00\x00\x0b\xe2\x00\x00\x00\x00\x00\x01\x00\x03w\xc6\ \x00\x00\x01y+\x8f\x93\xd7\ -\x00\x00\x06\xf0\x00\x00\x00\x00\x00\x01\x00\x01\x84\xba\ +\x00\x00\x06\xf0\x00\x00\x00\x00\x00\x01\x00\x01\x8b\xd2\ \x00\x00\x01y+\x8f\x93\xfd\ -\x00\x00\x07\xb8\x00\x00\x00\x00\x00\x01\x00\x01\x9bR\ +\x00\x00\x07\xb8\x00\x00\x00\x00\x00\x01\x00\x01\xa2j\ \x00\x00\x01y+\x8f\x94\x0f\ -\x00\x00\x0f\xce\x00\x00\x00\x00\x00\x01\x00\x04\x90\xf8\ +\x00\x00\x0f\xce\x00\x00\x00\x00\x00\x01\x00\x04\x98\x10\ \x00\x00\x01y+\x8f\x93\xcb\ -\x00\x00\x0e\xd0\x00\x01\x00\x00\x00\x01\x00\x04/\x99\ +\x00\x00\x0e\xd0\x00\x01\x00\x00\x00\x01\x00\x046\xb1\ \x00\x00\x01x\xc7F\xf5\xf3\ -\x00\x00\x06^\x00\x00\x00\x00\x00\x01\x00\x01zK\ +\x00\x00\x06^\x00\x00\x00\x00\x00\x01\x00\x01\x81c\ \x00\x00\x01y+\x8f\x94\x06\ -\x00\x00\x09\xb4\x00\x00\x00\x00\x00\x01\x00\x03\x0c\xa3\ +\x00\x00\x09\xb4\x00\x00\x00\x00\x00\x01\x00\x03\x13\xbb\ \x00\x00\x01y+\x8f\x93\xd0\ -\x00\x00\x0f\xf8\x00\x00\x00\x00\x00\x01\x00\x04\x93\xb2\ +\x00\x00\x0f\xf8\x00\x00\x00\x00\x00\x01\x00\x04\x9a\xca\ \x00\x00\x01x\xc7F\xf5\xf0\ -\x00\x00\x09\x86\x00\x00\x00\x00\x00\x01\x00\x02\x8c\x97\ +\x00\x00\x09\x86\x00\x00\x00\x00\x00\x01\x00\x02\x93\xaf\ \x00\x00\x01x\xc7F\xf6$\ -\x00\x00\x0d\xe4\x00\x01\x00\x00\x00\x01\x00\x03\xfb\xab\ +\x00\x00\x0d\xe4\x00\x01\x00\x00\x00\x01\x00\x04\x02\xc3\ \x00\x00\x01x\xc7F\xf6\x04\ -\x00\x00\x0c\xec\x00\x00\x00\x00\x00\x01\x00\x03\x8a\xa7\ +\x00\x00\x0c\xec\x00\x00\x00\x00\x00\x01\x00\x03\x91\xbf\ \x00\x00\x01y+\x8f\x94\x0e\ -\x00\x00\x0b\xaa\x00\x00\x00\x00\x00\x01\x00\x03[\xa0\ +\x00\x00\x0b\xaa\x00\x00\x00\x00\x00\x01\x00\x03b\xb8\ \x00\x00\x01x\xc7F\xf6L\ -\x00\x00\x06\xb8\x00\x00\x00\x00\x00\x01\x00\x01\x7f\x95\ +\x00\x00\x06\xb8\x00\x00\x00\x00\x00\x01\x00\x01\x86\xad\ \x00\x00\x01y+\x8f\x93\xe0\ -\x00\x00\x0c\xb0\x00\x00\x00\x00\x00\x01\x00\x03\x86\x07\ +\x00\x00\x0c\xb0\x00\x00\x00\x00\x00\x01\x00\x03\x8d\x1f\ \x00\x00\x01y+\x8f\x93\xdd\ -\x00\x00\x07\xea\x00\x00\x00\x00\x00\x01\x00\x01\x9d\xf4\ +\x00\x00\x07\xea\x00\x00\x00\x00\x00\x01\x00\x01\xa5\x0c\ \x00\x00\x01y+\x8f\x93\xe1\ -\x00\x00\x08\xb4\x00\x00\x00\x00\x00\x01\x00\x02H\xcc\ +\x00\x00\x08\xb4\x00\x00\x00\x00\x00\x01\x00\x02O\xe4\ \x00\x00\x01x\xc7F\xf6D\ -\x00\x00\x11\x06\x00\x00\x00\x00\x00\x01\x00\x05@\xf3\ +\x00\x00\x11\x06\x00\x00\x00\x00\x00\x01\x00\x05H\x0b\ \x00\x00\x01y+\x8f\x93\xd4\ -\x00\x00\x11\x88\x00\x00\x00\x00\x00\x01\x00\x05k\xae\ +\x00\x00\x11\x88\x00\x00\x00\x00\x00\x01\x00\x05r\xc6\ \x00\x00\x01y+\x8f\x93\xd1\ -\x00\x00\x11b\x00\x00\x00\x00\x00\x01\x00\x05Yl\ +\x00\x00\x11b\x00\x00\x00\x00\x00\x01\x00\x05`\x84\ \x00\x00\x01y+\x8f\x93\xcf\ -\x00\x00\x11\xa0\x00\x00\x00\x00\x00\x01\x00\x05o\xc9\ +\x00\x00\x11\xa0\x00\x00\x00\x00\x00\x01\x00\x05v\xe1\ \x00\x00\x01y+\x8f\x93\xd2\ -\x00\x00\x10\x94\x00\x00\x00\x00\x00\x01\x00\x059\xf7\ +\x00\x00\x10\x94\x00\x00\x00\x00\x00\x01\x00\x05A\x0f\ \x00\x00\x01x\xc7F\xf8/\ -\x00\x00\x106\x00\x00\x00\x00\x00\x01\x00\x05!\xc2\ +\x00\x00\x106\x00\x00\x00\x00\x00\x01\x00\x05(\xda\ \x00\x00\x01x\xc7F\xf8,\ -\x00\x00\x11 \x00\x00\x00\x00\x00\x01\x00\x05F\xfd\ +\x00\x00\x11 \x00\x00\x00\x00\x00\x01\x00\x05N\x15\ \x00\x00\x01x\xc7F\xf83\ -\x00\x00\x10\xdc\x00\x00\x00\x00\x00\x01\x00\x05@%\ +\x00\x00\x10\xdc\x00\x00\x00\x00\x00\x01\x00\x05G=\ \x00\x00\x01x\xc7F\xf8(\ -\x00\x00\x10`\x00\x00\x00\x00\x00\x01\x00\x05\x22O\ +\x00\x00\x10`\x00\x00\x00\x00\x00\x01\x00\x05)g\ \x00\x00\x01y+\x8f\x93\xd3\ -\x00\x00\x11J\x00\x00\x00\x00\x00\x01\x00\x05G\xa1\ +\x00\x00\x11J\x00\x00\x00\x00\x00\x01\x00\x05N\xb9\ \x00\x00\x01y+\x8f\x93\xca\ -\x00\x00\x10z\x00\x00\x00\x00\x00\x01\x00\x0509\ +\x00\x00\x10z\x00\x00\x00\x00\x00\x01\x00\x057Q\ \x00\x00\x01y+\x8f\x93\xc9\ -\x00\x00\x10\xbe\x00\x00\x00\x00\x00\x01\x00\x05:\xa6\ +\x00\x00\x10\xbe\x00\x00\x00\x00\x00\x01\x00\x05A\xbe\ \x00\x00\x01y+\x8f\x93\xe3\ " diff --git a/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/aws_utils.py b/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/aws_utils.py index b0c3c9c1b3..8152d2addc 100755 --- a/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/aws_utils.py +++ b/Gems/AWSCore/Code/Tools/ResourceMappingTool/utils/aws_utils.py @@ -12,10 +12,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. import boto3 from botocore.paginate import (PageIterator, Paginator) from botocore.client import BaseClient -from botocore.exceptions import ClientError +from botocore.exceptions import (ClientError, ConfigNotFound, NoCredentialsError, ProfileNotFound) from typing import Dict, List -from model import (constants, error_messages) +from model import error_messages from model.basic_resource_attributes import (BasicResourceAttributes, BasicResourceAttributesBuilder) """ @@ -65,8 +65,11 @@ def _initialize_boto3_aws_client(service: str, region: str = "") -> BaseClient: def setup_default_session(profile: str) -> None: - global default_session - default_session = boto3.session.Session(profile_name=profile) + try: + global default_session + default_session = boto3.session.Session(profile_name=profile) + except (ConfigNotFound, ProfileNotFound) as error: + raise RuntimeError(error) def get_default_account_id() -> str: @@ -76,6 +79,8 @@ def get_default_account_id() -> str: except ClientError as error: raise RuntimeError(error_messages.AWS_SERVICE_REQUEST_CLIENT_ERROR_MESSAGE.format( "get_caller_identity", error.response['Error']['Code'], error.response['Error']['Message'])) + except NoCredentialsError as error: + raise RuntimeError(error) def get_default_region() -> str: 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 diff --git a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Converters/FIR-Weights.cpp b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Converters/FIR-Weights.cpp index 192c82c165..b1d0cfbda0 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Converters/FIR-Weights.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/Code/Source/Converters/FIR-Weights.cpp @@ -88,7 +88,7 @@ namespace ImageProcessingAtom int dstPosition; signed short int n; bool trimZeros = true, stillzero; - int lastnonzero, hWeight, highest; + int lastnonzero = 0, hWeight, highest = 0; signed int sumiWeights, iWeight; signed short int* weightsPtr; signed short int* weightsMem; diff --git a/Gems/Atom/Asset/ImageProcessingAtom/External/CubeMapGen/CCubeMapProcessor.cpp b/Gems/Atom/Asset/ImageProcessingAtom/External/CubeMapGen/CCubeMapProcessor.cpp index 26768f8f2e..df31067af0 100644 --- a/Gems/Atom/Asset/ImageProcessingAtom/External/CubeMapGen/CCubeMapProcessor.cpp +++ b/Gems/Atom/Asset/ImageProcessingAtom/External/CubeMapGen/CCubeMapProcessor.cpp @@ -1106,7 +1106,7 @@ namespace ImageProcessingAtom //fractional amount to apply change in tap intensity along edge to taps // in a perpendicular direction to edge CP_ITYPE fixupFrac = (CP_ITYPE)(fixupDist - iFixup) / (CP_ITYPE)(fixupDist); - CP_ITYPE fixupWeight; + CP_ITYPE fixupWeight = 0.0f; switch(a_FixupType ) { diff --git a/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.azsl b/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.azsl index 942db3ed5f..4a228f076c 100644 --- a/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.azsl +++ b/Gems/Atom/Feature/Common/Assets/Materials/Special/ShadowCatcher.azsl @@ -37,7 +37,7 @@ ShaderResourceGroup MaterialSrg : SRG_PerMaterial } #include -#include +#include #include #include diff --git a/Gems/Atom/Feature/Common/Assets/Passes/TransparentParent.pass b/Gems/Atom/Feature/Common/Assets/Passes/TransparentParent.pass index b278f2bcb4..a9db59c646 100644 --- a/Gems/Atom/Feature/Common/Assets/Passes/TransparentParent.pass +++ b/Gems/Atom/Feature/Common/Assets/Passes/TransparentParent.pass @@ -124,7 +124,7 @@ "DrawListSortType": "KeyThenReverseDepth", "PipelineViewTag": "MainCamera", "PassSrgAsset": { - "FilePath": "shaderlib/atom/features/pbr/transparentpasssrg.azsli:PassSrg" + "FilePath": "shaderlib/atom/features/pbr/forwardpasssrg.azsli:PassSrg" } } } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/ForwardPassSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/ForwardPassSrg.azsli index 14cff21739..d9367f9d03 100644 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/ForwardPassSrg.azsli +++ b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/ForwardPassSrg.azsli @@ -35,4 +35,5 @@ ShaderResourceGroup PassSrg : SRG_PerPass Texture2D m_tileLightData; StructuredBuffer m_lightListRemapped; + Texture2D m_linearDepthTexture; } diff --git a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/TransparentPassSrg.azsli b/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/TransparentPassSrg.azsli deleted file mode 100644 index d9367f9d03..0000000000 --- a/Gems/Atom/Feature/Common/Assets/ShaderLib/Atom/Features/PBR/TransparentPassSrg.azsli +++ /dev/null @@ -1,39 +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. -* -*/ - -#pragma once - -#include - -ShaderResourceGroup PassSrg : SRG_PerPass -{ - // [GFX TODO][ATOM-2012] adapt to multiple shadowmaps - Texture2DArray m_directionalLightShadowmap; - Texture2DArray m_directionalLightExponentialShadowmap; - Texture2DArray m_projectedShadowmaps; - Texture2DArray m_projectedExponentialShadowmap; - Texture2D m_brdfMap; - - Sampler LinearSampler - { - MinFilter = Linear; - MagFilter = Linear; - MipFilter = Linear; - AddressU = Clamp; - AddressV = Clamp; - AddressW = Clamp; - }; - - Texture2D m_tileLightData; - StructuredBuffer m_lightListRemapped; - Texture2D m_linearDepthTexture; -} diff --git a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake index 359c0b9b20..a9ba765329 100644 --- a/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake +++ b/Gems/Atom/Feature/Common/Assets/atom_feature_common_asset_files.cmake @@ -246,7 +246,6 @@ set(FILES ShaderLib/Atom/Features/PBR/Hammersley.azsli ShaderLib/Atom/Features/PBR/LightingOptions.azsli ShaderLib/Atom/Features/PBR/LightingUtils.azsli - ShaderLib/Atom/Features/PBR/TransparentPassSrg.azsli ShaderLib/Atom/Features/PBR/Lighting/DualSpecularLighting.azsli ShaderLib/Atom/Features/PBR/Lighting/EnhancedLighting.azsli ShaderLib/Atom/Features/PBR/Lighting/LightingData.azsli diff --git a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DiskLightFeatureProcessorInterface.h b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DiskLightFeatureProcessorInterface.h index 50c0aa2455..ce911fecf7 100644 --- a/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DiskLightFeatureProcessorInterface.h +++ b/Gems/Atom/Feature/Common/Code/Include/Atom/Feature/CoreLights/DiskLightFeatureProcessorInterface.h @@ -46,8 +46,6 @@ namespace AZ uint16_t m_padding; // Explicit padding. }; - static constexpr size_t size = sizeof(DiskLightData); - //! DiskLightFeatureProcessorInterface provides an interface to acquire, release, and update a disk light. This is necessary for code outside of //! the Atom features gem to communicate with the DiskLightFeatureProcessor. class DiskLightFeatureProcessorInterface diff --git a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp index ffb5469aef..029a81ec49 100644 --- a/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp +++ b/Gems/Atom/Feature/Common/Code/Source/Material/MaterialAssignment.cpp @@ -12,7 +12,9 @@ #include #include +#include #include +#include namespace AZ { @@ -22,6 +24,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..a895c04b95 --- /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 +#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/Atom/RHI/DX12/Code/Source/RHI/FrameGraphCompiler.cpp b/Gems/Atom/RHI/DX12/Code/Source/RHI/FrameGraphCompiler.cpp index 7fc1db179a..e4fff11d32 100644 --- a/Gems/Atom/RHI/DX12/Code/Source/RHI/FrameGraphCompiler.cpp +++ b/Gems/Atom/RHI/DX12/Code/Source/RHI/FrameGraphCompiler.cpp @@ -484,6 +484,7 @@ namespace AZ } D3D12_RESOURCE_TRANSITION_BARRIER transition; + memset(&transition, 0, sizeof(D3D12_RESOURCE_TRANSITION_BARRIER)); // C4701 potentially unitialized local variable 'transition' used transition.pResource = image.GetMemoryView().GetMemory(); Scope& firstScope = static_cast(scopeAttachment->GetScope()); diff --git a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/Queue.cpp b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/Queue.cpp index 5bebc92125..88d103d282 100644 --- a/Gems/Atom/RHI/Vulkan/Code/Source/RHI/Queue.cpp +++ b/Gems/Atom/RHI/Vulkan/Code/Source/RHI/Queue.cpp @@ -45,7 +45,7 @@ namespace AZ Fence* fenceToSignal) { AZStd::vector vkCommandBuffers; - AZStd::vector vkWaitSemaphores; + AZStd::vector vkWaitSemaphoreVector; // vulkan.h has a #define called vkWaitSemaphores, so we name this differently AZStd::vector vkWaitPipelineStages; AZStd::vector vkSignalSemaphores; VkSubmitInfo submitInfo; @@ -65,11 +65,11 @@ namespace AZ return item->GetNativeSemaphore(); }); vkWaitPipelineStages.reserve(waitSemaphoresInfo.size()); - vkWaitSemaphores.reserve(waitSemaphoresInfo.size()); + vkWaitSemaphoreVector.reserve(waitSemaphoresInfo.size()); AZStd::for_each(waitSemaphoresInfo.begin(), waitSemaphoresInfo.end(), [&](auto& item) { vkWaitPipelineStages.push_back(item.first); - vkWaitSemaphores.push_back(item.second->GetNativeSemaphore()); + vkWaitSemaphoreVector.push_back(item.second->GetNativeSemaphore()); // Wait until the wait semaphores has been submitted for signaling. item.second->WaitEvent(); }); @@ -77,8 +77,8 @@ namespace AZ submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.pNext = nullptr; - submitInfo.waitSemaphoreCount = static_cast(vkWaitSemaphores.size()); - submitInfo.pWaitSemaphores = vkWaitSemaphores.empty() ? nullptr : vkWaitSemaphores.data(); + submitInfo.waitSemaphoreCount = static_cast(vkWaitSemaphoreVector.size()); + submitInfo.pWaitSemaphores = vkWaitSemaphoreVector.empty() ? nullptr : vkWaitSemaphoreVector.data(); submitInfo.pWaitDstStageMask = vkWaitPipelineStages.empty() ? nullptr : vkWaitPipelineStages.data(); submitInfo.commandBufferCount = static_cast(vkCommandBuffers.size()); submitInfo.pCommandBuffers = vkCommandBuffers.empty() ? nullptr : vkCommandBuffers.data(); 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/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/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/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/Culling.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp index c64f08e4f8..c5e9d949aa 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Culling.cpp @@ -239,16 +239,26 @@ namespace AZ void CullingScene::RegisterOrUpdateCullable(Cullable& cullable) { - m_cullDataConcurrencyCheck.soft_lock(); + // Multiple threads can call RegisterOrUpdateCullable at the same time + // since the underlying visScene is thread safe, but if you're inserting or + // updating between BeginCulling and EndCulling, you'll get non-deterministic + // results depending on a race condition if you happen to update before or after + // the culling system starts Enumerating, so use soft_lock_shared here + m_cullDataConcurrencyCheck.soft_lock_shared(); m_visScene->InsertOrUpdateEntry(cullable.m_cullData.m_visibilityEntry); - m_cullDataConcurrencyCheck.soft_unlock(); + m_cullDataConcurrencyCheck.soft_unlock_shared(); } void CullingScene::UnregisterCullable(Cullable& cullable) { - m_cullDataConcurrencyCheck.soft_lock(); + // Multiple threads can call RegisterOrUpdateCullable at the same time + // since the underlying visScene is thread safe, but if you're inserting or + // updating between BeginCulling and EndCulling, you'll get non-deterministic + // results depending on a race condition if you happen to update before or after + // the culling system starts Enumerating, so use soft_lock_shared here + m_cullDataConcurrencyCheck.soft_lock_shared(); m_visScene->RemoveEntry(cullable.m_cullData.m_visibilityEntry); - m_cullDataConcurrencyCheck.soft_unlock(); + m_cullDataConcurrencyCheck.soft_unlock_shared(); } uint32_t CullingScene::GetNumCullables() const 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)) { 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/Source/RPI.Reflect/Shader/ShaderAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp index 0a59772d6c..cf655d43f7 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderAsset.cpp @@ -10,6 +10,7 @@ * */ #include +#include #include #include @@ -34,10 +35,6 @@ namespace AZ uint32_t ShaderAsset::MakeAssetProductSubId(uint32_t rhiApiUniqueIndex, uint32_t subProductType) { - static constexpr uint32_t RhiIndexBitPosition = 30; - static constexpr uint32_t RhiIndexNumBits = 32 - RhiIndexBitPosition; - static constexpr uint32_t RhiIndexMaxValue = (1 << RhiIndexNumBits) - 1; - static constexpr uint32_t SubProductTypeBitPosition = 0; static constexpr uint32_t SubProductTypeNumBits = RhiIndexBitPosition - SubProductTypeBitPosition; static constexpr uint32_t SubProductTypeMaxValue = (1 << SubProductTypeNumBits) - 1; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp index 7864768351..5bcdcbb569 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Shader/ShaderVariantAsset.cpp @@ -10,6 +10,7 @@ * */ #include +#include #include #include @@ -24,10 +25,6 @@ namespace AZ uint32_t ShaderVariantAsset::MakeAssetProductSubId( uint32_t rhiApiUniqueIndex, ShaderVariantStableId variantStableId, uint32_t subProductType) { - static constexpr uint32_t RhiIndexBitPosition = 30; - static constexpr uint32_t RhiIndexNumBits = 32 - RhiIndexBitPosition; - static constexpr uint32_t RhiIndexMaxValue = (1 << RhiIndexNumBits) - 1; - static constexpr uint32_t SubProductTypeBitPosition = 17; static constexpr uint32_t SubProductTypeNumBits = RhiIndexBitPosition - SubProductTypeBitPosition; static constexpr uint32_t SubProductTypeMaxValue = (1 << SubProductTypeNumBits) - 1; 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/scripts/project_manager/CMakeLists.txt b/Gems/Atom/RPI/Tools/CMakeLists.txt similarity index 60% rename from scripts/project_manager/CMakeLists.txt rename to Gems/Atom/RPI/Tools/CMakeLists.txt index 36c9ad0360..35df664ab2 100644 --- a/scripts/project_manager/CMakeLists.txt +++ b/Gems/Atom/RPI/Tools/CMakeLists.txt @@ -9,14 +9,15 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # -ly_download_associated_package(pyside2) +if (PAL_TRAIT_BUILD_HOST_TOOLS) + ly_pip_install_local_package_editable(${CMAKE_CURRENT_LIST_DIR} atom_rpi_tools) -ly_add_pytest( - NAME test_pyside - PATH ${CMAKE_CURRENT_LIST_DIR}/tests/test_pyside.py -) + 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() -ly_add_pytest( - NAME test_projects - PATH ${CMAKE_CURRENT_LIST_DIR}/tests/test_projects.py -) \ No newline at end of file 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']) + ) diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h index 1318deb355..b88b340926 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h @@ -51,7 +51,8 @@ namespace AtomToolsFramework void UpdateViewport(const AzFramework::ViewportControllerUpdateEvent& event) override; // ModularViewportCameraControllerRequestBus overrides ... - void InterpolateToTransform(const AZ::Transform& worldFromLocal) override; + void InterpolateToTransform(const AZ::Transform& worldFromLocal, float lookAtDistance) override; + AZStd::optional LookAtAfterInterpolation() const override; private: // AzFramework::ViewportDebugDisplayEventBus overrides ... @@ -71,6 +72,8 @@ namespace AtomToolsFramework AZ::Transform m_transformEnd = AZ::Transform::CreateIdentity(); float m_animationT = 0.0f; CameraMode m_cameraMode = CameraMode::Control; + AZStd::optional m_lookAtAfterInterpolation; //!< The look at point after an interpolation has finished. + //!< Will be cleared when the view changes (camera looks away). bool m_updatingTransform = false; AZ::RPI::ViewportContext::MatrixChangedEvent::Handler m_cameraViewMatrixChangeHandler; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h index 5b90119372..a7f067cdf4 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h @@ -32,7 +32,12 @@ namespace AtomToolsFramework static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; //! Begin a smooth transition of the camera to the requested transform. - virtual void InterpolateToTransform(const AZ::Transform& worldFromLocal) = 0; + //! @param worldFromLocal The transform of where the camera should end up. + //! @param lookAtDistance The distance between the camera transform and the imagined look at point. + virtual void InterpolateToTransform(const AZ::Transform& worldFromLocal, float lookAtDistance) = 0; + + //! Look at point after an interpolation has finished and no translation has occurred. + virtual AZStd::optional LookAtAfterInterpolation() const = 0; protected: ~ModularViewportCameraControllerRequests() = default; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp index 896d9f8043..082dc8f272 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Viewport/ModularViewportCameraController.cpp @@ -140,6 +140,18 @@ namespace AtomToolsFramework m_targetCamera = m_cameraSystem.StepCamera(m_targetCamera, event.m_deltaTime.count()); m_camera = AzFramework::SmoothCamera(m_camera, m_targetCamera, event.m_deltaTime.count()); + // if there has been an interpolation, only clear the look at point if it is no longer + // centered in the view (the camera has looked away from it) + if (m_lookAtAfterInterpolation.has_value()) + { + if (const float lookDirection = + (*m_lookAtAfterInterpolation - m_camera.Translation()).GetNormalized().Dot(m_camera.Transform().GetBasisY()); + !AZ::IsCloseMag(lookDirection, 1.0f, 0.001f)) + { + m_lookAtAfterInterpolation = {}; + } + } + viewportContext->SetCameraTransform(m_camera.Transform()); } else if (m_cameraMode == CameraMode::Animation) @@ -148,8 +160,8 @@ namespace AtomToolsFramework { return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); }; - const float transitionT = smootherStepFn(m_animationT); + const float transitionT = smootherStepFn(m_animationT); const AZ::Transform current = AZ::Transform::CreateFromQuaternionAndTranslation( m_transformStart.GetRotation().Slerp(m_transformEnd.GetRotation(), transitionT), m_transformStart.GetTranslation().Lerp(m_transformEnd.GetTranslation(), transitionT)); @@ -185,11 +197,17 @@ namespace AtomToolsFramework } } - void ModernViewportCameraControllerInstance::InterpolateToTransform(const AZ::Transform& worldFromLocal) + void ModernViewportCameraControllerInstance::InterpolateToTransform(const AZ::Transform& worldFromLocal, const float lookAtDistance) { m_animationT = 0.0f; m_cameraMode = CameraMode::Animation; m_transformStart = m_camera.Transform(); m_transformEnd = worldFromLocal; + m_lookAtAfterInterpolation = m_transformEnd.GetTranslation() + m_transformEnd.GetBasisY() * lookAtDistance; + } + + AZStd::optional ModernViewportCameraControllerInstance::LookAtAfterInterpolation() const + { + return m_lookAtAfterInterpolation; } } // namespace AtomToolsFramework 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) 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()) 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; } } diff --git a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Code/CMakeLists.txt b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Code/CMakeLists.txt index 76cbf6546d..bad0d743c7 100644 --- a/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Code/CMakeLists.txt +++ b/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/Code/CMakeLists.txt @@ -9,6 +9,10 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +if(NOT PAL_TRAIT_BUILD_HOST_TOOLS) + return() +endif() + ly_add_target( NAME DccScriptingInterface.Static STATIC NAMESPACE Gem @@ -38,3 +42,9 @@ ly_add_target( PRIVATE Gem::DccScriptingInterface.Static ) + +# Any 'tool' type applications should use Gem::DccScriptingInterface.Editor: +ly_create_alias(NAME DccScriptingInterface.Tools NAMESPACE Gem TARGETS Gem::DccScriptingInterface.Editor) +# Add an empty 'builders' alias to allow the DccScriptInterface root gem path to be added to the generated +# cmake_dependencies..assetprocessor.setreg to allow the asset scan folder for it to be added +ly_create_alias(NAME DccScriptingInterface.Builders NAMESPACE Gem) diff --git a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/Commands.cpp b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/Commands.cpp index 294f4c7c99..46760af8f0 100644 --- a/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/Commands.cpp +++ b/Gems/EMotionFX/Code/EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/Commands.cpp @@ -218,6 +218,10 @@ namespace EMStudio } const bool saveResult = manifest.SaveToFile(manifestFilename.c_str()); + if (saveResult) + { + actor->SetDirtyFlag(false); + } // Source Control: Add file in case it did not exist before (when saving it the first time). if (saveResult && !fileExisted) diff --git a/Gems/EMotionFX/Code/Tests/BoolLogicNodeTests.cpp b/Gems/EMotionFX/Code/Tests/BoolLogicNodeTests.cpp index 30950790d9..218bea2169 100644 --- a/Gems/EMotionFX/Code/Tests/BoolLogicNodeTests.cpp +++ b/Gems/EMotionFX/Code/Tests/BoolLogicNodeTests.cpp @@ -165,8 +165,8 @@ namespace EMotionFX const AZ::Outcome boolYParamIndexOutcome = m_animGraphInstance->FindParameterIndex(nameBoolY); success = boolXParamIndexOutcome.IsSuccess() && boolYParamIndexOutcome.IsSuccess(); - uint32 boolXOutputPortIndex; - uint32 boolYOutputPortIndex; + uint32 boolXOutputPortIndex = InvalidIndex32; + uint32 boolYOutputPortIndex = InvalidIndex32; const int portIndicesTosetCount = 2; int portIndicesFound = 0; const AZStd::vector& parameterNodeOutputPorts = parameterNode->GetOutputPorts(); diff --git a/Gems/FastNoise/Code/External/FastNoise/FastNoise.cpp b/Gems/FastNoise/Code/External/FastNoise/FastNoise.cpp index fce7d6498a..3fe7bf45fe 100644 --- a/Gems/FastNoise/Code/External/FastNoise/FastNoise.cpp +++ b/Gems/FastNoise/Code/External/FastNoise/FastNoise.cpp @@ -612,7 +612,9 @@ FN_DECIMAL FastNoise::SingleValue(unsigned char offset, FN_DECIMAL x, FN_DECIMAL int y1 = y0 + 1; int z1 = z0 + 1; - FN_DECIMAL xs, ys, zs; + FN_DECIMAL xs = 0.0f; + FN_DECIMAL ys = 0.0f; + FN_DECIMAL zs = 0.0f; switch (m_interp) { case Linear: @@ -726,7 +728,8 @@ FN_DECIMAL FastNoise::SingleValue(unsigned char offset, FN_DECIMAL x, FN_DECIMAL int x1 = x0 + 1; int y1 = y0 + 1; - FN_DECIMAL xs, ys; + FN_DECIMAL xs = 0.0f; + FN_DECIMAL ys = 0.0f; switch (m_interp) { case Linear: @@ -840,7 +843,9 @@ FN_DECIMAL FastNoise::SinglePerlin(unsigned char offset, FN_DECIMAL x, FN_DECIMA int y1 = y0 + 1; int z1 = z0 + 1; - FN_DECIMAL xs, ys, zs; + FN_DECIMAL xs = 0.0f; + FN_DECIMAL ys = 0.0f; + FN_DECIMAL zs = 0.0f; switch (m_interp) { case Linear: @@ -962,7 +967,8 @@ FN_DECIMAL FastNoise::SinglePerlin(unsigned char offset, FN_DECIMAL x, FN_DECIMA int x1 = x0 + 1; int y1 = y0 + 1; - FN_DECIMAL xs, ys; + FN_DECIMAL xs = 0.0f; + FN_DECIMAL ys = 0.0f; switch (m_interp) { case Linear: @@ -1699,7 +1705,9 @@ FN_DECIMAL FastNoise::SingleCellular(FN_DECIMAL x, FN_DECIMAL y, FN_DECIMAL z) c int zr = FastRound(z); FN_DECIMAL distance = 999999; - int xc, yc, zc; + int xc = 0; + int yc = 0; + int zc = 0; switch (m_cellularDistanceFunction) { @@ -1923,7 +1931,8 @@ FN_DECIMAL FastNoise::SingleCellular(FN_DECIMAL x, FN_DECIMAL y) const int yr = FastRound(y); FN_DECIMAL distance = 999999; - int xc, yc; + int xc = 0; + int yc = 0; switch (m_cellularDistanceFunction) { diff --git a/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Styling/Parser.cpp b/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Styling/Parser.cpp index 319996949b..db07c3ca8e 100644 --- a/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Styling/Parser.cpp +++ b/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Styling/Parser.cpp @@ -1072,8 +1072,7 @@ namespace GraphCanvas if (!id.empty()) { - Selector selector = Selector::Get(id); - result.emplace_back(selector); + result.emplace_back(Selector::Get(id)); continue; } @@ -1111,8 +1110,7 @@ namespace GraphCanvas { bits.emplace_back(stateSelector); } - Selector selector = aznew CompoundSelector(std::move(bits)); - nestedSelectors.emplace_back(selector); + nestedSelectors.emplace_back(aznew CompoundSelector(std::move(bits))); } } diff --git a/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Utils/GraphUtils.cpp b/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Utils/GraphUtils.cpp index 28fb760c20..c2574a69f6 100644 --- a/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Utils/GraphUtils.cpp +++ b/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Utils/GraphUtils.cpp @@ -1239,7 +1239,7 @@ namespace GraphCanvas bool GraphUtils::IsValidModelConnection(const GraphId& graphId, const Endpoint& sourceEndpoint, const Endpoint& targetEndpoint) { - bool validConnection; + bool validConnection = false; AZStd::unordered_set< Endpoint > finalSourceEndpoints = RemapEndpointForModel(sourceEndpoint); AZStd::unordered_set< Endpoint > finalTargetEndpoints = RemapEndpointForModel(targetEndpoint); diff --git a/Gems/LyShine/Code/Editor/Animation/Controls/UiTimelineCtrl.cpp b/Gems/LyShine/Code/Editor/Animation/Controls/UiTimelineCtrl.cpp index 9f7fae6041..0b18539c54 100644 --- a/Gems/LyShine/Code/Editor/Animation/Controls/UiTimelineCtrl.cpp +++ b/Gems/LyShine/Code/Editor/Animation/Controls/UiTimelineCtrl.cpp @@ -23,10 +23,6 @@ #include #include -static const QColor timeMarkerCol = QColor(255, 0, 255); -static const QColor textCol = QColor(0, 0, 0); -static const QColor ltgrayCol = QColor(110, 110, 110); - QColor InterpolateColor(const QColor& c1, const QColor& c2, float fraction) { const int r = (c2.red() - c1.red()) * fraction + c1.red(); diff --git a/Gems/LyShine/Code/Source/Animation/AnimNode.cpp b/Gems/LyShine/Code/Source/Animation/AnimNode.cpp index 421e9a4fe3..369f080a15 100644 --- a/Gems/LyShine/Code/Source/Animation/AnimNode.cpp +++ b/Gems/LyShine/Code/Source/Animation/AnimNode.cpp @@ -48,7 +48,6 @@ static const EUiAnimCurveType DEFAULT_TRACK_TYPE = eUiAnimCurveType_BezierFloat; // Old serialization values that are no longer // defined in IUiAnimationSystem.h, but needed for conversion: -static const int OLD_APARAM_USER = 100; static const int OLD_ACURVE_GOTO = 21; static const int OLD_APARAM_PARTICLE_COUNT_SCALE = 95; static const int OLD_APARAM_PARTICLE_PULSE_PERIOD = 96; diff --git a/Gems/LyShine/Code/Source/Animation/AzEntityNode.cpp b/Gems/LyShine/Code/Source/Animation/AzEntityNode.cpp index 7ff46f285f..2d24a84c71 100644 --- a/Gems/LyShine/Code/Source/Animation/AzEntityNode.cpp +++ b/Gems/LyShine/Code/Source/Animation/AzEntityNode.cpp @@ -691,7 +691,7 @@ IUiAnimTrack* CUiAnimAzEntityNode::CreateTrackForAzField(const UiAnimParamData& return nullptr; } - EUiAnimValue valueType; + EUiAnimValue valueType = eUiAnimValue_Unknown; switch (numElements) { case 2: diff --git a/Gems/LyShine/Code/Source/LyShineSystemComponent.cpp b/Gems/LyShine/Code/Source/LyShineSystemComponent.cpp index 0eab7705f6..1290683145 100644 --- a/Gems/LyShine/Code/Source/LyShineSystemComponent.cpp +++ b/Gems/LyShine/Code/Source/LyShineSystemComponent.cpp @@ -375,7 +375,7 @@ namespace LyShine } /////////////////////////////////////////////////////////////////////////////////////////////// - void LyShineSystemComponent::OnCrySystemInitialized(ISystem& system, [[maybe_unused]] const SSystemInitParams& startupParams) + void LyShineSystemComponent::OnCrySystemInitialized([[maybe_unused]] ISystem& system, [[maybe_unused]] const SSystemInitParams& startupParams) { #if !defined(AZ_MONOLITHIC_BUILD) // When module is linked dynamically, we must set our gEnv pointer. diff --git a/Gems/LyShine/Code/Source/UiLayoutGridComponent.cpp b/Gems/LyShine/Code/Source/UiLayoutGridComponent.cpp index e39b9cc684..4c5d48911f 100644 --- a/Gems/LyShine/Code/Source/UiLayoutGridComponent.cpp +++ b/Gems/LyShine/Code/Source/UiLayoutGridComponent.cpp @@ -107,7 +107,8 @@ void UiLayoutGridComponent::ApplyLayoutHeight() AZStd::vector childEntityIds; EBUS_EVENT_ID_RESULT(childEntityIds, GetEntityId(), UiElementBus, GetChildEntityIds); int childIndex = 0; - int columnIndex, rowIndex; + int columnIndex = 0; + int rowIndex = 0; for (auto child : childEntityIds) { // Set the anchors @@ -627,7 +628,8 @@ AZ::Vector2 UiLayoutGridComponent::GetChildrenBoundingRectSize(const AZ::Vector2 UiLayoutHelpers::GetSizeInsidePadding(GetEntityId(), m_padding, layoutRectSize); // Calculate number of rows and columns - int numColumns, numRows; + int numColumns = 0; + int numRows = 0; switch (m_startingDirection) { case StartingDirection::HorizontalOrder: diff --git a/Gems/LyShine/Code/Source/UiNavigationHelpers.cpp b/Gems/LyShine/Code/Source/UiNavigationHelpers.cpp index 0c1b6765ce..79084c8bbe 100644 --- a/Gems/LyShine/Code/Source/UiNavigationHelpers.cpp +++ b/Gems/LyShine/Code/Source/UiNavigationHelpers.cpp @@ -151,6 +151,8 @@ namespace UiNavigationHelpers } UiTransformInterface::Rect parentRect; + parentRect.Set(0.0f, 0.0f, 0.0f, 0.0f); + AZ::Matrix4x4 parentTransformFromViewport; if (parentElement.IsValid() && !isCurElementDescendantOfParentElement) { diff --git a/Gems/Maestro/Code/Source/Cinematics/AnimNode.cpp b/Gems/Maestro/Code/Source/Cinematics/AnimNode.cpp index 7fea8097ed..8398ad47fd 100644 --- a/Gems/Maestro/Code/Source/Cinematics/AnimNode.cpp +++ b/Gems/Maestro/Code/Source/Cinematics/AnimNode.cpp @@ -60,7 +60,6 @@ static const EAnimCurveType DEFAULT_TRACK_TYPE = eAnimCurveType_BezierFloat; // Old serialization values that are no longer // defined in IMovieSystem.h, but needed for conversion: -static const int OLD_APARAM_USER = 100; static const int OLD_ACURVE_GOTO = 21; static const int OLD_APARAM_PARTICLE_COUNT_SCALE = 95; static const int OLD_APARAM_PARTICLE_PULSE_PERIOD = 96; diff --git a/Gems/Maestro/Code/Source/Cinematics/AnimPostFXNode.cpp b/Gems/Maestro/Code/Source/Cinematics/AnimPostFXNode.cpp index 67b6ab5cdb..992527d2c4 100644 --- a/Gems/Maestro/Code/Source/Cinematics/AnimPostFXNode.cpp +++ b/Gems/Maestro/Code/Source/Cinematics/AnimPostFXNode.cpp @@ -281,7 +281,6 @@ void CAnimPostFXNode::SerializeAnims(XmlNodeRef& xmlNode, bool bLoading, bool bL paramType.Serialize(trackNode, true); // Don't use APARAM_USER because it could change in newer versions // CAnimNode::SerializeAnims will then take care of that - static const unsigned int OLD_APARAM_USER = 100; paramType = static_cast(static_cast(paramType.GetType()) + OLD_APARAM_USER); paramType.Serialize(trackNode, false); } diff --git a/Gems/Microphone/Code/Source/Platform/Windows/MicrophoneSystemComponent_Windows.cpp b/Gems/Microphone/Code/Source/Platform/Windows/MicrophoneSystemComponent_Windows.cpp index 8b3e3c2ce6..2eb840b253 100644 --- a/Gems/Microphone/Code/Source/Platform/Windows/MicrophoneSystemComponent_Windows.cpp +++ b/Gems/Microphone/Code/Source/Platform/Windows/MicrophoneSystemComponent_Windows.cpp @@ -51,11 +51,11 @@ namespace Audio // To avoid errors, we initialize COM here with the same model. CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); - const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); - const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); + const CLSID CLSID_MMDeviceEnumerator_UUID = __uuidof(MMDeviceEnumerator); + const IID IID_IMMDeviceEnumerator_UUID = __uuidof(IMMDeviceEnumerator); HRESULT hresult = CoCreateInstance( - CLSID_MMDeviceEnumerator, nullptr, - CLSCTX_ALL, IID_IMMDeviceEnumerator, + CLSID_MMDeviceEnumerator_UUID, nullptr, + CLSCTX_ALL, IID_IMMDeviceEnumerator_UUID, reinterpret_cast(&m_enumerator) ); @@ -133,8 +133,8 @@ namespace Audio AZ_Assert(m_device != nullptr, "Attempting to start a Microphone session while the device is uninitialized - Windows!\n"); // Get the IAudioClient from the device - const IID IID_IAudioClient = __uuidof(IAudioClient); - HRESULT hresult = m_device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, reinterpret_cast(&m_audioClient)); + const IID IID_IAudioClient_UUID = __uuidof(IAudioClient); + HRESULT hresult = m_device->Activate(IID_IAudioClient_UUID, CLSCTX_ALL, nullptr, reinterpret_cast(&m_audioClient)); if (FAILED(hresult)) { @@ -182,8 +182,8 @@ namespace Audio } // Get the IAudioCaptureClient - const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient); - hresult = m_audioClient->GetService(IID_IAudioCaptureClient, reinterpret_cast(&m_audioCaptureClient)); + const IID IID_IAudioCaptureClient_UUID = __uuidof(IAudioCaptureClient); + hresult = m_audioClient->GetService(IID_IAudioCaptureClient_UUID, reinterpret_cast(&m_audioCaptureClient)); if (FAILED(hresult)) { diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h index 4fe60f14a3..7d9b7d4086 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetBindComponent.h @@ -39,6 +39,7 @@ namespace Multiplayer using EntityMigrationStartEvent = AZ::Event; using EntityMigrationEndEvent = AZ::Event<>; using EntityServerMigrationEvent = AZ::Event; + using EntityPreRenderEvent = AZ::Event; //! @class NetBindComponent //! @brief Component that provides net-binding to a networked entity. @@ -97,6 +98,7 @@ namespace Multiplayer void NotifyMigrationStart(ClientInputId migratedInputId); void NotifyMigrationEnd(); void NotifyServerMigration(HostId hostId, AzNetworking::ConnectionId connectionId); + void NotifyPreRender(float deltaTime, float blendFactor); void AddEntityStopEventHandler(EntityStopEvent::Handler& eventHandler); void AddEntityDirtiedEventHandler(EntityDirtiedEvent::Handler& eventHandler); @@ -104,6 +106,7 @@ namespace Multiplayer void AddEntityMigrationStartEventHandler(EntityMigrationStartEvent::Handler& eventHandler); void AddEntityMigrationEndEventHandler(EntityMigrationEndEvent::Handler& eventHandler); void AddEntityServerMigrationEventHandler(EntityServerMigrationEvent::Handler& eventHandler); + void AddEntityPreRenderEventHandler(EntityPreRenderEvent::Handler& eventHandler); bool SerializeEntityCorrection(AzNetworking::ISerializer& serializer); @@ -152,6 +155,7 @@ namespace Multiplayer EntityMigrationStartEvent m_entityMigrationStartEvent; EntityMigrationEndEvent m_entityMigrationEndEvent; EntityServerMigrationEvent m_entityServerMigrationEvent; + EntityPreRenderEvent m_entityPreRenderEvent; AZ::Event<> m_onRemove; RpcSendEvent::Handler m_handleLocalServerRpcMessageEventHandle; AZ::Event<>::Handler m_handleMarkedDirty; diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h index f3eb1922fd..1393e7e12f 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/Components/NetworkTransformComponent.h @@ -13,6 +13,7 @@ #pragma once #include +#include #include namespace Multiplayer @@ -32,13 +33,22 @@ namespace Multiplayer void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override; private: + void OnPreRender(float deltaTime, float blendFactor); + void OnRotationChangedEvent(const AZ::Quaternion& rotation); void OnTranslationChangedEvent(const AZ::Vector3& translation); void OnScaleChangedEvent(float scale); + void OnResetCountChangedEvent(); + + AZ::Transform m_previousTransform = AZ::Transform::CreateIdentity(); + AZ::Transform m_targetTransform = AZ::Transform::CreateIdentity(); AZ::Event::Handler m_rotationEventHandler; AZ::Event::Handler m_translationEventHandler; AZ::Event::Handler m_scaleEventHandler; + AZ::Event::Handler m_resetCountEventHandler; + + EntityPreRenderEvent::Handler m_entityPreRenderEventHandler; }; class NetworkTransformComponentController diff --git a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/ReplicationRecord.h b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/ReplicationRecord.h index 3dfc4b8016..33e1e0bde6 100644 --- a/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/ReplicationRecord.h +++ b/Gems/Multiplayer/Code/Include/Multiplayer/NetworkEntity/EntityReplication/ReplicationRecord.h @@ -45,10 +45,10 @@ namespace Multiplayer static constexpr uint32_t MaxRecordBits = 2048; ReplicationRecord() = default; - ReplicationRecord(NetEntityRole netEntityRole); + ReplicationRecord(NetEntityRole remoteNetEntityRole); - void SetNetworkRole(NetEntityRole netEntityRole); - NetEntityRole GetNetworkRole() const; + void SetRemoteNetworkRole(NetEntityRole remoteNetEntityRole); + NetEntityRole GetRemoteNetworkRole() const; bool AreAllBitsConsumed() const; void ResetConsumedBits(); @@ -92,6 +92,6 @@ namespace Multiplayer // Sequence number this ReplicationRecord was sent on AzNetworking::PacketId m_sentPacketId = AzNetworking::InvalidPacketId; - NetEntityRole m_netEntityRole = NetEntityRole::InvalidRole;; + NetEntityRole m_remoteNetEntityRole = NetEntityRole::InvalidRole;; }; } diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja index 61dcacaa94..05403a00ef 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Common.jinja @@ -175,7 +175,7 @@ void Handle{{ PropertyName }}(AzNetworking::IConnection* invokingConnection, {{ //! {{ PropertyName }} Handler //! {{ Property.attrib['Description'] }} //! HandleOn {{ HandleOn }} -virtual void Handle{{ PropertyName }}(AzNetworking::IConnection* invokingConnection, {{ ', '.join(paramDefines) }}) = 0; +virtual void Handle{{ PropertyName }}([[maybe_unused]] AzNetworking::IConnection* invokingConnection, [[maybe_unused]] {{ ', [[maybe_unused]] '.join(paramDefines) }}) {} {% endif %} {% endmacro %} {# diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja index 5cfd0bfc4d..259f469020 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja @@ -311,7 +311,7 @@ void {{ ClassName }}::Set{{ UpperFirst(Property.attrib['Name']) }}(const {{ Prop {{ AutoComponentMacros.ParseRpcParams(Property, paramNames, paramTypes, paramDefines) }} void {{ ClassName }}::{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(paramDefines) }}) { - constexpr RpcIndex rpcId = static_cast({{ UpperFirst(Component.attrib['Name']) }}Internal::RemoteProcedure::{{ UpperFirst(Property.attrib['Name']) }}); + constexpr Multiplayer::RpcIndex rpcId = static_cast({{ UpperFirst(Component.attrib['Name']) }}Internal::RemoteProcedure::{{ UpperFirst(Property.attrib['Name']) }}); {% if Property.attrib['IsReliable']|booleanTrue %} constexpr AzNetworking::ReliabilityType isReliable = Multiplayer::ReliabilityType::Reliable; {% else %} @@ -368,6 +368,31 @@ void {{ ClassName }}::Signal{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.jo ->Method("{{ UpperFirst(Property.attrib['Name']) }}", [](const {{ ClassName }}* self, {{ ', '.join(paramDefines) }}) { self->m_controller->{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(paramNames) }}); }) + ->Method("{{ UpperFirst(Property.attrib['Name']) }}ByEntity", [](AZ::EntityId id, {{ ', '.join(paramDefines) }}) { + + AZ::Entity* entity = AZ::Interface::Get()->FindEntity(id); + if (!entity) + { + AZ_Warning("Network Property", false, "{{ ClassName }} Get{{ UpperFirst(Property.attrib['Name']) }} failed. The entity with id %s doesn't exist, please provide a valid entity id.", id.ToString().c_str()) + return; + } + + {{ ClassName }}* networkComponent = entity->FindComponent<{{ ClassName }}>(); + if (!networkComponent) + { + AZ_Warning("Network Property", false, "{{ ClassName }} Get{{ UpperFirst(Property.attrib['Name']) }} failed. Entity '%s' (id: %s) is missing {{ ClassName }}, be sure to add {{ ClassName }} to this entity.", entity->GetName().c_str(), id.ToString().c_str()) + return; + } + + {{ ClassName }}Controller* controller = static_cast<{{ ClassName }}Controller*>(networkComponent->GetController()); + if (!controller) + { + AZ_Warning("Network Property", false, "{{ ClassName }} Get{{ UpperFirst(Property.attrib['Name']) }} method failed. Entity '%s' (id: %s) {{ ClassName }} is missing the network controller. This RemoteProcedure can only be invoked from {{InvokeFrom}} network entities, because this entity doesn't have a controller, it must not be a {{InvokeFrom}} entity. Please check your network context before attempting to call {{ UpperFirst(Property.attrib['Name']) }}.", entity->GetName().c_str(), id.ToString().c_str()) + return; + } + + controller->{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(paramNames) }}); + }) {% endif %} {% endcall %} {% endmacro %} diff --git a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp index 612601883c..97e194dccb 100644 --- a/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/LocalPredictionPlayerInputComponent.cpp @@ -25,6 +25,7 @@ namespace Multiplayer AZ_CVAR(AZ::TimeMs, cl_MaxRewindHistoryMs, AZ::TimeMs{ 2000 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum number of milliseconds to keep for server correction rewind and replay"); #ifndef AZ_RELEASE_BUILD AZ_CVAR(float, cl_DebugHackTimeMultiplier, 1.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "Scalar value used to simulate clock hacking cheats for validating bank time system and anticheat"); + AZ_CVAR(bool, cl_EnableDesyncDebugging, false, nullptr, AZ::ConsoleFunctorFlags::Null, "If enabled, debug logs will contain verbose information on detected state desyncs"); #endif AZ_CVAR(bool, sv_EnableCorrections, true, nullptr, AZ::ConsoleFunctorFlags::Null, "Enables server corrections on autonomous proxy desyncs"); @@ -214,11 +215,12 @@ namespace Multiplayer // Send correction SendClientInputCorrection(GetLastInputId(), correction); -#ifdef _DEBUG - // In debug, show which states caused the correction +#ifndef AZ_RELEASE_BUILD AZStd::string clientStateString; AZStd::string serverStateString; + if (cl_EnableDesyncDebugging) { + // In debug, show which states caused the correction // Write in client state AzNetworking::NetworkOutputSerializer clientStateSerializer(clientState.GetBuffer(), clientState.GetSize()); GetNetBindComponent()->SerializeEntityCorrection(clientStateSerializer); @@ -236,11 +238,13 @@ namespace Multiplayer GetNetBindComponent()->SerializeEntityCorrection(serverValues); AZStd::map> mapComparison; + // put the server value in the first part of the pair for (const auto& pair : serverValues.GetValueMap()) { mapComparison[pair.first].first = pair.second; } + // put the client value in the second part of the pair for (const auto& pair : clientValues.GetValueMap()) { @@ -266,12 +270,13 @@ namespace Multiplayer } } } -#else - const AZStd::string clientStateString = "available in debug only"; - const AZStd::string serverStateString = "available in debug only"; -#endif - + else + { + clientStateString = "available in debug only"; + serverStateString = "available in debug only"; + } AZLOG_ERROR("** Autonomous proxy desync detected! ** clientState=[%s], serverState=[%s]", clientStateString.c_str(), serverStateString.c_str()); +#endif } } } @@ -416,7 +421,7 @@ namespace Multiplayer ClientInputId LocalPredictionPlayerInputComponentController::GetLastInputId() const { - return m_clientInputId; + return m_lastClientInputId; } HostFrameId LocalPredictionPlayerInputComponentController::GetInputFrameId(const NetworkInput& input) const @@ -520,10 +525,13 @@ namespace Multiplayer // In debug, send the entire client output state to the server to make it easier to debug desync issues AzNetworking::PacketEncodingBuffer processInputResult; -#ifdef _DEBUG - AzNetworking::NetworkInputSerializer processInputResultSerializer(processInputResult.GetBuffer(), processInputResult.GetCapacity()); - GetNetBindComponent()->SerializeEntityCorrection(processInputResultSerializer); - processInputResult.Resize(processInputResultSerializer.GetSize()); +#ifndef AZ_RELEASE_BUILD + if (cl_EnableDesyncDebugging) + { + AzNetworking::NetworkInputSerializer processInputResultSerializer(processInputResult.GetBuffer(), processInputResult.GetCapacity()); + GetNetBindComponent()->SerializeEntityCorrection(processInputResultSerializer); + processInputResult.Resize(processInputResultSerializer.GetSize()); + } #endif // Save this input and discard move history outside our client rewind window diff --git a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp index d91bba2e0c..0847d42dd6 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetBindComponent.cpp @@ -390,6 +390,11 @@ namespace Multiplayer m_entityServerMigrationEvent.Signal(m_netEntityHandle, hostId, connectionId); } + void NetBindComponent::NotifyPreRender(float deltaTime, float blendFactor) + { + m_entityPreRenderEvent.Signal(deltaTime, blendFactor); + } + void NetBindComponent::AddEntityStopEventHandler(EntityStopEvent::Handler& eventHandler) { eventHandler.Connect(m_entityStopEvent); @@ -420,6 +425,11 @@ namespace Multiplayer eventHandler.Connect(m_entityServerMigrationEvent); } + void NetBindComponent::AddEntityPreRenderEventHandler(EntityPreRenderEvent::Handler& eventHandler) + { + eventHandler.Connect(m_entityPreRenderEvent); + } + bool NetBindComponent::SerializeEntityCorrection(AzNetworking::ISerializer& serializer) { m_predictableRecord.ResetConsumedBits(); diff --git a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp index 682f7ea988..bb256701ff 100644 --- a/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Components/NetworkTransformComponent.cpp @@ -33,6 +33,8 @@ namespace Multiplayer : m_rotationEventHandler([this](const AZ::Quaternion& rotation) { OnRotationChangedEvent(rotation); }) , m_translationEventHandler([this](const AZ::Vector3& translation) { OnTranslationChangedEvent(translation); }) , m_scaleEventHandler([this](float scale) { OnScaleChangedEvent(scale); }) + , m_resetCountEventHandler([this](const uint8_t&) { OnResetCountChangedEvent(); }) + , m_entityPreRenderEventHandler([this](float deltaTime, float blendFactor) { OnPreRender(deltaTime, blendFactor); }) { ; } @@ -47,6 +49,11 @@ namespace Multiplayer RotationAddEvent(m_rotationEventHandler); TranslationAddEvent(m_translationEventHandler); ScaleAddEvent(m_scaleEventHandler); + ResetCountAddEvent(m_resetCountEventHandler); + GetNetBindComponent()->AddEntityPreRenderEventHandler(m_entityPreRenderEventHandler); + + // When coming into relevance, reset all blending factors so we don't interpolate to our start position + OnResetCountChangedEvent(); } void NetworkTransformComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating) @@ -56,23 +63,40 @@ namespace Multiplayer void NetworkTransformComponent::OnRotationChangedEvent(const AZ::Quaternion& rotation) { - AZ::Transform worldTm = GetTransformComponent()->GetWorldTM(); - worldTm.SetRotation(rotation); - GetTransformComponent()->SetWorldTM(worldTm); + m_previousTransform.SetRotation(m_targetTransform.GetRotation()); + m_targetTransform.SetRotation(rotation); } void NetworkTransformComponent::OnTranslationChangedEvent(const AZ::Vector3& translation) { - AZ::Transform worldTm = GetTransformComponent()->GetWorldTM(); - worldTm.SetTranslation(translation); - GetTransformComponent()->SetWorldTM(worldTm); + m_previousTransform.SetTranslation(m_targetTransform.GetTranslation()); + m_targetTransform.SetTranslation(translation); } void NetworkTransformComponent::OnScaleChangedEvent(float scale) { - AZ::Transform worldTm = GetTransformComponent()->GetWorldTM(); - worldTm.SetUniformScale(scale); - GetTransformComponent()->SetWorldTM(worldTm); + m_previousTransform.SetUniformScale(m_targetTransform.GetUniformScale()); + m_targetTransform.SetUniformScale(scale); + } + + void NetworkTransformComponent::OnResetCountChangedEvent() + { + m_targetTransform.SetRotation(GetRotation()); + m_targetTransform.SetTranslation(GetTranslation()); + m_targetTransform.SetUniformScale(GetScale()); + m_previousTransform = m_targetTransform; + } + + void NetworkTransformComponent::OnPreRender([[maybe_unused]] float deltaTime, float blendFactor) + { + if (!HasController()) + { + AZ::Transform blendTransform; + blendTransform.SetRotation(m_previousTransform.GetRotation().Slerp(m_targetTransform.GetRotation(), blendFactor)); + blendTransform.SetTranslation(m_previousTransform.GetTranslation().Lerp(m_targetTransform.GetTranslation(), blendFactor)); + blendTransform.SetUniformScale(AZ::Lerp(m_previousTransform.GetUniformScale(), m_targetTransform.GetUniformScale(), blendFactor)); + GetTransformComponent()->SetWorldTM(blendTransform); + } } @@ -96,11 +120,8 @@ namespace Multiplayer void NetworkTransformComponentController::OnTransformChangedEvent(const AZ::Transform& worldTm) { - if (IsAuthority()) - { - SetRotation(worldTm.GetRotation()); - SetTranslation(worldTm.GetTranslation()); - SetScale(worldTm.GetUniformScale()); - } + SetRotation(worldTm.GetRotation()); + SetTranslation(worldTm.GetTranslation()); + SetScale(worldTm.GetUniformScale()); } } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp index f684e1f12f..847d1caadf 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorConnection.cpp @@ -140,7 +140,7 @@ namespace Multiplayer AZ::CVarFixedString remoteAddress; uint16_t remotePort; if (console->GetCvarValue("editorsv_serveraddr", remoteAddress) != AZ::GetValueResult::ConsoleVarNotFound && - console->GetCvarValue("editorsv_port", remotePort) != AZ::GetValueResult::ConsoleVarNotFound) + console->GetCvarValue("sv_port", remotePort) != AZ::GetValueResult::ConsoleVarNotFound) { // Connect the Editor to the editor server for Multiplayer simulation AZ::Interface::Get()->InitializeMultiplayer(MultiplayerAgentType::Client); @@ -149,6 +149,8 @@ namespace Multiplayer const IpAddress ipAddress(remoteAddress.c_str(), remotePort, networkInterface->GetType()); networkInterface->Connect(ipAddress); + + AZ::Interface::Get()->SendReadyForEntityUpdates(true); } } } diff --git a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp index 523ffd90de..3ac4e98d42 100644 --- a/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/Editor/MultiplayerEditorSystemComponent.cpp @@ -165,7 +165,7 @@ namespace Multiplayer // BeginGameMode and Prefab Processing have completed at this point IMultiplayerTools* mpTools = AZ::Interface::Get(); - if (editorsv_enabled && mpTools != nullptr && mpTools->DidProcessNetworkPrefabs()) + if (editorsv_enabled && mpTools != nullptr) { const AZStd::vector>& assetData = prefabEditorEntityOwnershipInterface->GetPlayInEditorAssetData(); diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp index 0818f605df..069ac49d96 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.cpp @@ -12,7 +12,6 @@ #include #include - #include #include #include @@ -24,14 +23,23 @@ #include #include #include +#include #include #include +#include #include #include #include + +#include +#include +#include #include + #include +#include + namespace AZ::ConsoleTypeHelpers { template <> @@ -74,6 +82,7 @@ namespace Multiplayer AZ_CVAR(ProtocolType, sv_protocol, ProtocolType::Udp, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "This flag controls whether we use TCP or UDP for game networking"); AZ_CVAR(bool, sv_isDedicated, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether the host command creates an independent or client hosted server"); AZ_CVAR(AZ::TimeMs, cl_defaultNetworkEntityActivationTimeSliceMs, AZ::TimeMs{ 0 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Max Ms to use to activate entities coming from the network, 0 means instantiate everything"); + AZ_CVAR(AZ::TimeMs, sv_serverSendRateMs, AZ::TimeMs{ 50 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Minimum number of milliseconds between each network update"); AZ_CVAR(AZ::CVarFixedString, sv_defaultPlayerSpawnAsset, "prefabs/player.network.spawnable", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The default spawnable to use when a new player connects"); void MultiplayerSystemComponent::Reflect(AZ::ReflectContext* context) @@ -156,10 +165,26 @@ namespace Multiplayer AZ::TickBus::Handler::BusDisconnect(); } - void MultiplayerSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) + void MultiplayerSystemComponent::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time) { - AZ::TimeMs deltaTimeMs = aznumeric_cast(static_cast(deltaTime * 1000.0f)); - AZ::TimeMs hostTimeMs = AZ::GetElapsedTimeMs(); + const AZ::TimeMs deltaTimeMs = aznumeric_cast(static_cast(deltaTime * 1000.0f)); + const AZ::TimeMs hostTimeMs = AZ::GetElapsedTimeMs(); + const AZ::TimeMs serverRateMs = static_cast(sv_serverSendRateMs); + const float serverRateSeconds = static_cast(serverRateMs) / 1000.0f; + + TickVisibleNetworkEntities(deltaTime, serverRateSeconds); + + if (GetAgentType() == MultiplayerAgentType::ClientServer + || GetAgentType() == MultiplayerAgentType::DedicatedServer) + { + m_serverSendAccumulator += deltaTime; + if (m_serverSendAccumulator < serverRateSeconds) + { + return; + } + m_serverSendAccumulator -= serverRateSeconds; + m_networkTime.IncrementHostFrameId(); + } // Handle deferred local rpc messages that were generated during the updates m_networkEntityManager.DispatchLocalDeferredRpcMessages(); @@ -365,13 +390,21 @@ namespace Multiplayer } EntityReplicationManager& replicationManager = reinterpret_cast(connection->GetUserData())->GetReplicationManager(); - - // Ignore a_Request.GetServerGameTimePoint(), clients can't affect the server gametime + + if ((GetAgentType() == MultiplayerAgentType::Client) && (packet.GetHostFrameId() > m_lastReplicatedHostFrameId)) + { + // Update client to latest server time + m_renderBlendFactor = 0.0f; + m_lastReplicatedHostTimeMs = packet.GetHostTimeMs(); + m_lastReplicatedHostFrameId = packet.GetHostFrameId(); + m_networkTime.AlterTime(m_lastReplicatedHostFrameId, m_lastReplicatedHostTimeMs, AzNetworking::InvalidConnectionId); + } + for (AZStd::size_t i = 0; i < packet.GetEntityMessages().size(); ++i) { const NetworkEntityUpdateMessage& updateMessage = packet.GetEntityMessages()[i]; handledAll &= replicationManager.HandleEntityUpdateMessage(connection, packetHeader, updateMessage); - AZ_Assert(handledAll, "GameServerToClientNetworkRequestHandler EntityUpdates Did not handle all updates"); + AZ_Assert(handledAll, "EntityUpdates did not handle all update messages"); } return handledAll; @@ -440,7 +473,7 @@ namespace Multiplayer // Hosts will spawn a new default player prefab for the user that just connected if (GetAgentType() == MultiplayerAgentType::ClientServer - || GetAgentType() == MultiplayerAgentType::DedicatedServer) + || GetAgentType() == MultiplayerAgentType::DedicatedServer) { NetworkEntityHandle controlledEntity = SpawnDefaultPlayerPrefab(); if (controlledEntity.Exists()) @@ -602,6 +635,74 @@ namespace Multiplayer AZLOG_INFO("Total RPCs received bytes: %llu", aznumeric_cast(rpcsRecv.m_totalBytes)); } + void MultiplayerSystemComponent::TickVisibleNetworkEntities(float deltaTime, float serverRateSeconds) + { + const float targetAdjustBlend = AZStd::clamp(deltaTime / serverRateSeconds, 0.0f, 1.0f); + m_renderBlendFactor += targetAdjustBlend; + + // Linear close to the origin, but asymptote at y = 1 + const float adjustedBlendFactor = 1.0f - (std::pow(0.2f, m_renderBlendFactor)); + AZLOG(NET_Blending, "Computed blend factor of %f", adjustedBlendFactor); + + if (Camera::ActiveCameraRequestBus::HasHandlers()) + { + // If there's a camera, update only what's visible + AZ::Transform activeCameraTransform; + Camera::Configuration activeCameraConfiguration; + Camera::ActiveCameraRequestBus::BroadcastResult(activeCameraTransform, &Camera::ActiveCameraRequestBus::Events::GetActiveCameraTransform); + Camera::ActiveCameraRequestBus::BroadcastResult(activeCameraConfiguration, &Camera::ActiveCameraRequestBus::Events::GetActiveCameraConfiguration); + + const AZ::ViewFrustumAttributes frustumAttributes + ( + activeCameraTransform, + activeCameraConfiguration.m_frustumHeight / activeCameraConfiguration.m_frustumWidth, + activeCameraConfiguration.m_fovRadians, + activeCameraConfiguration.m_nearClipDistance, + activeCameraConfiguration.m_farClipDistance + ); + const AZ::Frustum viewFrustum = AZ::Frustum(frustumAttributes); + + // Unfortunately necessary, as NotifyPreRender can update transforms and thus cause a deadlock inside the vis system + AZStd::vector gatheredEntities; + AzFramework::IEntityBoundsUnion* entityBoundsUnion = AZ::Interface::Get(); + AZ::Interface::Get()->GetDefaultVisibilityScene()->Enumerate(viewFrustum, + [&gatheredEntities, entityBoundsUnion](const AzFramework::IVisibilityScene::NodeData& nodeData) + { + gatheredEntities.reserve(gatheredEntities.size() + nodeData.m_entries.size()); + for (AzFramework::VisibilityEntry* visEntry : nodeData.m_entries) + { + if (visEntry->m_typeFlags & AzFramework::VisibilityEntry::TypeFlags::TYPE_Entity) + { + AZ::Entity* entity = static_cast(visEntry->m_userData); + NetBindComponent* netBindComponent = entity->FindComponent(); + if (netBindComponent != nullptr) + { + gatheredEntities.push_back(netBindComponent); + } + } + } + }); + + for (NetBindComponent* netBindComponent : gatheredEntities) + { + netBindComponent->NotifyPreRender(deltaTime, adjustedBlendFactor); + } + } + else + { + // If there's no camera, fall back to updating all net entities + for (auto& iter : *(m_networkEntityManager.GetNetworkEntityTracker())) + { + AZ::Entity* entity = iter.second; + NetBindComponent* netBindComponent = entity->FindComponent(); + if (netBindComponent != nullptr) + { + netBindComponent->NotifyPreRender(deltaTime, adjustedBlendFactor); + } + } + } + } + void MultiplayerSystemComponent::OnConsoleCommandInvoked ( AZStd::string_view command, diff --git a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h index e8e05a9d4c..3b1faafd40 100644 --- a/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h +++ b/Gems/Multiplayer/Code/Source/MultiplayerSystemComponent.h @@ -102,6 +102,7 @@ namespace Multiplayer private: + void TickVisibleNetworkEntities(float deltaTime, float serverRateSeconds); void OnConsoleCommandInvoked(AZStd::string_view command, const AZ::ConsoleCommandContainer& args, AZ::ConsoleFunctorFlags flags, AZ::ConsoleInvokedFrom invokedFrom); void ExecuteConsoleCommandList(AzNetworking::IConnection* connection, const AZStd::fixed_vector& commands); NetworkEntityHandle SpawnDefaultPlayerPrefab(); @@ -124,6 +125,9 @@ namespace Multiplayer AZ::TimeMs m_lastReplicatedHostTimeMs = AZ::TimeMs{ 0 }; HostFrameId m_lastReplicatedHostFrameId = InvalidHostFrameId; + double m_serverSendAccumulator = 0.0; + float m_renderBlendFactor = 0.0f; + #if !defined(AZ_RELEASE_BUILD) MultiplayerEditorConnection m_editorConnectionListener; #endif diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp index bd30c5e37f..5d0284dfb2 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/EntityReplicationManager.cpp @@ -132,7 +132,7 @@ namespace Multiplayer EntityReplicatorList replicatorUpdatedList; MultiplayerPackets::EntityUpdates entityUpdatePacket; entityUpdatePacket.SetHostTimeMs(hostTimeMs); - entityUpdatePacket.SetHostFrameId(InvalidHostFrameId); + entityUpdatePacket.SetHostFrameId(GetNetworkTime()->GetHostFrameId()); // Serialize everything while (!toSendList.empty()) { diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertyPublisher.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertyPublisher.cpp index dfa324f76b..8af636870b 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertyPublisher.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/PropertyPublisher.cpp @@ -27,7 +27,7 @@ namespace Multiplayer , m_sentRecords(net_EntityReplicatorRecordsMax) { AZ_Assert(m_netBindComponent, "NetBindComponent is nullptr"); - m_pendingRecord.SetNetworkRole(remoteNetworkRole); + m_pendingRecord.SetRemoteNetworkRole(remoteNetworkRole); } bool PropertyPublisher::IsDeleting() const @@ -67,7 +67,7 @@ namespace Multiplayer void PropertyPublisher::SetRebasing() { - AZ_Assert(m_pendingRecord.GetNetworkRole() == NetEntityRole::Autonomous, "Expected to be rebasing on a Autonomous entity"); + AZ_Assert(m_pendingRecord.GetRemoteNetworkRole() == NetEntityRole::Autonomous, "Expected to be rebasing on a Autonomous entity"); m_replicatorState = EntityReplicatorState::Rebasing; } @@ -118,7 +118,7 @@ namespace Multiplayer m_sentRecords.clear(); m_netBindComponent->FillTotalReplicationRecord(m_pendingRecord); // Don't send predictable properties back to the Autonomous unless we correct them - if (m_pendingRecord.GetNetworkRole() == NetEntityRole::Autonomous) + if (m_pendingRecord.GetRemoteNetworkRole() == NetEntityRole::Autonomous) { m_pendingRecord.Subtract(m_netBindComponent->GetPredictableRecord()); } @@ -137,7 +137,7 @@ namespace Multiplayer // We need to clear out old records, and build up a list of everything that has changed since the last acked packet m_sentRecords.push_front(m_pendingRecord); auto iter = m_sentRecords.begin(); - ++iter; // consider everything after the record we are going to send + ++iter; // Consider everything after the record we are going to send for (; iter != m_sentRecords.end(); ++iter) { // Sequence wasn't acked, so we need to send these bits again @@ -145,7 +145,7 @@ namespace Multiplayer } // Don't send predictable properties back to the Autonomous unless we correct them - if (m_pendingRecord.GetNetworkRole() == NetEntityRole::Autonomous) + if (m_pendingRecord.GetRemoteNetworkRole() == NetEntityRole::Autonomous) { m_pendingRecord.Subtract(m_netBindComponent->GetPredictableRecord()); } diff --git a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/ReplicationRecord.cpp b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/ReplicationRecord.cpp index 7fe0efd323..47360bfba6 100644 --- a/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/ReplicationRecord.cpp +++ b/Gems/Multiplayer/Code/Source/NetworkEntity/EntityReplication/ReplicationRecord.cpp @@ -49,19 +49,19 @@ namespace Multiplayer } ReplicationRecord::ReplicationRecord(NetEntityRole netEntityRole) - : m_netEntityRole(netEntityRole) + : m_remoteNetEntityRole(netEntityRole) { ; } - void ReplicationRecord::SetNetworkRole(NetEntityRole netEntityRole) + void ReplicationRecord::SetRemoteNetworkRole(NetEntityRole remoteNetEntityRole) { - m_netEntityRole = netEntityRole; + m_remoteNetEntityRole = remoteNetEntityRole; } - NetEntityRole ReplicationRecord::GetNetworkRole() const + NetEntityRole ReplicationRecord::GetRemoteNetworkRole() const { - return m_netEntityRole; + return m_remoteNetEntityRole; } bool ReplicationRecord::AreAllBitsConsumed() const @@ -196,26 +196,26 @@ namespace Multiplayer bool ReplicationRecord::ContainsAuthorityToClientBits() const { - return (m_netEntityRole != NetEntityRole::Authority) - || (m_netEntityRole == NetEntityRole::InvalidRole); + return (m_remoteNetEntityRole != NetEntityRole::Authority) + || (m_remoteNetEntityRole == NetEntityRole::InvalidRole); } bool ReplicationRecord::ContainsAuthorityToServerBits() const { - return (m_netEntityRole == NetEntityRole::Server) - || (m_netEntityRole == NetEntityRole::InvalidRole); + return (m_remoteNetEntityRole == NetEntityRole::Server) + || (m_remoteNetEntityRole == NetEntityRole::InvalidRole); } bool ReplicationRecord::ContainsAuthorityToAutonomousBits() const { - return (m_netEntityRole == NetEntityRole::Autonomous || m_netEntityRole == NetEntityRole::Server) - || (m_netEntityRole == NetEntityRole::InvalidRole); + return (m_remoteNetEntityRole == NetEntityRole::Autonomous || m_remoteNetEntityRole == NetEntityRole::Server) + || (m_remoteNetEntityRole == NetEntityRole::InvalidRole); } bool ReplicationRecord::ContainsAutonomousToAuthorityBits() const { - return (m_netEntityRole == NetEntityRole::Authority) - || (m_netEntityRole == NetEntityRole::InvalidRole); + return (m_remoteNetEntityRole == NetEntityRole::Authority) + || (m_remoteNetEntityRole == NetEntityRole::InvalidRole); } uint32_t ReplicationRecord::GetRemainingAuthorityToClientBits() const diff --git a/Gems/PhysX/Code/NumericalMethods/Source/Optimization/Constants.h b/Gems/PhysX/Code/NumericalMethods/Source/Optimization/Constants.h index d480fb9cd0..feb626aa50 100644 --- a/Gems/PhysX/Code/NumericalMethods/Source/Optimization/Constants.h +++ b/Gems/PhysX/Code/NumericalMethods/Source/Optimization/Constants.h @@ -27,6 +27,6 @@ namespace NumericalMethods::Optimization const double epsilon = 1e-7; // values recommended in Nocedal and Wright for constants in the Wolfe conditions for satisfactory solution improvement - const double c1 = 1e-4; - const double c2 = 0.9; + const double WolfeConditionsC1 = 1e-4; + const double WolfeConditionsC2 = 0.9; } // namespace NumericalMethods::Optimization diff --git a/Gems/PhysX/Code/NumericalMethods/Source/Optimization/LineSearch.cpp b/Gems/PhysX/Code/NumericalMethods/Source/Optimization/LineSearch.cpp index abc2154228..06152446d6 100644 --- a/Gems/PhysX/Code/NumericalMethods/Source/Optimization/LineSearch.cpp +++ b/Gems/PhysX/Code/NumericalMethods/Source/Optimization/LineSearch.cpp @@ -72,7 +72,7 @@ namespace NumericalMethods::Optimization for (AZ::u32 iteration = 0; iteration < lineSearchIterations; iteration++) { - ScalarVariable alphaNew; + ScalarVariable alphaNew = 0.0; if (iteration > 0) { // first try selecting a new alpha value based on cubic interpolation through the most recent points @@ -162,16 +162,16 @@ namespace NumericalMethods::Optimization { // if the value of f corresponding to alpha1 isn't sufficiently small compared to f at x0, // then the interval [alpha0 ... alpha1] must bracket a suitable point. - if ((f_alpha1 > f_x0 + c1 * alpha1 * df_x0) || (iteration > 0 && f_alpha1 > f_alpha0)) + if ((f_alpha1 > f_x0 + WolfeConditionsC1 * alpha1 * df_x0) || (iteration > 0 && f_alpha1 > f_alpha0)) { return SelectStepSizeFromInterval(alpha0, alpha1, f_alpha0, f_alpha1, df_alpha0, - f, x0, searchDirection, f_x0, df_x0, c1, c2); + f, x0, searchDirection, f_x0, df_x0, WolfeConditionsC1, WolfeConditionsC2); } // otherwise, if the derivative corresponding to alpha1 is large enough, alpha1 already // satisfies the Wolfe conditions and so return alpha1. double df_alpha1 = DirectionalDerivative(f, x0 + alpha1 * searchDirection, searchDirection); - if (fabs(df_alpha1) <= -c2 * df_x0) + if (fabs(df_alpha1) <= -WolfeConditionsC2 * df_x0) { LineSearchResult result; result.m_outcome = LineSearchOutcome::Success; @@ -184,7 +184,7 @@ namespace NumericalMethods::Optimization if (df_alpha1 >= 0.0) { return SelectStepSizeFromInterval(alpha1, alpha0, f_alpha1, f_alpha0, df_alpha1, - f, x0, searchDirection, f_x0, df_x0, c1, c2); + f, x0, searchDirection, f_x0, df_x0, WolfeConditionsC1, WolfeConditionsC2); } // haven't found an interval which is guaranteed to bracket a suitable point, diff --git a/Gems/PhysX/Code/NumericalMethods/Tests/OptimizationTest.cpp b/Gems/PhysX/Code/NumericalMethods/Tests/OptimizationTest.cpp index b02f13d8a4..92d7e0dcba 100644 --- a/Gems/PhysX/Code/NumericalMethods/Tests/OptimizationTest.cpp +++ b/Gems/PhysX/Code/NumericalMethods/Tests/OptimizationTest.cpp @@ -158,12 +158,12 @@ namespace NumericalMethods::Optimization double f_x0 = f_alpha0; double df_x0 = df_alpha0; LineSearchResult lineSearchResult = SelectStepSizeFromInterval(alpha0, alpha1, f_alpha0, f_alpha1, df_alpha0, - testFunctionRosenbrock, x0, searchDirection, f_x0, df_x0, c1, c2); + testFunctionRosenbrock, x0, searchDirection, f_x0, df_x0, WolfeConditionsC1, WolfeConditionsC2); EXPECT_TRUE(lineSearchResult.m_outcome == LineSearchOutcome::Success); // check that the Wolfe conditions are satisfied by the returned step size - EXPECT_TRUE(lineSearchResult.m_functionValue < f_x0 + c1 * df_x0 * lineSearchResult.m_stepSize); - EXPECT_TRUE(fabs(lineSearchResult.m_derivativeValue) <= -c2 * df_x0); + EXPECT_TRUE(lineSearchResult.m_functionValue < f_x0 + WolfeConditionsC1 * df_x0 * lineSearchResult.m_stepSize); + EXPECT_TRUE(fabs(lineSearchResult.m_derivativeValue) <= -WolfeConditionsC2 * df_x0); } TEST(OptimizationTest, LineSearch_VariousSearchDirections_SatisfiesWolfeCondition) @@ -180,8 +180,8 @@ namespace NumericalMethods::Optimization EXPECT_TRUE(lineSearchResult.m_outcome == LineSearchOutcome::Success); // check that the Wolfe conditions are satisfied by the returned step size - EXPECT_TRUE(lineSearchResult.m_functionValue < f_x0 + c1 * df_x0 * lineSearchResult.m_stepSize); - EXPECT_TRUE(fabs(lineSearchResult.m_derivativeValue) <= -c2 * df_x0); + EXPECT_TRUE(lineSearchResult.m_functionValue < f_x0 + WolfeConditionsC1 * df_x0 * lineSearchResult.m_stepSize); + EXPECT_TRUE(fabs(lineSearchResult.m_derivativeValue) <= -WolfeConditionsC2 * df_x0); } } diff --git a/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.h b/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.h index ab9bd58148..0971226644 100644 --- a/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.h +++ b/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.h @@ -112,9 +112,9 @@ namespace PhysX::Benchmarks for (double percentile : percentiles) { //ensure the percentile is between 0.0 and 1.0 - const double epsilon = 0.001; - AZ::ClampIfCloseMag(percentile, 0.0, epsilon); - AZ::ClampIfCloseMag(percentile, 1.0, epsilon); + const double testEpsilon = 0.001; + AZ::ClampIfCloseMag(percentile, 0.0, testEpsilon); + AZ::ClampIfCloseMag(percentile, 1.0, testEpsilon); size_t idx = aznumeric_cast(std::round(percentile * (values.size() - 1))); std::nth_element(values.begin(), values.begin() + idx, values.end()); diff --git a/Gems/PhysXDebug/Code/Source/SystemComponent.cpp b/Gems/PhysXDebug/Code/Source/SystemComponent.cpp index 08bf71753d..34315eb11e 100644 --- a/Gems/PhysXDebug/Code/Source/SystemComponent.cpp +++ b/Gems/PhysXDebug/Code/Source/SystemComponent.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -183,9 +184,7 @@ namespace PhysXDebug void SystemComponent::OnCrySystemInitialized([[maybe_unused]] ISystem& system, const SSystemInitParams&) { InitPhysXColorMappings(); - RegisterCommands(); ConfigurePhysXVisualizationParameters(); - } void SystemComponent::Reflect(AZ::ReflectContext* context) @@ -537,12 +536,13 @@ namespace PhysXDebug } } - static void CmdEnableWireFrame([[maybe_unused]] IConsoleCmdArgs* args) + static void physx_CullingBox([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { PhysXDebug::PhysXDebugRequestBus::Broadcast(&PhysXDebug::PhysXDebugRequestBus::Events::ToggleCullingWireFrame); } + AZ_CONSOLEFREEFUNC(physx_CullingBox, AZ::ConsoleFunctorFlags::DontReplicate, "Enables physx wireframe view"); - static void CmdConnectToPvd([[maybe_unused]] IConsoleCmdArgs* args) + static void physx_PvdConnect([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { auto* debug = AZ::Interface::Get(); if (debug) @@ -550,8 +550,9 @@ namespace PhysXDebug debug->ConnectToPvd(); } } + AZ_CONSOLEFREEFUNC(physx_PvdConnect, AZ::ConsoleFunctorFlags::DontReplicate, "Connects to the physx visual debugger"); - static void CmdDisconnectFromPvd([[maybe_unused]] IConsoleCmdArgs* args) + static void physx_PvdDisconnect([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { auto* debug = AZ::Interface::Get(); if (debug) @@ -559,13 +560,14 @@ namespace PhysXDebug debug->DisconnectFromPvd(); } } + AZ_CONSOLEFREEFUNC(physx_PvdDisconnect, AZ::ConsoleFunctorFlags::DontReplicate, "Disconnects from the physx visual debugger"); - static void CmdSetPhysXDebugCullingBoxSize(IConsoleCmdArgs* args) + static void physx_CullingBoxSize([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { - const int argumentCount = args->GetArgCount(); + const int argumentCount = arguments.size(); if (argumentCount == 2) { - float newCullingBoxSize = (float)strtol(args->GetArg(1), nullptr, 10); + float newCullingBoxSize = (float)strtol(AZ::CVarFixedString(arguments[1]).c_str(), nullptr, 10); PhysXDebug::PhysXDebugRequestBus::Broadcast(&PhysXDebug::PhysXDebugRequestBus::Events::SetCullingBoxSize, newCullingBoxSize); } else @@ -574,16 +576,17 @@ namespace PhysXDebug "Please use physx_SetDebugCullingBoxSize e.g. physx_SetDebugCullingBoxSize 100."); } } + AZ_CONSOLEFREEFUNC(physx_CullingBoxSize, AZ::ConsoleFunctorFlags::DontReplicate, "Sets physx debug culling box size"); - static void CmdTogglePhysXDebugVisualization(IConsoleCmdArgs* args) + static void physx_Debug([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { using namespace CryStringUtils; - const int argumentCount = args->GetArgCount(); + const int argumentCount = arguments.size(); if (argumentCount == 2) { - const auto userPreference = static_cast(strtol(args->GetArg(1), nullptr, 10)); + const auto userPreference = static_cast(strtol(AZ::CVarFixedString(arguments[1]).c_str(), nullptr, 10)); switch (userPreference) { @@ -609,29 +612,7 @@ namespace PhysXDebug AZ_Warning("PhysXDebug", false, "Invalid physx_Debug Arguments. Please use physx_Debug 1 to enable, physx_Debug 0 to disable or physx_Debug 2 to enable all configuration settings."); } } - - void SystemComponent::RegisterCommands() - { - if (m_registered) - { - return; - } - - if (gEnv) - { - IConsole* console = gEnv->pSystem->GetIConsole(); - if (console) - { - console->AddCommand("physx_Debug", CmdTogglePhysXDebugVisualization); - console->AddCommand("physx_CullingBox", CmdEnableWireFrame); - console->AddCommand("physx_CullingBoxSize", CmdSetPhysXDebugCullingBoxSize); - console->AddCommand("physx_PvdConnect", CmdConnectToPvd); - console->AddCommand("physx_PvdDisconnect", CmdDisconnectFromPvd); - } - - m_registered = true; - } - } + AZ_CONSOLEFREEFUNC(physx_Debug, AZ::ConsoleFunctorFlags::DontReplicate, "Toggles physx debug visualization"); void SystemComponent::ConfigurePhysXVisualizationParameters() { diff --git a/Gems/PhysXDebug/Code/Source/SystemComponent.h b/Gems/PhysXDebug/Code/Source/SystemComponent.h index 631354c034..f4d033fd4c 100644 --- a/Gems/PhysXDebug/Code/Source/SystemComponent.h +++ b/Gems/PhysXDebug/Code/Source/SystemComponent.h @@ -161,9 +161,6 @@ namespace PhysXDebug /// Initialise the PhysX debug draw colors based on defaults. void InitPhysXColorMappings(); - /// Register debug drawing PhysX commands with Open 3D Engine console during game mode. - void RegisterCommands(); - /// Draw the culling box being used by the viewport. /// @param cullingBoxAabb culling box Aabb to debug draw. void DrawDebugCullingBox(const AZ::Aabb& cullingBoxAabb); diff --git a/Templates/DefaultProject/Template/Code/enabled_gems.cmake b/Templates/DefaultProject/Template/Code/enabled_gems.cmake index dfb7d93233..ec45be0743 100644 --- a/Templates/DefaultProject/Template/Code/enabled_gems.cmake +++ b/Templates/DefaultProject/Template/Code/enabled_gems.cmake @@ -10,7 +10,7 @@ # {END_LICENSE} set(ENABLED_GEMS - Project::${Name} + ${Name} Atom_AtomBridge Camera CameraFramework 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 diff --git a/cmake/Gems.cmake b/cmake/Gems.cmake index d418d5dcd1..169210d991 100644 --- a/cmake/Gems.cmake +++ b/cmake/Gems.cmake @@ -29,9 +29,6 @@ function(ly_create_alias) message(FATAL_ERROR "Provide the namespace of the alias to create using the NAMESPACE keyword") endif() - if (NOT ly_create_alias_TARGETS) - message(FATAL_ERROR "Provide the name of the targets the alias be associated with, using the TARGETS keyword") - endif() if(TARGET ${ly_create_alias_NAMESPACE}::${ly_create_alias_NAME}) message(FATAL_ERROR "Target already exists, cannot create an alias for it: ${ly_create_alias_NAMESPACE}::${ly_create_alias_NAME}\n" @@ -78,8 +75,11 @@ function(ly_create_alias) list(APPEND final_targets ${de_aliased_target_name}) endforeach() - ly_parse_third_party_dependencies("${final_targets}") - ly_add_dependencies(${ly_create_alias_NAME} ${final_targets}) + # add_dependencies must be called with at least one dependent target + if(final_targets) + ly_parse_third_party_dependencies("${final_targets}") + ly_add_dependencies(${ly_create_alias_NAME} ${final_targets}) + endif() # now add the final alias: add_library(${ly_create_alias_NAMESPACE}::${ly_create_alias_NAME} ALIAS ${ly_create_alias_NAME}) diff --git a/cmake/Platform/Common/Install_common.cmake b/cmake/Platform/Common/Install_common.cmake index b18aed6fb4..11260ab018 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 @@ -439,7 +443,6 @@ function(ly_setup_others) install(DIRECTORY ${LY_ROOT_FOLDER}/scripts/bundler - ${LY_ROOT_FOLDER}/scripts/project_manager ${LY_ROOT_FOLDER}/scripts/o3de DESTINATION ./scripts COMPONENT ${LY_DEFAULT_INSTALL_COMPONENT} diff --git a/cmake/Platform/Common/MSVC/Configurations_msvc.cmake b/cmake/Platform/Common/MSVC/Configurations_msvc.cmake index 25b6e63ab9..f53b8aa769 100644 --- a/cmake/Platform/Common/MSVC/Configurations_msvc.cmake +++ b/cmake/Platform/Common/MSVC/Configurations_msvc.cmake @@ -74,8 +74,6 @@ ly_append_configurations_options( /wd4436 # the result of unary operator may be unaligned /wd4450 # declaration hides global declaration /wd4457 # declaration hides function parameter - /wd4459 # declaration hides global declaration - /wd4701 # potentially unintialized local variable # Enabling warnings that are disabled by default from /W4 # https://docs.microsoft.com/en-us/cpp/preprocessor/compiler-warnings-that-are-off-by-default?view=vs-2019 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 diff --git a/cmake/SettingsRegistry.cmake b/cmake/SettingsRegistry.cmake index 4d932601b4..d7ef91d284 100644 --- a/cmake/SettingsRegistry.cmake +++ b/cmake/SettingsRegistry.cmake @@ -25,14 +25,15 @@ set(gems_json_template [[ @target_gem_dependencies_names@ } } -}]] +} +]] ) -set(gem_module_template [[ - "@stripped_gem_target@": - { - "Modules":["$"], - "SourcePaths":["@gem_module_root_relative_to_engine_root@"] - }]] + string(APPEND gem_module_template +[=[ "@stripped_gem_target@":]=] "\n" +[=[ {]=] "\n" +[=[$<$,INTERFACE_LIBRARY>>: "Modules":["$"]]=] "$\n>" +[=[ "SourcePaths":["@gem_module_root_relative_to_engine_root@"]]=] "\n" +[=[ }]=] ) #!ly_get_gem_load_dependencies: Retrieves the list of "load" dependencies for a target @@ -161,10 +162,12 @@ function(ly_delayed_generate_settings_registry) message(FATAL_ERROR "Dependency ${gem_target} from ${target} does not exist") endif() + get_property(has_manually_added_dependencies TARGET ${gem_target} PROPERTY MANUALLY_ADDED_DEPENDENCIES SET) get_target_property(target_type ${gem_target} TYPE) - if (target_type STREQUAL "INTERFACE_LIBRARY") - # don't use interface libraries here, we only want ones which produce actual binaries. - # we have still already recursed into their dependencies - they'll show up later. + if (target_type STREQUAL "INTERFACE_LIBRARY" AND has_manually_added_dependencies) + # don't use interface libraries here, we only want ones which produce actual binaries unless the target + # is empty. We have still already recursed into their dependencies - they'll show up later. + # When the target has no dependencies however we want to add the gem root path to the generated setreg continue() endif() diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt index d3c9640665..6df0f4b77f 100644 --- a/scripts/CMakeLists.txt +++ b/scripts/CMakeLists.txt @@ -12,5 +12,4 @@ add_subdirectory(detect_file_changes) add_subdirectory(commit_validation) add_subdirectory(o3de) -add_subdirectory(project_manager) add_subdirectory(ctest) diff --git a/scripts/build/Platform/Mac/pipeline.json b/scripts/build/Platform/Mac/pipeline.json index 58f62b421d..81c57002b6 100644 --- a/scripts/build/Platform/Mac/pipeline.json +++ b/scripts/build/Platform/Mac/pipeline.json @@ -1,6 +1,6 @@ { "ENV": { - "NODE_LABEL": "mac", + "NODE_LABEL": "mac-catalina-7ad2e45b", "LY_3RDPARTY_PATH": "/Users/lybuilder/3rdParty", "TIMEOUT": 30, "WORKSPACE": "/Users/lybuilder/workspace", diff --git a/scripts/build/Platform/iOS/pipeline.json b/scripts/build/Platform/iOS/pipeline.json index 58f62b421d..81c57002b6 100644 --- a/scripts/build/Platform/iOS/pipeline.json +++ b/scripts/build/Platform/iOS/pipeline.json @@ -1,6 +1,6 @@ { "ENV": { - "NODE_LABEL": "mac", + "NODE_LABEL": "mac-catalina-7ad2e45b", "LY_3RDPARTY_PATH": "/Users/lybuilder/3rdParty", "TIMEOUT": 30, "WORKSPACE": "/Users/lybuilder/workspace", diff --git a/scripts/o3de.py b/scripts/o3de.py index 8d7532878c..85d49ec268 100755 --- a/scripts/o3de.py +++ b/scripts/o3de.py @@ -32,7 +32,7 @@ def add_args(parser, subparsers) -> None: # add the scripts/o3de directory to the front of the sys.path sys.path.insert(0, str(o3de_package_dir)) from o3de import engine_template, global_project, register, print_registration, get_registration, \ - enable_gem, disable_gem, sha256 + enable_gem, disable_gem, project_properties, sha256 # Remove the temporarily added path sys.path = sys.path[1:] @@ -55,7 +55,10 @@ def add_args(parser, subparsers) -> None: # remove a gem from a project disable_gem.add_args(subparsers) - + + # modify project properties + project_properties.add_args(subparsers) + # sha256 sha256.add_args(subparsers) diff --git a/scripts/o3de/o3de/manifest.py b/scripts/o3de/o3de/manifest.py index 2a7e5bba11..edcd44c525 100644 --- a/scripts/o3de/o3de/manifest.py +++ b/scripts/o3de/o3de/manifest.py @@ -573,9 +573,7 @@ def get_registered(engine_name: str = None, return engine_path elif isinstance(project_name, str): - enging_projects = get_engine_projects() - projects = json_data['projects'].copy() - projects.extend(engine_object['projects']) + projects = get_all_projects() for project_path in projects: project_path = pathlib.Path(project_path).resolve() project_json = project_path / 'project.json' @@ -605,9 +603,7 @@ def get_registered(engine_name: str = None, return gem_path elif isinstance(template_name, str): - engine_templates = get_engine_templates() - templates = json_data['templates'].copy() - templates.extend(engine_templates) + templates = get_all_templates() for template_path in templates: template_path = pathlib.Path(template_path).resolve() template_json = template_path / 'template.json' @@ -622,9 +618,7 @@ def get_registered(engine_name: str = None, return template_path elif isinstance(restricted_name, str): - engine_restricted = get_engine_restricted() - restricted = json_data['restricted'].copy() - restricted.extend(engine_restricted) + restricted = get_all_restricted() for restricted_path in restricted: restricted_path = pathlib.Path(restricted_path).resolve() restricted_json = restricted_path / 'restricted.json' diff --git a/scripts/o3de/o3de/project_properties.py b/scripts/o3de/o3de/project_properties.py new file mode 100644 index 0000000000..69bd1b9406 --- /dev/null +++ b/scripts/o3de/o3de/project_properties.py @@ -0,0 +1,104 @@ +# +# 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 argparse +import json +import os +import pathlib +import sys +import logging + +from o3de import manifest + +logger = logging.getLogger() +logging.basicConfig() + +def get_project_props(name: str = None, path: pathlib.Path = None) -> dict: + proj_json = manifest.get_project_json_data(project_name=name, project_path=path) + if not proj_json: + param = name if name else path + logger.error(f'Could not retrieve project.json file for {param}') + return None + return proj_json + +def edit_project_props(proj_path, proj_name, new_origin, new_display, + new_summary, new_icon, new_tag, remove_tag) -> int: + proj_json = get_project_props(proj_name, proj_path) + + if not proj_json: + return 1 + + if new_origin: + proj_json['origin'] = new_origin + if new_display: + proj_json['display_name'] = new_display + if new_summary: + proj_json['summary'] = new_summary + if new_icon: + proj_json['icon_path'] = new_icon + if new_tag: + proj_json.setdefault('user_tags', []).append(new_tag) + if remove_tag: + if 'user_tags' in proj_json: + if remove_tag in proj_json['user_tags']: + proj_json['user_tags'].remove(remove_tag) + else: + logger.warn(f'{remove_tag} not found in user_tags for removal.') + else: + logger.warn(f'user_tags property not found for removal of tag {remove_tag}.') + + manifest.save_o3de_manifest(proj_json, pathlib.Path(proj_path) / 'project.json') + return 0 + +def _edit_project_props(args: argparse) -> int: + return edit_project_props(args.project_path, + args.project_name, + args.project_origin, + args.project_display, + args.project_summary, + args.project_icon, + args.project_tag, + args.remove_tag) + +def add_parser_args(parser): + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument('-pp', '--project-path', type=pathlib.Path, required=False, + help='The path to the project.') + group.add_argument('-pn', '--project-name', type=str, required=False, + help='The name of the project.') + group = parser.add_argument_group('properties', 'arguments for modifying individual project properties.') + group.add_argument('-po', '--project-origin', type=str, required=False, + help='Sets description or url for project origin (such as project host, repository, owner...etc).') + group.add_argument('-pd', '--project-display', type=str, required=False, + help='Sets the project display name.') + group.add_argument('-ps', '--project-summary', type=str, required=False, + help='Sets the summary description of the project.') + group.add_argument('-pi', '--project-icon', type=str, required=False, + help='Sets the path to the projects icon resource.') + group.add_argument('-pt', '--project-tag', type=str, required=False, + help='Adds a tag to user_tags property. These tags are intended for documentation and filtering.') + group.add_argument('-rt', '--remove-tag', type=str, required=False, + help='Removes a tag from the user_tags property.') + parser.set_defaults(func=_edit_project_props) + +def add_args(subparsers) -> None: + enable_project_props_subparser = subparsers.add_parser('edit-project-properties') + add_parser_args(enable_project_props_subparser) + +def main(): + the_parser = argparse.ArgumentParser() + add_parser_args(the_parser) + the_args = the_parser.parse_args() + ret = the_args.func(the_args) if hasattr(the_args, 'func') else 1 + sys.exit(ret) + +if __name__ == "__main__": + main() diff --git a/scripts/project_manager/__init__.py b/scripts/project_manager/__init__.py deleted file mode 100755 index 4d5680a30d..0000000000 --- a/scripts/project_manager/__init__.py +++ /dev/null @@ -1,10 +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. -# diff --git a/scripts/project_manager/projects.py b/scripts/project_manager/projects.py deleted file mode 100755 index f9f40aa4bf..0000000000 --- a/scripts/project_manager/projects.py +++ /dev/null @@ -1,804 +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. -# - -# PySide project and gem selector GUI - -import os -import pathlib -import sys -import argparse -import json -import logging -import subprocess -from logging.handlers import RotatingFileHandler -from typing import List -from pyside import add_pyside_environment, is_pyside_ready, uninstall_env - -engine_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..')) -sys.path.append(engine_path) -executable_path = '' - -logger = logging.getLogger() -logger.setLevel(logging.INFO) - -from o3de import disable_gem, enable_gem, cmake, engine_template, manifest, register - -o3de_folder = manifest.get_o3de_folder() -o3de_logs_folder = manifest.get_o3de_logs_folder() -project_manager_log_file_path = o3de_logs_folder / "project_manager.log" -log_file_handler = RotatingFileHandler(filename=project_manager_log_file_path, maxBytes=1024 * 1024, backupCount=1) -formatter = logging.Formatter('%(asctime)s | %(levelname)s : %(message)s') -log_file_handler.setFormatter(formatter) -logger.addHandler(log_file_handler) - -logger.info("Starting Project Manager") - - -def initialize_pyside_from_parser(): - # Parse arguments up top. We need to know the path to our binaries and QT libs in particular to load up - # PySide - parser = argparse.ArgumentParser() - parser.add_argument('--executable-path', required=True, help='Path to Executable to launch with project') - parser.add_argument('--binaries-path', default=None, help='Path to QT Binaries necessary for PySide. If not' - ' provided executable_path folder is assumed') - parser.add_argument('--parent-pid', default=0, help='Process ID of launching process') - - args = parser.parse_args() - - logger.info(f"parent_pid is {args.parent_pid}") - global executable_path - executable_path = args.executable_path - binaries_path = args.binaries_path or os.path.dirname(executable_path) - - # Initialize PySide before imports below. This adds both PySide python modules to the python system interpreter - # path and adds the necessary paths to binaries for the DLLs to be found and load their dependencies - add_pyside_environment(binaries_path) - - -if not is_pyside_ready(): - initialize_pyside_from_parser() - -try: - from PySide2.QtWidgets import QApplication, QDialogButtonBox, QPushButton, QComboBox, QMessageBox, QFileDialog - from PySide2.QtWidgets import QListView, QLabel - from PySide2.QtUiTools import QUiLoader - from PySide2.QtCore import QFile, QObject, Qt, Signal, Slot - from PySide2.QtGui import QIcon, QStandardItemModel, QStandardItem -except ImportError as e: - logger.error(f"Failed to import PySide2 with error {e}") - exit(-1) - -logger.error(f"PySide2 imports successful") - - -class DialogLoggerSignaller(QObject): - send_to_dialog = Signal(str) - - def __init__(self, dialog_logger): - super(DialogLoggerSignaller, self).__init__() - - self.dialog_logger = dialog_logger - - -# Independent class to handle log forwarding. Logger and qt signals both use emit method. -# This class's job is to receive the logger record and then emit the formatted message through -# DialogLoggerSignaller which is what the ProjectDialog handler listens for -class DialogLogger(logging.Handler): - - def __init__(self, log_dialog, log_level=logging.INFO, forward_log_level=logging.WARNING, - message_box_log_level=logging.ERROR): - super(DialogLogger, self).__init__() - - self.log_dialog = log_dialog - self.log_level = log_level - self.forward_log_level = forward_log_level - self.message_box_log_level = message_box_log_level - self.log_records = [] - self.formatter = logging.Formatter('%(levelname)s : %(message)s') - self.setFormatter(self.formatter) - self.signaller = DialogLoggerSignaller(self) - - def emit(self, record): - self.log_records.append(record) - if record.levelno >= self.message_box_log_level: - QMessageBox.warning(None, record.levelname, record.message) - elif record.levelno >= self.forward_log_level: - self.signaller.send_to_dialog.emit(self.format(record)) - - -class ProjectManagerDialog(QObject): - """ - Main project manager dialog is responsible for displaying the project selection list and output pane - """ - - def __init__(self, parent=None): - super(ProjectManagerDialog, self).__init__(parent) - - self.ui_path = (pathlib.Path(__file__).parent / 'ui').resolve() - self.home_folder = manifest.get_home_folder() - - self.log_display = None - self.dialog_logger = DialogLogger(self) - logger.addHandler(self.dialog_logger) - logger.setLevel(logging.INFO) - - self.dialog_logger.signaller.send_to_dialog.connect(self.handle_log_message) - self.mru_file_path = o3de_folder / 'mru.json' - - self.create_from_template_ui_file_path = self.ui_path / 'create_from_template.ui' - self.create_gem_ui_file_path = self.ui_path / 'create_gem.ui' - self.create_project_ui_file_path = self.ui_path / 'create_project.ui' - self.manage_project_gem_targets_ui_file_path = self.ui_path / 'manage_gem_targets.ui' - self.project_manager_icon_file_path = self.ui_path / 'project_manager.ico' - self.project_manager_ui_file_path = self.ui_path / 'project_manager.ui' - - self.project_manager_ui_file = QFile(self.project_manager_ui_file_path.as_posix()) - self.project_manager_ui_file.open(QFile.ReadOnly) - - loader = QUiLoader() - self.dialog = loader.load(self.project_manager_ui_file) - self.dialog.setWindowIcon(QIcon(self.project_manager_icon_file_path.as_posix())) - self.dialog.setFixedSize(self.dialog.size()) - - self.project_list_box = self.dialog.findChild(QComboBox, 'projectListBox') - self.refresh_project_list() - mru = self.get_mru_list() - if len(mru): - last_mru = pathlib.Path(mru[0]).resolve() - for this_slot in range(self.project_list_box.count()): - item_text = self.project_list_box.itemText(this_slot) - if last_mru.as_posix() in item_text: - self.project_list_box.setCurrentIndex(this_slot) - break - - self.create_project_button = self.dialog.findChild(QPushButton, 'createProjectButton') - self.create_project_button.clicked.connect(self.create_project_handler) - self.create_gem_button = self.dialog.findChild(QPushButton, 'createGemButton') - self.create_gem_button.clicked.connect(self.create_gem_handler) - self.create_template_button = self.dialog.findChild(QPushButton, 'createTemplateButton') - self.create_template_button.clicked.connect(self.create_template_handler) - self.create_from_template_button = self.dialog.findChild(QPushButton, 'createFromTemplateButton') - self.create_from_template_button.clicked.connect(self.create_from_template_handler) - - self.add_project_button = self.dialog.findChild(QPushButton, 'addProjectButton') - self.add_project_button.clicked.connect(self.add_project_handler) - self.add_gem_button = self.dialog.findChild(QPushButton, 'addGemButton') - self.add_gem_button.clicked.connect(self.add_gem_handler) - self.add_template_button = self.dialog.findChild(QPushButton, 'addTemplateButton') - self.add_template_button.clicked.connect(self.add_template_handler) - self.add_restricted_button = self.dialog.findChild(QPushButton, 'addRestrictedButton') - self.add_restricted_button.clicked.connect(self.add_restricted_handler) - - self.remove_project_button = self.dialog.findChild(QPushButton, 'removeProjectButton') - self.remove_project_button.clicked.connect(self.remove_project_handler) - self.remove_gem_button = self.dialog.findChild(QPushButton, 'removeGemButton') - self.remove_gem_button.clicked.connect(self.remove_gem_handler) - self.remove_template_button = self.dialog.findChild(QPushButton, 'removeTemplateButton') - self.remove_template_button.clicked.connect(self.remove_template_handler) - self.remove_restricted_button = self.dialog.findChild(QPushButton, 'removeRestrictedButton') - self.remove_restricted_button.clicked.connect(self.remove_restricted_handler) - - self.manage_project_gem_targets_button = self.dialog.findChild(QPushButton, 'manageRuntimeGemTargetsButton') - self.manage_project_gem_targets_button.clicked.connect(self.manage_project_gem_targets_handler) - - self.log_display = self.dialog.findChild(QLabel, 'logDisplay') - - self.ok_cancel_button = self.dialog.findChild(QDialogButtonBox, 'okCancel') - self.ok_cancel_button.accepted.connect(self.accepted_handler) - - self.dialog.show() - - def refresh_project_list(self) -> None: - projects = manifest.get_all_projects() - self.project_list_box.clear() - for this_slot in range(len(projects)): - display_name = f'{os.path.basename(os.path.normpath(projects[this_slot]))} ({projects[this_slot]})' - self.project_list_box.addItem(display_name) - self.project_list_box.setItemData(self.project_list_box.count() - 1, projects[this_slot], - Qt.ToolTipRole) - - def accepted_handler(self) -> None: - """ - Override for handling "Ok" on main project dialog to first check whether the user has selected a project and - prompt them to if not. If a project is selected will attempt to open it. - :return: None - """ - if not self.project_list_box.currentText(): - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText("Please select a project") - msg_box.exec() - return - self.launch_with_project_path(self.get_selected_project_path()) - - def get_launch_project(self) -> str: - return os.path.normpath(self.get_selected_project_path()) - - def get_executable_launch_params(self) -> list: - """ - Retrieve the necessary launch parameters to make the subprocess launch call with - this is the path - to the executable such as the Editor and the path to the selected project - :return: list of params - """ - launch_params = [executable_path, - f'-regset="/Amazon/AzCore/Bootstrap/project_path={self.get_launch_project()}"'] - return launch_params - - def launch_with_project_path(self, project_path: str) -> None: - """ - Launch the desired application given the selected project - :param project_path: Path to currently selected project - :return: None - """ - logger.info(f'Attempting to open {project_path}') - self.update_mru_list(project_path) - launch_params = self.get_executable_launch_params() - logger.info(f'Launching with params {launch_params}') - subprocess.run(launch_params, env=uninstall_env()) - - def get_selected_project_path(self) -> str: - if self.project_list_box.currentIndex() == -1: - logger.warning("No project selected") - return "" - return self.project_list_box.itemData(self.project_list_box.currentIndex(), Qt.ToolTipRole) - - def get_selected_project_name(self) -> str: - project_data = manifest.get_project_json_data(project_path=self.get_selected_project_path()) - return project_data['project_name'] - - def create_project_handler(self): - """ - Opens the Create Project pane. Retrieves a list of available templates for display - :return: None - """ - loader = QUiLoader() - self.create_project_file = QFile(self.create_project_ui_file_path.as_posix()) - - if not self.create_project_file: - logger.error(f'Failed to create project UI file at {self.create_project_file}') - return - - self.create_project_dialog = loader.load(self.create_project_file) - - if not self.create_project_dialog: - logger.error(f'Failed to load create project dialog file at {self.create_project_file}') - return - - self.create_project_ok_button = self.create_project_dialog.findChild(QDialogButtonBox, 'okCancel') - self.create_project_ok_button.accepted.connect(self.create_project_accepted_handler) - - self.create_project_template_list = self.create_project_dialog.findChild(QListView, 'projectTemplates') - self.refresh_create_project_template_list() - - self.create_project_dialog.exec() - - def create_project_accepted_handler(self) -> None: - """ - Searches the available gems list for selected gems and attempts to add each one to the current project. - Updates UI after completion. - :return: None - """ - - selected_item = self.create_project_template_list.selectionModel().currentIndex() - project_template_path = self.create_project_template_list.model().data(selected_item) - if not project_template_path: - return - - folder_dialog = QFileDialog(self.dialog, "Select a Folder and Enter a New Project Name", - manifest.get_o3de_projects_folder().as_posix()) - folder_dialog.setFileMode(QFileDialog.AnyFile) - folder_dialog.setOptions(QFileDialog.ShowDirsOnly) - project_count = 0 - project_name = "MyNewProject" - while os.path.exists(os.path.join(engine_path, project_name)): - project_name = f"MyNewProject{project_count}" - project_count += 1 - folder_dialog.selectFile(project_name) - project_folder = None - if folder_dialog.exec(): - project_folder = folder_dialog.selectedFiles() - if project_folder: - if engine_template.create_project(project_path=project_folder[0], - template_path=project_template_path) == 0: - # Success - register.register(project_path=project_folder[0]) - self.refresh_project_list() - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Project {project_folder[0]} created.") - msg_box.exec() - return - - def create_gem_handler(self): - """ - Opens the Create Gem pane. Retrieves a list of available templates for display - :return: None - """ - loader = QUiLoader() - self.create_gem_file = QFile(self.create_gem_ui_file_path.as_posix()) - - if not self.create_gem_file: - logger.error(f'Failed to create gem UI file at {self.create_gem_file}') - return - - self.create_gem_dialog = loader.load(self.create_gem_file) - - if not self.create_gem_dialog: - logger.error(f'Failed to load create gem dialog file at {self.create_gem_file}') - return - - self.create_gem_ok_button = self.create_gem_dialog.findChild(QDialogButtonBox, 'okCancel') - self.create_gem_ok_button.accepted.connect(self.create_gem_accepted_handler) - - self.create_gem_template_list = self.create_gem_dialog.findChild(QListView, 'gemTemplates') - self.refresh_create_gem_template_list() - - self.create_gem_dialog.exec() - - def create_gem_accepted_handler(self) -> None: - """ - Searches the available gems list for selected gems and attempts to add each one to the current gem. - Updates UI after completion. - :return: None - """ - selected_item = self.create_gem_template_list.selectionModel().currentIndex() - gem_template_path = self.create_gem_template_list.model().data(selected_item) - if not gem_template_path: - return - - folder_dialog = QFileDialog(self.dialog, "Select a Folder and Enter a New Gem Name", - manifest.get_o3de_gems_folder().as_posix()) - folder_dialog.setFileMode(QFileDialog.AnyFile) - folder_dialog.setOptions(QFileDialog.ShowDirsOnly) - gem_count = 0 - gem_name = "MyNewGem" - while os.path.exists(os.path.join(engine_path, gem_name)): - gem_name = f"MyNewGem{gem_count}" - gem_count += 1 - folder_dialog.selectFile(gem_name) - gem_folder = None - if folder_dialog.exec(): - gem_folder = folder_dialog.selectedFiles() - if gem_folder: - if engine_template.create_gem(gem_path=gem_folder[0], - template_path=gem_template_path) == 0: - # Success - register.register(gem_path=gem_folder[0]) - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Gem {gem_folder[0]} created.") - msg_box.exec() - return - - def create_template_handler(self): - """ - Opens a foldr select dialog and lets the user select the source folder they want to make a template - out of, then opens a second folder select dialog to get where they want to put the template and it name - :return: None - """ - - source_folder = QFileDialog.getExistingDirectory(self.dialog, - "Select a Folder to make a template out of.", - manifest.get_o3de_folder().as_posix()) - if not source_folder: - return - - destination_template_folder_dialog = QFileDialog(self.dialog, - "Select where the template is to be created and named.", - manifest.get_o3de_templates_folder().as_posix()) - destination_template_folder_dialog.setFileMode(QFileDialog.AnyFile) - destination_template_folder_dialog.setOptions(QFileDialog.ShowDirsOnly) - destination_folder = None - if destination_template_folder_dialog.exec(): - destination_folder = destination_template_folder_dialog.selectedFiles() - if not destination_folder: - return - - if engine_template.create_template(source_path=source_folder, - template_path=destination_folder[0]) == 0: - # Success - register.register(template_path=destination_folder[0]) - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Template {destination_folder[0]} created.") - msg_box.exec() - return - - def create_from_template_handler(self): - """ - Opens the Create from_template pane. Retrieves a list of available from_templates for display - :return: None - """ - loader = QUiLoader() - self.create_from_template_file = QFile(self.create_from_template_ui_file_path.as_posix()) - - if not self.create_from_template_file: - logger.error(f'Failed to create from_template UI file at {self.create_from_template_file}') - return - - self.create_from_template_dialog = loader.load(self.create_from_template_file) - - if not self.create_from_template_dialog: - logger.error(f'Failed to load create from_template dialog file at {self.create_from_template_file}') - return - - self.create_from_template_ok_button = self.create_from_template_dialog.findChild(QDialogButtonBox, 'okCancel') - self.create_from_template_ok_button.accepted.connect(self.create_from_template_accepted_handler) - - self.create_from_template_list = self.create_from_template_dialog.findChild(QListView, 'genericTemplates') - self.refresh_create_from_template_list() - - self.create_from_template_dialog.exec() - - def create_from_template_accepted_handler(self) -> None: - """ - Searches the available gems list for selected gems and attempts to add each one to the current gem. - Updates UI after completion. - :return: None - """ - create_gem_item = self.get_selected_gem_template() - if not create_gem_item: - return - - folder_dialog = QFileDialog(self.dialog, "Select a Folder and Enter a New Gem Name", - manifest.get_o3de_gems_folder().as_posix()) - folder_dialog.setFileMode(QFileDialog.AnyFile) - folder_dialog.setOptions(QFileDialog.ShowDirsOnly) - gem_count = 0 - gem_name = "MyNewGem" - while os.path.exists(os.path.join(engine_path, gem_name)): - gem_name = f"MyNewGem{gem_count}" - gem_count += 1 - folder_dialog.selectFile(gem_name) - gem_folder = None - if folder_dialog.exec(): - gem_folder = folder_dialog.selectedFiles() - if gem_folder: - if engine_template.create_gem(gem_folder[0], create_gem_item[1]) == 0: - # Success - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"gem {os.path.basename(os.path.normpath(gem_folder[0]))} created." - " Build your\nnew gem before hitting OK to launch.") - msg_box.exec() - return - - def add_project_handler(self): - """ - Open a file search dialog looking for a folder which contains a valid project. If valid - will update the mru list with the new entry, if invalid will warn the user. - :return: None - """ - project_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Project Folder", - manifest.get_o3de_projects_folder().as_posix()) - if project_folder: - if register.register(project_path=project_folder) == 0: - # Success - self.refresh_project_list() - - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Added Project {project_folder}.") - msg_box.exec() - return - - def add_gem_handler(self): - """ - Open a file search dialog looking for a folder which contains a gem. If valid - will update the mru list with the new entry, if invalid will warn the user. - :return: None - """ - gem_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Gem Folder", - manifest.get_o3de_gems_folder().as_posix()) - if gem_folder: - if register.register(gem_path=gem_folder) == 0: - # Success - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Added Gem {gem_folder}.") - msg_box.exec() - return - - def add_template_handler(self): - """ - Open a file search dialog looking for a folder which contains a valid template. If valid - will update the mru list with the new entry, if invalid will warn the user. - :return: None - """ - template_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Template Folder", - manifest.get_o3de_templates_folder().as_posix()) - if template_folder: - if register.register(template_path=template_folder) == 0: - # Success - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Added Template {template_folder}.") - msg_box.exec() - return - - def add_restricted_handler(self): - """ - Open a file search dialog looking for a folder which contains a valid template. If valid - will update the mru list with the new entry, if invalid will warn the user. - :return: None - """ - restricted_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Restricted Folder", - manifest.get_o3de_restricted_folder().as_posix()) - if restricted_folder: - if register.register(restricted_path=restricted_folder) == 0: - # Success - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Added Restricted {restricted_folder}.") - msg_box.exec() - return - - def remove_project_handler(self): - """ - Open a file search dialog looking for a folder which contains a valid project. If valid - will update the mru list with the new entry, if invalid will warn the user. - :return: None - """ - project_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Project Folder", - manifest.get_o3de_projects_folder().as_posix()) - if project_folder: - if register.register(project_path=project_folder, remove=True) == 0: - # Success - self.refresh_project_list() - - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Removed Project {project_folder}.") - msg_box.exec() - return - - def remove_gem_handler(self): - """ - Open a file search dialog looking for a folder which contains a gem. If valid - will update the mru list with the new entry, if invalid will warn the user. - :return: None - """ - gem_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Gem Folder", - manifest.get_o3de_gems_folder().as_posix()) - if gem_folder: - if register.register(gem_path=gem_folder, remove=True) == 0: - # Success - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Removed Gem {gem_folder}.") - msg_box.exec() - return - - def remove_template_handler(self): - """ - Open a file search dialog looking for a folder which contains a valid template. If valid - will update the mru list with the new entry, if invalid will warn the user. - :return: None - """ - template_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Template Folder", - manifest.get_o3de_templates_folder().as_posix()) - if template_folder: - if register.register(template_path=template_folder, remove=True) == 0: - # Success - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Removed Template {template_folder}.") - msg_box.exec() - return - - def remove_restricted_handler(self): - """ - Open a file search dialog looking for a folder which contains a valid template. If valid - will update the mru list with the new entry, if invalid will warn the user. - :return: None - """ - restricted_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Restricted Folder", - manifest.get_o3de_restricted_folder().as_posix()) - if restricted_folder: - if register.register(restricted_path=restricted_folder, remove=True) == 0: - # Success - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Removed Restricted {restricted_folder}.") - msg_box.exec() - return - - def manage_project_gem_targets_handler(self): - """ - Opens the Gem management pane. Waits for the load thread to complete if still running and displays all - active gems for the current project as well as all available gems which aren't currently active. - :return: None - """ - - if not self.get_selected_project_path(): - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText("Please select a project") - msg_box.exec() - return - - loader = QUiLoader() - self.manage_project_gem_targets_file = QFile(self.manage_project_gem_targets_ui_file_path.as_posix()) - - if not self.manage_project_gem_targets_file: - logger.error(f'Failed to load manage gem targets UI file at {self.manage_project_gem_targets_ui_file_path}') - return - - self.manage_project_gem_targets_dialog = loader.load(self.manage_project_gem_targets_file) - - if not self.manage_project_gem_targets_dialog: - logger.error(f'Failed to load gems dialog file at {self.manage_project_gem_targets_ui_file_path.as_posix()}') - return - - self.manage_project_gem_targets_dialog.setWindowTitle(f"Manage Gems for Project:" - f" {self.get_selected_project_name()}") - - self.add_gem_button = self.manage_project_gem_targets_dialog.findChild(QPushButton, 'addGemTargetsButton') - self.add_gem_button.clicked.connect(self.add_project_gem_targets_handler) - - self.available_gem_targets_list = self.manage_project_gem_targets_dialog.findChild(QListView, - 'availableGemTargetsList') - self.refresh_project_gem_targets_available_list() - - self.remove_project_gem_targets_button = self.manage_project_gem_targets_dialog.findChild(QPushButton, - 'removeGemTargetsButton') - self.remove_project_gem_targets_button.clicked.connect(self.remove_project_gem_targets_handler) - - self.enabled_gem_targets_list = self.manage_project_gem_targets_dialog.findChild(QListView, - 'enabledGemTargetsList') - self.refresh_project_gem_targets_enabled_list() - - self.manage_project_gem_targets_dialog.exec() - - - def manage_project_gem_targets_get_selected_available_gems(self) -> list: - selected_items = self.available_gem_targets_list.selectionModel().selectedRows() - return [(self.available_gem_targets_list.model().data(item)) for item in selected_items] - - def manage_project_gem_targets_get_selected_enabled_gems(self) -> list: - selected_items = self.enabled_gem_targets_list.selectionModel().selectedRows() - return [(self.enabled_gem_targets_list.model().data(item)) for item in selected_items] - - def add_project_gem_targets_handler(self) -> None: - gem_paths = manifest.get_all_gems() - for gem_target in self.manage_project_gem_targets_get_selected_available_gems(): - for gem_path in gem_paths: - enable_gem.enable_gem_in_project(gem_path=gem_path, - project_path=self.get_selected_project_path()) - self.refresh_project_gem_targets_available_list() - self.refresh_project_gem_targets_enabled_list() - return - self.refresh_project_gem_targets_available_list() - self.refresh_project_gem_targets_enabled_list() - - def remove_project_gem_targets_handler(self): - gem_paths = manifest.get_all_gems() - for gem_target in self.manage_project_gem_targets_get_selected_enabled_gems(): - for gem_path in gem_paths: - disable_gem.disable_gem_in_project(gem_path=gem_path, - project_path=self.get_selected_project_path()) - self.refresh_project_gem_targets_available_list() - self.refresh_project_gem_targets_enabled_list() - return - self.refresh_project_gem_targets_available_list() - self.refresh_project_gem_targets_enabled_list() - - def refresh_project_gem_targets_enabled_list(self) -> None: - enabled_project_gem_targets_model = QStandardItemModel() - enabled_project_gems = cmake.get_project_gems(project_path=self.get_selected_project_path()) - for gem_target in sorted(enabled_project_gems): - model_item = QStandardItem(gem_target) - enabled_project_gem_targets_model.appendRow(model_item) - self.enabled_gem_targets_list.setModel(enabled_project_gem_targets_model) - - - def refresh_project_gem_targets_available_list(self) -> None: - available_project_gem_targets_model = QStandardItemModel() - enabled_project_gem_targets = cmake.get_project_gems(project_path=self.get_selected_project_path()) - all_gem_targets = manifest.get_all_gems() - for gem_target in sorted(all_gem_targets): - if gem_target not in enabled_project_gem_targets: - model_item = QStandardItem(gem_target) - available_project_gem_targets_model.appendRow(model_item) - self.available_gem_targets_list.setModel(available_project_gem_targets_model) - - - def refresh_create_project_template_list(self) -> None: - self.create_project_template_model = QStandardItemModel() - for project_template_path in manifest.get_project_templates(): - model_item = QStandardItem(project_template_path) - self.create_project_template_model.appendRow(model_item) - self.create_project_template_list.setModel(self.create_project_template_model) - - def refresh_create_gem_template_list(self) -> None: - self.create_gem_template_model = QStandardItemModel() - for gem_template_path in manifest.get_gem_templates(): - model_item = QStandardItem(gem_template_path) - self.create_gem_template_model.appendRow(model_item) - self.create_gem_template_list.setModel(self.create_gem_template_model) - - def refresh_create_from_template_list(self) -> None: - self.create_from_template_model = QStandardItemModel() - for generic_template_path in manifest.get_generic_templates(): - model_item = QStandardItem(generic_template_path) - self.create_from_template_model.appendRow(model_item) - self.create_from_template_list.setModel(self.create_from_template_model) - - def update_mru_list(self, used_project: str) -> None: - """ - Promote a supplied project name to the "most recent" in a given MRU list. - :param used_project: path to project to promote - :param file_path: path to mru list file - :return: None - """ - used_project = os.path.normpath(used_project) - if not os.path.exists(os.path.dirname(self.mru_file_path)): - os.makedirs(os.path.dirname(self.mru_file_path), exist_ok=True) - mru_data = {} - try: - with open(self.mru_file_path, 'r') as mru_file: - mru_data = json.loads(mru_file.read()) - except FileNotFoundError: - pass - except json.JSONDecodeError: - pass - - recent_list = mru_data.get('Projects', []) - recent_list = [item for item in recent_list if item.get('Path') != used_project and - self.is_project_folder(item.get('Path'))] - - new_list = [{'Path': used_project}] - new_list.extend(recent_list) - - mru_data['Projects'] = new_list - try: - with open(self.mru_file_path, 'w') as mru_file: - mru_file.write(json.dumps(mru_data, indent=1)) - except PermissionError as e: - logger.warning(f"Failed to write {self.mru_file_path} with error {e}") - - def get_mru_list(self) -> List[str]: - """ - Retrieve the current MRU list. Does not perform validation that the projects still appear valid - :return: list of full path strings to project folders - """ - if not os.path.exists(os.path.dirname(self.mru_file_path)): - return [] - try: - with open(self.mru_file_path, 'r') as mru_file: - mru_data = json.loads(mru_file.read()) - except FileNotFoundError: - return [] - except json.JSONDecodeError: - logger.error(f'MRU list at {self.mru_file_path} is not valid JSON') - return [] - - recent_list = mru_data.get('Projects', []) - return [item.get('Path') for item in recent_list if item.get('Path') is not None] - - @Slot(str) - def handle_log_message(self, message: str) -> None: - """ - Signal handler for messages from the logger. Displays the most recent warning/error - :param message: formatted log message from DialogLoggerSignaller - :return: - """ - if not self.log_display: - return - self.log_display.setText(message) - self.log_display.setToolTip(message) - - -if __name__ == "__main__": - dialog_app = QApplication(sys.argv) - my_dialog = ProjectManagerDialog() - dialog_app.exec_() - sys.exit(0) diff --git a/scripts/project_manager/pyside.py b/scripts/project_manager/pyside.py deleted file mode 100755 index 80bf327f9b..0000000000 --- a/scripts/project_manager/pyside.py +++ /dev/null @@ -1,61 +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. -# - -import os -import logging - -from pathlib import Path - -logger = logging.getLogger() - -pyside_initialized = False -old_env = os.environ.copy() - -# Helper to extend OS PATH for pyside to locate our QT binaries based on our build folder -def add_pyside_environment(bin_path): - if is_pyside_ready(): - # No need to reinitialize currently - logger.info("Pyside environment already initialized") - return - global old_env - old_env = os.environ.copy() - binaries_path = Path(os.path.normpath(bin_path)) - platforms_path = binaries_path.joinpath("platforms") - logger.info(f'Adding binaries path {binaries_path}') - os.environ["QT_QPA_PLATFORM_PLUGIN_PATH"] = str(platforms_path) - - path = os.environ['PATH'] - - new_path = os.pathsep.join([str(binaries_path), str(platforms_path), path]) - os.environ['PATH'] = new_path - - global pyside_initialized - pyside_initialized = True - - -def is_pyside_ready(): - return pyside_initialized - - -def is_configuration_valid(workspace): - return os.path.basename(workspace.paths.build_directory()) != "debug" - - -def uninstall_env(): - if not is_pyside_ready(): - logger.warning("Pyside not initialized") - return os.environ - - global old_env - if old_env.get("QT_QPA_PLATFORM_PLUGIN_PATH"): - old_env.pop("QT_QPA_PLATFORM_PLUGIN_PATH") - os.environ = old_env - return old_env diff --git a/scripts/project_manager/tests/__init__.py b/scripts/project_manager/tests/__init__.py deleted file mode 100755 index 4d5680a30d..0000000000 --- a/scripts/project_manager/tests/__init__.py +++ /dev/null @@ -1,10 +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. -# diff --git a/scripts/project_manager/tests/test_projects.py b/scripts/project_manager/tests/test_projects.py deleted file mode 100755 index d073cf6c7a..0000000000 --- a/scripts/project_manager/tests/test_projects.py +++ /dev/null @@ -1,255 +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. -# - - -import pytest -''' -import os -import sys -import tempfile -import logging -import pathlib -from unittest.mock import MagicMock - -logger = logging.getLogger() - -# Code lives one folder above -project_manager_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) -sys.path.append(project_manager_path) - -from pyside import add_pyside_environment, is_configuration_valid -from ly_test_tools import WINDOWS - -sys.path.append(os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))) -executable_path = '' -from cmake.Tools import registration -from cmake.Tools import engine_template - - -class ProjectHelper: - def __init__(self): - self._temp_directory = pathlib.Path(tempfile.TemporaryDirectory().name).resolve() - self._temp_directory.mkdir(parents=True, exist_ok=True) - - self.home_path = self._temp_directory - registration.override_home_folder = self.home_path - self.engine_path = registration.get_this_engine_path() - if registration.register(engine_path=self.engine_path): - assert True, f"Failed to register the engine." - - if registration.register_shipped_engine_o3de_objects(): - assert True, f"Failed to register shipped engine objects." - - self.projects_folder = registration.get_o3de_projects_folder() - if not self.projects_folder.is_dir(): - assert True - - self.application = None - self.dialog = None - - def create_empty_projects(self): - self.project_1_dir = self.projects_folder / "Project1" - if engine_template.create_project(project_manager_path=self.project_1_dir): - assert True, f"Failed to create Project1." - - self.project_2_dir = self.projects_folder / "Project2" - if engine_template.create_project(project_manager_path=self.project_2_dir): - assert True, f"Failed to create Project2." - - self.project_3_dir = self.projects_folder / "Project3" - if engine_template.create_project(project_manager_path=self.project_3_dir): - assert True, f"Failed to create Project3." - - self.invalid_project_dir = self.projects_folder / "InvalidProject" - self.invalid_project_dir.mkdir(parents=True, exist_ok=True) - - def setup_dialog_test(self, workspace): - add_pyside_environment(workspace.paths.build_directory()) - - if not is_configuration_valid(workspace): - # This is essentially skipif debug. Our debug tests use our profile version of python, but that means we'd - # need to use the profile version of PySide which works with the profile QT libs which aren't in the debug - # folder we've built. - return None - - from PySide2.QtWidgets import QApplication, QMessageBox - - if QApplication.instance(): - self.application = QApplication.instance() - else: - self.application = QApplication(sys.argv) - assert self.application - - from projects import ProjectManagerDialog - - try: - self.dialog = ProjectManagerDialog(settings_folder=self.home_path) - return self.dialog - except Exception as e: - logger.error(f'Failed to create ProjectManagerDialog with error {e}') - return None - - def create_project_from_template(self, project_name) -> bool: - """ - Uses the dialog to create a temporary project based on the first template found - :param project_name: Name of project to create. Will be created under temp_project_root - :return: True for Success, False for failure - """ - from PySide2.QtWidgets import QWidget, QFileDialog - from projects import ProjectManagerDialog - - QWidget.exec = MagicMock() - self.dialog.create_project_handler() - QWidget.exec.assert_called_once() - - assert len(self.dialog.project_templates), 'Failed to find any project templates' - ProjectManagerDialog.get_selected_project_template = MagicMock(return_value=self.dialog.project_templates[0]) - - QFileDialog.exec = MagicMock() - create_project_path = self.projects_folder / project_name - QFileDialog.selectedFiles = MagicMock(return_value=[create_project_path]) - self.dialog.create_project_accepted_handler() - if create_project_path.is_dir(): - assert True, f"Expected project creation folder not found at {create_project_path}" - - if QWidget.exec.call_count == 2: - assert True, "Message box confirming project creation failed to show" - - -@pytest.fixture -def project_helper(): - return ProjectHelper() - - -@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently") -@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent -def test_logger_handler(workspace, project_helper): - my_dialog = project_helper.setup_dialog_test(workspace) - if not my_dialog: - return - - from PySide2.QtWidgets import QMessageBox - QMessageBox.warning = MagicMock() - logger.error(f'Testing logger') - QMessageBox.warning.assert_called_once() - - -@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently") -@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent -def test_mru_list(workspace, project_helper): - my_dialog = project_helper.setup_dialog_test(workspace) - if not my_dialog: - return - - project_helper.create_empty_projects() - - from PySide2.QtWidgets import QMessageBox - - mru_list = my_dialog.get_mru_list() - assert len(mru_list) == 0, f'MRU list unexpectedly had entries: {mru_list}' - - QMessageBox.warning = MagicMock() - my_dialog.add_project(project_helper.invalid_project_dir) - mru_list = my_dialog.get_mru_list() - assert len(mru_list) == 0, f'MRU list unexpectedly added an invalid project : {mru_list}' - QMessageBox.warning.assert_called_once() - - my_dialog.add_project(project_helper.project_1_dir) - mru_list = my_dialog.get_mru_list() - assert len(mru_list) == 1, f'MRU list failed to add project at {project_helper.project_1_dir}' - - my_dialog.add_project(project_helper.project_1_dir) - mru_list = my_dialog.get_mru_list() - assert len(mru_list) == 1, f'MRU list added project at {project_helper.project_1_dir} a second time : {mru_list}' - - my_dialog.update_mru_list(project_helper.project_1_dir) - mru_list = my_dialog.get_mru_list() - assert len(mru_list) == 1, f'MRU list added project at {project_helper.project_1_dir} a second time : {mru_list}' - - my_dialog.add_project(project_helper.project_2_dir) - mru_list = my_dialog.get_mru_list() - assert len(mru_list) == 2, f'MRU list failed to add project at {project_helper.project_2_dir}' - - assert mru_list[0] == project_helper.project_2_dir, f"{project_helper.project_2_dir} wasn't first item" - assert mru_list[1] == project_helper.project_1_dir, f"{project_helper.project_1_dir} wasn't second item" - - my_dialog.update_mru_list(project_helper.project_1_dir) - mru_list = my_dialog.get_mru_list() - assert len(mru_list) == 2, f'MRU list added wrong items {mru_list}' - assert mru_list[0] == project_helper.project_1_dir, f"{project_helper.project_1_dir} wasn't first item" - assert mru_list[1] == project_helper.project_2_dir, f"{project_helper.project_2_dir} wasn't second item" - - my_dialog.add_project(project_helper.invalid_project_dir) - mru_list = my_dialog.get_mru_list() - assert len(mru_list) == 2, f'MRU list added invalid item {mru_list}' - assert mru_list[0] == project_helper.project_1_dir, f"{project_helper.project_1_dir} wasn't first item" - assert mru_list[1] == project_helper.project_2_dir, f"{project_helper.project_2_dir} wasn't second item" - - my_dialog.add_project(project_helper.project_3_dir) - mru_list = my_dialog.get_mru_list() - assert len(mru_list) == 3, f'MRU list failed to add {project_helper.project_3_dir} : {mru_list}' - assert mru_list[0] == project_helper.project_3_dir, f"{project_helper.project_3_dir} wasn't first item" - assert mru_list[1] == project_helper.project_1_dir, f"{project_helper.project_1_dir} wasn't second item" - assert mru_list[2] == project_helper.project_2_dir, f"{project_helper.project_2_dir} wasn't third item" - - -@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently") -@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent -def test_create_project(workspace, project_helper): - my_dialog = project_helper.setup_dialog_test(workspace) - if not my_dialog: - return - - project_helper.create_project_from_template("TestCreateProject") - - -@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently") -@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent -def test_add_remove_gems(workspace, project_helper): - my_dialog = project_helper.setup_dialog_test(workspace) - if not my_dialog: - return - - my_project_name = "TestAddRemoveGems" - - project_helper.create_project_from_template(project_manager_path=my_project_name) - my_project_path = project_helper.projects_folder / my_project_name - - from PySide2.QtWidgets import QWidget, QFileDialog - from projects import ProjectManagerDialog - - assert my_dialog.get_selected_project_path() == my_project_path, "TestAddRemoveGems project not selected" - QWidget.exec = MagicMock() - my_dialog.manage_gems_handler() - assert my_dialog.manage_gem_targets_dialog, "No gem management dialog created" - QWidget.exec.assert_called_once() - - if not len(my_dialog.all_gems_list): - assert True, 'Failed to find any gems' - - my_test_gem_path = my_dialog.all_gems_list[0] - gem_data = registration.get_gem_data(my_test_gem_path) - my_test_gem_selection = (my_test_gem_name, my_test_gem_path) - ProjectManagerDialog.get_selected_add_gems = MagicMock(return_value=[my_test_gem_selection]) - - assert my_test_gem_name, "No Name set in test gem" - assert my_test_gem_name not in my_dialog.project_gem_list, f'Gem {my_test_gem_name} already in project gem list' - - my_dialog.add_gems_handler() - assert my_test_gem_name in my_dialog.project_gem_list, f'Gem {my_test_gem_name} failed to add to gem list' - - ProjectManagerDialog.get_selected_project_gems = MagicMock(return_value=[my_test_gem_name]) - my_dialog.remove_gems_handler() - assert my_test_gem_name not in my_dialog.project_gem_list, f'Gem {my_test_gem_name} still in project gem list' -''' - -def test_project_place_holder(): - pass \ No newline at end of file diff --git a/scripts/project_manager/tests/test_pyside.py b/scripts/project_manager/tests/test_pyside.py deleted file mode 100755 index c9be313f2b..0000000000 --- a/scripts/project_manager/tests/test_pyside.py +++ /dev/null @@ -1,54 +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. -# - -import pytest -import sys -import os - -pyside_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) -sys.path.append(pyside_path) - -from pyside import add_pyside_environment, is_pyside_ready, is_configuration_valid - -from ly_test_tools import WINDOWS - -import logging -logger = logging.getLogger() - -@pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently") -@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent -def test_add_pyside_environment(workspace): - import_failed = False - try: - import PySide2 - except ImportError: - import_failed = True - - if not import_failed: - cur_path = sys.path - logger.warning(f"Expected to fail initial import but passed. Sys path was {cur_path}") - - assert is_pyside_ready() is False, "Expected pyside not to be initialized yet" - add_pyside_environment(workspace.paths.build_directory()) - assert is_pyside_ready() is True, "Expected pyside to be initialized yet" - - try: - import PySide2 - if not is_configuration_valid(workspace): - return - from PySide2.QtWidgets import QApplication - except ImportError as e: - assert False, f"Failed to import PySide2 with error {e}" - try: - from PySide2.QtWidgets import QApplication - except ImportError as e: - assert False, f"Failed to import QApplication from PySide2.QtWidgets with error {e}" - diff --git a/scripts/project_manager/ui/create_from_template.ui b/scripts/project_manager/ui/create_from_template.ui deleted file mode 100644 index b6a8740594..0000000000 --- a/scripts/project_manager/ui/create_from_template.ui +++ /dev/null @@ -1,94 +0,0 @@ - - - createFromTemplateDialog - - - - 0 - 0 - 467 - 288 - - - - Create From Template - - - - - 50 - 250 - 400 - 32 - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - 10 - 20 - 449 - 221 - - - - QAbstractItemView::SingleSelection - - - - - - 10 - 0 - 300 - 16 - - - - Available Templates - - - - - - - okCancel - accepted() - createFromTemplateDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - okCancel - rejected() - createFromTemplateDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/scripts/project_manager/ui/create_gem.ui b/scripts/project_manager/ui/create_gem.ui deleted file mode 100644 index 5a5fd14a6d..0000000000 --- a/scripts/project_manager/ui/create_gem.ui +++ /dev/null @@ -1,94 +0,0 @@ - - - createGemDialog - - - - 0 - 0 - 467 - 288 - - - - Create Gem - - - - - 50 - 250 - 400 - 32 - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - 10 - 20 - 449 - 221 - - - - QAbstractItemView::SingleSelection - - - - - - 10 - 0 - 300 - 16 - - - - Available Templates - - - - - - - okCancel - accepted() - createGemDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - okCancel - rejected() - createGemDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/scripts/project_manager/ui/create_project.ui b/scripts/project_manager/ui/create_project.ui deleted file mode 100644 index 67a13325b0..0000000000 --- a/scripts/project_manager/ui/create_project.ui +++ /dev/null @@ -1,94 +0,0 @@ - - - createProjectDialog - - - - 0 - 0 - 467 - 288 - - - - Create Project - - - - - 50 - 250 - 400 - 32 - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - 10 - 20 - 449 - 221 - - - - QAbstractItemView::SingleSelection - - - - - - 10 - 0 - 300 - 16 - - - - Available Templates - - - - - - - okCancel - accepted() - createProjectDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - okCancel - rejected() - createProjectDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/scripts/project_manager/ui/manage_gem_targets.ui b/scripts/project_manager/ui/manage_gem_targets.ui deleted file mode 100644 index ea9e607d42..0000000000 --- a/scripts/project_manager/ui/manage_gem_targets.ui +++ /dev/null @@ -1,165 +0,0 @@ - - - manageGemTargetsDialog - - - - 0 - 0 - 702 - 297 - - - - Manage Gem Targets - - - - - 310 - 260 - 71 - 32 - - - - Qt::Horizontal - - - QDialogButtonBox::Close - - - - - - 440 - 50 - 250 - 221 - - - - - 0 - 0 - - - - QAbstractItemView::ExtendedSelection - - - - - - 10 - 50 - 250 - 221 - - - - QAbstractItemView::ExtendedSelection - - - - - - 440 - 30 - 251 - 16 - - - - Available Gem Targets - - - - - - 10 - 30 - 251 - 16 - - - - Enabled Gem Targets - - - - - - 264 - 130 - 171 - 23 - - - - Remove Gem Targets >> - - - - - - 264 - 100 - 171 - 23 - - - - << Add Gem Targets - - - - - - 10 - 5 - 400 - 21 - - - - Adding new Gem Targets may require rebuilding - - - - - - - close - accepted() - manageGemTargetsDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - close - rejected() - manageGemTargetsDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/scripts/project_manager/ui/project_manager.ico b/scripts/project_manager/ui/project_manager.ico deleted file mode 100644 index 597266946a..0000000000 --- a/scripts/project_manager/ui/project_manager.ico +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:113be7ded1969e0d535722382b6d3730eed052a10430cf23804ae7f20414b999 -size 108278 diff --git a/scripts/project_manager/ui/project_manager.ui b/scripts/project_manager/ui/project_manager.ui deleted file mode 100644 index 6b3b5f758c..0000000000 --- a/scripts/project_manager/ui/project_manager.ui +++ /dev/null @@ -1,407 +0,0 @@ - - - Dialog - - - - 0 - 0 - 712 - 395 - - - - - 1 - 0 - - - - O3DE - - - Select and manage your projects for O3DE - - - - - 540 - 360 - 161 - 31 - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - 10 - 30 - 691 - 31 - - - - Current project to launch or manage gems for. - - - - - - 10 - 10 - 47 - 13 - - - - Project - - - - - - 270 - 220 - 431 - 141 - - - - QFrame::Panel - - - QFrame::Sunken - - - - - - true - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - - - - 10 - 70 - 241 - 151 - - - - Create - - - - - 10 - 110 - 221 - 31 - - - - Create a new O3DE object from a pre configured template. - - - Create From Template - - - - - - 10 - 20 - 221 - 31 - - - - Create a new O3DE object from a pre configured template. - - - Create Project - - - - - - 11 - 50 - 221 - 31 - - - - Create a new O3DE object from a pre configured template. - - - Create Gem - - - - - - 11 - 80 - 221 - 31 - - - - Create a new O3DE object from a pre configured template. - - - Create Template - - - - - - - 260 - 70 - 451 - 141 - - - - Registration - - - - - 10 - 80 - 211 - 31 - - - - Browse for an existing O3DE project. - - - Add Template - - - - - - 10 - 20 - 211 - 31 - - - - Browse for an existing O3DE project. - - - Add Project - - - - - - 10 - 50 - 211 - 31 - - - - Browse for an existing O3DE project. - - - Add Gem - - - - - - 10 - 110 - 211 - 31 - - - - Browse for an existing O3DE project. - - - Add Restricted - - - - - - 230 - 110 - 211 - 31 - - - - Browse for an existing O3DE project. - - - Remove Restricted - - - - - - 230 - 20 - 211 - 31 - - - - Browse for an existing O3DE project. - - - Remove Project - - - - - - 230 - 50 - 211 - 31 - - - - Browse for an existing O3DE project. - - - Remove Gem - - - - - - 230 - 80 - 211 - 31 - - - - Browse for an existing O3DE project. - - - Remove Template - - - - - - - 10 - 230 - 241 - 121 - - - - Manage Project - - - - - 10 - 80 - 221 - 31 - - - - Add or remove gems from your selected project. Gems add and remove additional assets and features to projects. - - - Manage Server Gem Targets - - - - - - 10 - 50 - 221 - 31 - - - - Add or remove gems from your selected project. Gems add and remove additional assets and features to projects. - - - Manage Tool Gem Targets - - - - - - 10 - 20 - 221 - 31 - - - - Add or remove gems from your selected project. Gems add and remove additional assets and features to projects. - - - Manage Runtime Gem Targets - - - - - - - - okCancel - accepted() - Dialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - okCancel - rejected() - Dialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - -